1. 程式人生 > >Android檔案儲存的問題:ClassLoader和實現Parcelable介面後 詳解及用途

Android檔案儲存的問題:ClassLoader和實現Parcelable介面後 詳解及用途

可能小夥伴們讀了我上一篇部落格關於Android檔案儲存的的,在檔案操作的時候大家有沒有疑問呀,有就對了,可能在儲存自定義物件的時候,如何序列化呀?ClassLoader到底是啥鬼呀?序列化後怎麼讀取出來呀?好吧,針對於上面的問題,我一個一個的說明吧!
今天主要是講ClassLoader的用途,

ClassLoader主要對類的請求提供服務,當JVM需要某類時,它根據名稱向ClassLoader要求這個類,然後由ClassLoader返回這個類的class物件。 1.1 幾個相關概念ClassLoader負責載入系統的所有Resources(Class,檔案,來自網路的位元組流等),通過ClassLoader從而將資源載入JVM 
每個class都有一個reference,指向自己的ClassLoader。Class.getClassLoader
() array的ClassLoader就是其元素的ClassLoader,若是基本資料型別,則這個array沒有ClassLoader 1.2 主要方法和工作過程Java1.1及從前版本中,ClassLoader主要方法: Class loadClass( String name, boolean resolve ); ClassLoader.loadClass() 是 ClassLoader 的入口點 defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始位元組組成的陣列並把它轉換成 Class 物件。原始陣列包含如從檔案系統或網路裝入的資料。 findSystemClass 方法從本地檔案系統裝入檔案。它在本地檔案系統中尋找類檔案,如果存在,就使用 defineClass 將原始位元組轉換成 Class 物件,以將該檔案轉換成類。當執行 Java 應用程式時,這是 JVM 正常裝入類的預設機制。 resolveClass可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以呼叫 resolveClass,這取決於 loadClass 的 resolve 引數的值 findLoadedClass 充當一個快取:當請求 loadClass 裝入類時,它呼叫該方法來檢視 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應首先呼叫該方法 一般load方法過程如下: 呼叫 findLoadedClass 來檢視是否存在已裝入的類。 如果沒有,那麼採用某種特殊的神奇方式來獲取原始位元組。(通過IO從檔案系統,來自網路的位元組流等) 如果已有原始位元組,呼叫 defineClass 將它們轉換成 Class 物件。 如果沒有原始位元組,然後呼叫 findSystemClass 檢視是否從本地檔案系統獲取類。 如果 resolve 引數是 true,那麼呼叫 resolveClass 解析 Class 物件。 如果還沒有類,返回 ClassNotFoundException。 否則,將類返回給呼叫程式。 1.3
委託模型自從JDK1.2以後,ClassLoader做了改進,使用了委託模型,所有系統中的ClassLoader組成一棵樹,ClassLoader在載入類庫時先讓Parent尋找,Parent找不到才自己找。 JVM在執行時會產生三個ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。其中,Bootstrap ClassLoader是用C++編寫的,在Java中看不到它,是null。它用來載入核心類庫,就是在lib下的類庫,Extension ClassLoader載入lib/ext下的類庫,App ClassLoader載入Classpath裡的類庫,三者的關係為:App ClassLoader的Parent是Extension ClassLoader,而Extension ClassLoader的Parent為Bootstrap ClassLoader。載入一個類時,首先BootStrap進行尋找,找不到再由Extension ClassLoader尋找,最後才是App ClassLoader。 將ClassLoader設計成委託模型的一個重要原因是出於安全考慮,比如在Applet中,如果編寫了一個java.lang
.String類並具有破壞性。假如不採用這種委託機制,就會將這個具有破壞性的String載入到了使用者機器上,導致破壞使用者安全。但採用這種委託機制則不會出現這種情況。因為要載入java.lang.String類時,系統最終會由Bootstrap進行載入,這個具有破壞性的String永遠沒有機會載入。 委託模型還帶來了一些問題,在某些情況下會產生混淆,如下是Tomcat的ClassLoader結構圖: Bootstrap | System | Common / Catalina Shared / Webapp1 Webapp2 ... 由 Common 類裝入器裝入的類決不能(根據名稱)直接訪問由 Web 應用程式裝入的類。使這些類聯絡在一起的唯一方法是通過使用這兩個類集都可見的介面。在這個例子中,就是包含由 Java servlet 實現的 javax.servlet.Servlet。 如果在lib或者lib/ext等類庫有與應用中同樣的類,那麼應用中的類將無法被載入。通常在jdk新版本出現有類庫移動時會出現問題,例如最初我們使用自己的xml解析器,而在jdk1.4中xml解析器變成標準類庫,load的優先順序也高於我們自己的xml解析器,我們自己的xml解析器永遠無法找到,將可能導致我們的應用無法執行。 相同的類,不同的ClassLoader,將導致ClassCastException異常 1.4 執行緒中的ClassLoader每個執行中的執行緒都有一個成員contextClassLoader,用來在執行時動態地載入其它類,可以使用方法Thread.currentThread().setContextClassLoader(...);更改當前執行緒的contextClassLoader,來改變其載入類的行為;也可以通過方法Thread.currentThread().getContextClassLoader()來獲得當前執行緒的ClassLoader。 實際上,在Java應用中所有程式都執行線上程裡,如果在程式中沒有手工設定過ClassLoader,對於一般的java類如下兩種方法獲得的ClassLoader通常都是同一個 this.getClass.getClassLoader(); Thread.currentThread().getContextClassLoader(); 方法一得到的Classloader是靜態的,表明類的載入者是誰;方法二得到的Classloader是動態的,誰執行(某個執行緒),就是那個執行者的Classloader。對於單例模式的類,靜態類等,載入一次後,這個例項會被很多程式(執行緒)呼叫,對於這些類,載入的Classloader和執行執行緒的Classloader通常都不同。 1.5 Web應用中的ClassLoader回到上面的例子,在Tomcat裡,WebApp的ClassLoader的工作原理有點不同,它先試圖自己載入類(在ContextPath/WEB-INF/...中載入類),如果無法載入,再請求父ClassLoader完成。 由此可得: 對於WEB APP執行緒,它的contextClassLoader是WebAppClassLoader 對於Tomcat Server執行緒,它的contextClassLoader是CatalinaClassLoader 1.6 獲得ClassLoader的幾種方法可以通過如下3種方法得到ClassLoader this.getClass.getClassLoader(); // 使用當前類的ClassLoader Thread.currentThread().getContextClassLoader(); // 使用當前執行緒的ClassLoader ClassLoader.getSystemClassLoader(); // 使用系統ClassLoader,即系統的入口點所使用的ClassLoader。(注意,system ClassLoader與根ClassLoader並不一樣。JVM下system ClassLoader通常為App ClassLoader) 1.7 幾種擴充套件應用使用者定製自己的ClassLoader可以實現以下的一些應用 安全性。類進入JVM之前先經過ClassLoader,所以可以在這邊檢查是否有正確的數字簽名等 加密。java位元組碼很容易被反編譯,通過定製ClassLoader使得位元組碼先加密防止別人下載後反編譯,這裡的ClassLoader相當於一個動態的解碼器 歸檔。可能為了節省網路資源,對自己的程式碼做一些特殊的歸檔,然後用定製的ClassLoader來解檔 自展開程式。把java應用程式編譯成單個可執行類檔案,這個檔案包含壓縮的和加密的類檔案資料,同時有一個固定的ClassLoader,當程式執行時它在記憶體中完全自行解開,無需先安裝 動態生成。可以生成應用其他還未生成類的類,實時建立整個類並可在任何時刻引入JVM 2.0 資源載入 所有資源都通過ClassLoader載入到JVM裡,那麼在載入資源時當然可以使用ClassLoader,只是對於不同的資源還可以使用一些別的方式載入,例如對於類可以直接new,對於檔案可以直接做IO等。 2.1 載入類的幾種方法假設有類A和類B,A在方法amethod裡需要例項化B,可能的方法有3種。對於載入類的情況,使用者需要知道B類的完整名字(包括包名,例如"com.rain.B"1. 使用Class靜態方法 Class.forName Class cls = Class.forName("com.rain.B"); B b = (B)cls.newInstance(); 2. 使用ClassLoader /* Step 1. Get ClassLoader */ ClassLoader cl; // 如何獲得ClassLoader參考1.6 /* Step 2. Load the class */ Class cls = cl.loadClass("com.rain.B"); // 使用第一步得到的ClassLoader來載入B /* Step 3. new instance */ B b = (B)cls.newInstance(); // 有B的類得到一個B的例項 3. 直接new B b = new B(); 2.2 檔案載入(例如配置檔案等)假設在com.rain.A類裡想讀取資料夾 /com/rain/config 裡的檔案sys.properties,讀取檔案可以通過絕對路徑或相對路徑,絕對路徑很簡單,在Windows下以盤號開始,在Unix下以"/"開始 對於相對路徑,其相對值是相對於ClassLoader的,因為ClassLoader是一棵樹,所以這個相對路徑和ClassLoader樹上的任何一個ClassLoader相對比較後可以找到檔案,那麼檔案就可以找到,當然,讀取檔案也使用委託模型 1. 直接IO /** * 假設當前位置是 "C:/test",通過執行如下命令來執行A "java com.rain.A" * 1. 在程式裡可以使用絕對路徑,Windows下的絕對路徑以盤號開始,Unix下以"/"開始 * 2. 也可以使用相對路徑,相對路徑前面沒有"/" * 因為我們在 "C:/test" 目錄下執行程式,程式入口點是"C:/test",相對路徑就 * 是 "com/rain/config/sys.properties" * (例子中,當前程式的ClassLoader是App ClassLoader,system ClassLoader = 當前的 * 程式的ClassLoader,入口點是"C:/test") * 對於ClassLoader樹,如果檔案在jdk lib下,如果檔案在jdk lib/ext下,如果檔案在環境變數裡, * 都可以通過相對路徑"sys.properties"找到,lib下的檔案最先被找到 */ File f = new File("C:/test/com/rain/config/sys.properties"); // 使用絕對路徑 //File f = new File("com/rain/config/sys.properties"); // 使用相對路徑 InputStream is = new FileInputStream(f); 如果是配置檔案,可以通過java.util.Properties.load(is)將內容讀到Properties裡,Properties預設認為is的編碼是ISO-8859-1,如果配置檔案是非英文的,可能出現亂碼問題。 2. 使用ClassLoader /** * 因為有3種方法得到ClassLoader,對應有如下3種方法讀取檔案 * 使用的路徑是相對於這個ClassLoader的那個點的相對路徑,此處只能使用相對路徑 */ InputStream is = null; is = this.getClass().getClassLoader().getResourceAsStream( "com/rain/config/sys.properties"); //方法1 //is = Thread.currentThread().getContextClassLoader().getResourceAsStream( "com/rain/config/sys.properties"); //方法2 //is = ClassLoader.getSystemResourceAsStream("com/rain/config/sys.properties"); //方法3 如果是配置檔案,可以通過java.util.Properties.load(is)將內容讀到Properties裡,這裡要注意編碼問題。 3. 使用ResourceBundle ResourceBundle bundle = ResourceBundle.getBoundle("com.rain.config.sys"); 這種用法通常用來載入使用者的配置檔案,關於ResourceBunlde更詳細的用法請參考其他文件 總結:有如下3種途徑來載入檔案 1. 絕對路徑 ---> IO 2. 相對路徑 ---> IO ---> ClassLoader 3. 資原始檔 ---> ResourceBundle 2.3 如何在web應用裡載入資源在web應用裡當然也可以使用ClassLoader來載入資源,但更常用的情況是使用ServletContext,如下是web目錄結構 ContextRoot |- JSP、HTML、Image等各種檔案 |- [WEB-INF] |- web.xml |- [lib] Web用到的JAR檔案 |- [classes] 類檔案 使用者程式通常在classes目錄下,如果想讀取classes目錄裡的檔案,可以使用ClassLoader,如果想讀取其他的檔案,一般使用ServletContext.getResource() 如果使用ServletContext.getResource(path)方法,路徑必須以"/"開始,路徑被解釋成相對於ContextRoot的路徑,此處載入檔案的方法和ClassLoader不同,舉例"/WEB-INF/web.xml","/download/WebExAgent.rar"

