一、前言
在 外掛化知識梳理(6) - Small 原始碼分析之 Hook 原理 這一章的學習完成之後,下一步我們將進入外掛化載入的精髓,動態載入類的學習,在此之前,我們需要先準備一些關於類載入的知識。
Android
當中,支援動態載入的兩種方式為:DexClassLoader
和PathClassLoader
。這兩者之間的區別為:
DexClassLoader
- 可以載入
jar、apk、dex
- 支援從
SD
卡目錄載入。 PathClassLoader
- 在許多文章中都有提到,在
Dalvik
虛擬機器上,只能載入已經安裝到系統中的Apk
檔案,也就是/data/app
目錄下的apk
檔案。之所以有這個限制是因為PathClassLoader
會去讀取data/dalvik-cache
目錄下經過優化後的dex
檔案,如果檔案不存在,那麼就會報錯。由於手邊沒有機器,所以沒有版本驗證。 - 而在
ART
虛擬機器上,通過原始碼當中的註釋,可以發現是支援的。
二、具體例項
例項的工程目錄結構為:
app
:宿主模組library
:外掛模組libraryinterface
:外掛介面模組
其中,app
和library
模組分別依賴於libraryinterface
,library
和libraryinterface
為Android Library
型別的Module
,下面,我們開始講解整個工程的構建過程。
2.1 介面模組 libraryinterface
介面模組相當於是宿主模組和外掛模組所定義的一套標準,宿主模組遵循固定的業務邏輯,而具體的實現則根據外掛模組的不同而不同。
在介面模組中,我們定義一個簡單的介面IPlugin.java
:
public interface IPlugin {
public int getVersion();
}
複製程式碼
2.2 外掛模組 library
首先,我們在外掛模組的build.gradle
檔案中,引入libraryinterface
模組
dependencies {
//引入介面模組
compile project (':libraryinterface')
}
複製程式碼
接著,我們編寫一個實現類:
public class PluginImpl implements IPlugin {
@Override
public int getVersion() {
return 1;
}
}
複製程式碼
接下來需要做的就是將該外掛模組打包成一個jar
檔案,同樣是在build.gradle
檔案中,建立一個Task
任務:
task makeJar(type: Copy) {
delete 'build/libs/plugin.jar'
from ('build/intermediates/bundles/release/')
into ('../file/')
include ('classes.jar')
rename ('classes.jar','plugin.jar')
}
makeJar.dependsOn(build)
複製程式碼
首先點選make module
:
./gradlew makeJar
複製程式碼
就會得到一個plugin.jar
檔案,但是這個jar
檔案是不能夠被動態載入的,因為它內部其實是.class
檔案,我們通過解壓可以看出:
Android SDK
自帶的dx
工具進行轉換,把它轉換為.dex
,轉換後的檔案為plugin_dex.jar
/Users/lizejun/Library/Android/sdk/build-tools/25.0.3/dx --dex --output=file/plugin_dex.jar file/plugin.jar
複製程式碼
將plugin_dex.jar
解壓之後,可以看到它已經被轉換成了.dex
檔案:
jar
包push
到手機中的/sdcard/Plugin
目錄下:
2.3 宿主模組 app
首先,宿主模組同樣需要依賴於介面模組libraryinterface
:
dependencies {
//引入介面模組
compile project (':libraryinterface')
}
複製程式碼
在程式碼當中,我們通過DexClassLoader/PathClassLoader
動態外部的外掛plugin_dex.jar
,通過反射例項化PluginImpl
類,並呼叫它的getVersion()
方法進行驗證:
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.tv_plug_result);
getPluginA();
}
private void getPluginA() {
File dexOutputDir = getDir("dex1", 0);
String dexPath = Environment.getExternalStorageDirectory().toString() + "/Plugin/plugin_dex.jar";
DexClassLoader loader = new DexClassLoader(dexPath, dexOutputDir.getAbsolutePath(), null, getClassLoader());
try {
Class clz = loader.loadClass("com.demo.lizejun.library.PluginImpl");
IPlugin impl = (IPlugin) clz.newInstance();
int version = impl.getVersion();
mTextView.setText("Version=" + version);
} catch (Exception e) {
e.printStackTrace();
}
}
private void getPluginB() {
String dexPath = Environment.getExternalStorageDirectory().toString() + "/Plugin/plugin_dex.jar";
PathClassLoader loader = new PathClassLoader(dexPath, getClassLoader());
try {
Class clz = loader.loadClass("com.demo.lizejun.library.PluginImpl");
IPlugin impl = (IPlugin) clz.newInstance();
int version = impl.getVersion();
mTextView.setText("Version=" + version);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製程式碼
最終的結果為:
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/