java类加载器 架构 设计_类加载器(DexClassLoader)与插件化(动态加载)
類加載器與插件化解析
2.1 類裝載器 DexClassLoader
首先,我們需要了解關于java代碼本地import的一些知識:
import中所引用的類有兩個特點:
1.必須存在于本地,當程序運行時需要該類時,內部類裝載器會自動裝載該類,這對程序員來講是透明的,即程序員感知不到該過程
2.編譯時必須在現場,否則編譯過程會因為找不到引用文件而不能正常編譯。
使用ClassLoader的必要說明,多用于動態加載一些自定義的類。
一般情況下,應用程序不需要創建一個全新的ClassLoader,而是使用當前環境中已經存在的ClassLoader。因為Java的Runtime環境在初始化時,其內部會創建一個ClassLoader對象用于加載Runtime所需的各種Java類。
每個ClassLoader必須有一個父ClassLoader,在裝載Class文件時,子ClassLoader會先請求其父ClassLoader加載該Class文件,只有當其父ClassLoader找不到該Class時,子ClassLoader才會繼續裝載該類,這是一種安全機制。
Android使用的是DexClassLoader。
下面簡單介紹下DexClassLoader的使用方法
假設這里有兩個APK,第一個叫做Host,第二個叫做Plugin,其中Plugin中定義了一個PluginClass,該類中定義了一個函數,functionl(),代碼如下:
public class PluginClass {
public PluginClass(){
Log.i( "Plugin" , "PluginClass client initialized" ) ;
}
public int functionl( int a, int b ) {
return a + b;
}
}
我們剩下要做的就是在這個Host APK中去掉用我們PluginClass的functionl()方法。
這里用注釋解釋下代碼
public void useDexClassLoader(){
//確定目標class所在的位置
Intent intent = new Intent("com.haiii.android.plugin.client",null);
//通過PackageManager獲取信息。
PackageManager pm = getPackageManager();
final List plugins = pm.queryIntentActivities(intent,0);
ResolveInfo info = plugins.get(0);
ActivityInfo ainfo = info.activityInfo;
//當存在多個dexPath路徑時,需要此分隔符
String div = System.getProperty("npath.separator");
//報名
String packageName = ainfo.packageName;
//目標全路徑
String dexPath = ainfo.applicationInfo.sourceDir;
//本地解析輸出路徑
String dexOutputDir = getApplicationInfo().dataDir;
//目標lib(C/C++)文件存放路徑
String libPath = ainfo.applicationInfo.nativeLibraryDir;
//創建我們自己的ClassLoader
DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDir,libPath,this.getClass().getClassLoader());
try {
//通過反射機制去調用方法
Class> clazz = cl.loadClass(packageName+".PluginClass");
Object obj = clazz.newInstance();
Class[] params = new Class[2];
params[0] = Integer.TYPE;
params[1] = Integer.TYPE;
Method action = clazz.getMethod("functionl",params);
Integer ret = (Integer)action.invoke(obj,12,34);
Log.i("Host","returnvalueis"+ret);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}catch(Exception e){
Log.e("Host", "Something wrong has happened!");
}
}
上面的注釋應該挺明確的,這里主要對DexClassLoader的初始化做下說明:
dexPath,指目標類所在的APK或Jar文件的路徑。類裝載器將從該路徑中尋找指定的目標類,該路徑必須是APk或Jar的全路徑,比如/data/app/com.haiii.android.plugin.apk。如果要包含多個路徑,路徑之間必須用特定分隔符進行分割,即上面的System.getProperty(“path.sepator”)。
dexOutputDir,由于dex文件被包含在APk或者Jar中,因此在裝載目標類之前需要從APk或jar中解壓出dex文件,該參數就是指定解壓出的dex文件存放路徑。在Android系統中,一個應用程序一般對應一個Linux用戶id,應用程序僅對屬于自己的數據目錄路徑有寫的權限,因此,該參數可以使用該程序的數據路徑。
libPath,指目標類中所使用的C/C++庫存放路徑。
parent,指該裝載器的父裝載器,一般為當前執行類的裝載器。
通過類裝載器裝載class之后會返回一個class對象,但這個class對象,我們本地是沒有對其定義的,所以我們無法使用它的方法,而且此時也沒有生成我們需要的PluginClass對象,只是裝載了PluginClass的代碼而已。所以這里就需要用到反射去創建對象并調用相應的方法。
2.2 基于類裝載器設計的”插件“架構
目前市場上也有不少關于插件化的東西,這里將簡單介紹下關于使用類裝載器實現插件化的思想與方法。
上面的關于DexClassLoader的介紹,我們發現,整個調用都還算簡單,但到了最后使用反射這一塊太過于繁瑣,既然我們知道我們要調用的類和方法,那么為什么不考慮下,使用接口機制呢??相信看這篇文章的人都有一定的Android基礎,那么應該都了解service遠程aidl機制里,有一個asInterface的方式,我們可以模仿下。
首先定義一個接口,這個接口僅僅定義函數的輸入和輸出,不定義具體實現。這個接口要同時參與Host與Plugin兩個項目(APK)的編譯。
//接口定義如下
public interface Comm{
public int functionl( int a, int b ) ;
}
然后修改下PluginClass的代碼
public class PluginClass implements Comm{
public PluginClass(){
Log.i( "Plugin" , "PluginClass client initialized" ) ;
}
public int functionl( int a, int b ) {
return a + b;
}
}
然后在Host中,我們對于Class對象newInstance()之后返回的對象就可以強制轉換為Comm接口對象了,修改下代碼:
try{
Class> clazz = cl.loadClass(packageName + ".PluginClass");
Comm comm = (Comm)clazz.newInstance();
Integer ret = comm.functionl(12,34);
Log.i("Host","return value is " + ret);
}
這段代碼執行結果與上一段代碼的結果是相同的。
我們來看下,當前市場中關于插件的描述:
1.一種邏輯概念,不是技術標準;
2.插件不能獨立運行,必須運行與一個宿主程序中,由宿主程序去調用插件程序。
3.宿主程序中可以管理不同的插件,包括數量,禁用或使用,主題
設置等。多個插件,應該能做到切換插件。
4.宿主程序需要保證向下兼容,新版本應該能運行老版本的插件。
注意點:
1.接口一般定義在Host中,如本例的Comm.java。
2.Plugin項目中需要使用Comm是,必須通過一個外部的jar包,這個jar包必須是以Library方式添加到Plugin的build
path中,不能以外部jar包的方式添加,因為我們只需要在Host中存在我們定義的接口,而不想在Plugin中也編譯進去,如果都編譯進去,就會產生包名相同但驗證碼不同的文件,導致”Class
ref in pre-verified class resolved to unexpected implementation”.
總結
以上是生活随笔為你收集整理的java类加载器 架构 设计_类加载器(DexClassLoader)与插件化(动态加载)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 零宽断言java_正则表达之零宽断言(零
- 下一篇: 基于java的社交网站毕业设计_软件工程