上面就是關於CLassLoader的作用和解釋了,
下面來說說parcelable介面的呼叫吧,我擦,上網一查,大一堆呀,先來看看下過吧,無圖無真相
這裡寫圖片描述
這是儲存實體集合
這裡寫圖片描述
怎麼實現的呢?
先自定義一個測試實體類,實現序列化介面,瞎寫的一個測試類哈,這不是重點,

package com.example.entity;

import java.io.Serializable;
import java.util.Date;

import android.os.Parcel;
import android.os.Parcelable;


public class Test implements Parcelable,Serializable{
    public int id;

    public String name ;
    public String workdaty;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getWorkdaty() {
        return workdaty;
    }
    public void setWorkdaty(String workdaty) {
        this.workdaty = workdaty;
    }


    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }


    public static final Parcelable.Creator<Test> CREATOR
    = new Parcelable.Creator<Test>() {
        public Test createFromParcel(Parcel in) {
            return new Test(in);
        }

        public Test[] newArray(int size) {
            return new Test[size];
        }
    };

    public Test(Parcel in) {
        id = in.readInt();
        name=in.readString();

    }


}

在一個activity中,誰便寫了一個:
其中的檔案工具類就是我的上一篇部落格

package com.example.do0me;


import java.util.ArrayList;
import java.util.List;

