編輯:關於Android編程
可能小伙伴們讀了我上一篇博客關於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 CREATOR
= new Parcelable.Creator() {
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");
Listlist=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) {
Listlist2=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());
}
}
}
這就很容易的將對象寫入到文件中啦,是不是很方便呢?小伙伴們點個贊吧!!!!
原文地址 MasteringAndroidDataBinding本教程是跟著 Data Binding Guide 學習過程中得出的一些實踐經驗,同時修改了官方教程的一
剛開始學對於這個機制理解不夠深刻,現在重新整理下思路。開發中,接口回調是我們經常用到的。接口回調的意思即,注冊之後並不立馬執行,而在某個時機觸發執行。首先解決啥是回調:舉
本文實例講述了android通過Location API顯示地址信息的實現方法。分享給大家供大家參考。具體如下:android的Locatin API,可以通過Geoco
本文為大家分享了Android實現拖動選擇按鈕的具體代碼,供大家參考,具體內容如下效果圖View代碼第一步:自定義屬性 <declare-styleable nam