import com.example.entity.Test;
import com.example.util.FileUtil;

import android.app.Activity;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
 * 檔案資料各種測試
 * @author Administrator
 *
 */
public class FileTestActivity extends Activity {
    private FileUtil fileutil;
    private boolean b;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        fileutil=new FileUtil();
        testFille();
    }
    /**
     * 測試方法
     */
    @SuppressWarnings("static-access")
    private void testFille() {
        Parcel p=Parcel.obtain();
        Test test=new Test(p);
        test.setId(1);
        test.setName("double 江");
        test.setWorkdaty("1994-11-04");
        List<Parcelable>list=new ArrayList<>();
        Test[] tests=new Test[5];
        for (int i = 0; i <5; i++) {
            tests[i]=new Test(p);
            tests[i].setName("我是double江="+i);
            list.add(tests[i]);
        }
        Log.i("長度", list.size()+"");
        if (!"".equals(list)) {
            boolean c=fileutil.writeParcelableList(getApplicationContext(), "listobject.dat", list);
            if (c) {
                List<Parcelable>list2=new ArrayList<>();
                list2=fileutil.readParcelableList(getApplicationContext(), "listobject.dat", this.getClassLoader());
                if (null!=list2) {
                    for (int i = 0; i < list2.size(); i++) {
                    System.out.println("獲得集合資料"+((Test)list2.get(i)).getName());
                    }
                }
            }
        }
        //------------
        boolean bp=fileutil.writeParcelable(getApplicationContext(), "agbc.dat", test);
        if (bp) {
            Test test2=(Test) fileutil.readParcelable(getApplicationContext(), "agbc.dat", this.getClassLoader());
            Log.i("讀出序列化物件",test2.getName() );
        }
        //----------------
        b=fileutil.saveSerializable(getApplicationContext(), "object.dat", test);   
        if (b) {
            Test test2= (Test) fileutil.readSerialLizable(getApplicationContext(), "object.dat");
            System.err.println("姓名"+test2.getName()+"\n Id號"+test.getId());
        }
    }
}

這就很容易的將物件寫入到檔案中啦,是不是很方便呢?小夥伴們點個贊吧!!!!