【Android高階】DexClassloader和PathClassloader動態載入外掛的實現
阿新 • • 發佈:2019-01-31
(一)DexClassloader
一、基本概念:
在Android中可以跟java一樣實現動態載入jar,但是Android使用德海Dalvik VM,不能直接載入java打包jar的byte code,需要通過dx工具來優化Dalvik byte code。
Android在API中給出可動態載入的有:DexClassLoader 和 PathClassLoader。
DexClassLoader:可載入jar、apk和dex,可以從SD卡中載入(本文使用這種方式)
PathClassLoader:只能載入已經安裝搭配Android系統中的apk檔案
二、實施
編寫介面:Dynamic
package com.smilegames.dynamic.interfaces;
public interface Dynamic {
public String helloWorld();
public String smileGames();
public String fyt();
}
編寫實現類:DynamicTest三、打包並編譯成dexpackage com.smilegames.dynamic.impl; import com.smilegames.dynamic.interfaces.Dynamic; public class DynamicTest implements Dynamic { @Override public String helloWorld() { return "Hello Word!"; } @Override public String smileGames() { return "Smile Games"; } @Override public String fyt() { return "fengyoutian"; } }
將介面打包成jar:dynamic.jar(只打包這Dynamic.java這一個介面)
將實現類打包成jar:dynamic_test.jar(只打包DynamicTest.java這一個實現類)
將打包後的實現類(dynamic_test.jar)編譯成dex:dynamic_impl.jar
1、將dynamic_test.jar拷貝到SDK安裝目錄android-sdk-windows\platform-tools下(ps:如果platform-tools沒有dx.bat,可拷貝到build-tools目錄下有dx.bat的子目錄
2、執行以下命令:
dx --dex --output=dynamic_impl.jar dynamic_test.jar3、將dynamic.jar引入測試例項
4、將dynamic_impl.jar放到模擬器或真機的sdcard
四、修改onCreate例子
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 或許activity按鈕
helloWorld = (Button) findViewById(R.id.helloWorld);
smileGames = (Button) findViewById(R.id.smileGames);
fyt = (Button) findViewById(R.id.fyt);
/*使用DexCkassLoader方式載入類*/
// dex壓縮檔案的路徑(可以是apk,jar,zip格式)
String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "dynamic_impl.jar";
// dex解壓釋放後的目錄
String dexOutputDirs = Environment.getExternalStorageDirectory().toString();
// 定義DexClassLoader
// 第一個引數:是dex壓縮檔案的路徑
// 第二個引數:是dex解壓縮後存放的目錄
// 第三個引數:是C/C++依賴的本地庫檔案目錄,可以為null
// 第四個引數:是上一級的類載入器
DexClassLoader dexClassLoader = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());
Class libProvierClazz = null;
// 使用DexClassLoader載入類
try {
libProvierClazz = dexClassLoader.loadClass("com.smilegames.dynamic.impl.DynamicTest");
// 建立dynamic例項
dynamic = (Dynamic) libProvierClazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
helloWorld.setOnClickListener(new HelloWorldOnClickListener());
smileGames.setOnClickListener(new SmileGamesOnClickListener());
fyt.setOnClickListener(new FytOnClickListener());
}
private final class HelloWorldOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
if (null != dynamic) {
Toast.makeText(getApplicationContext(), dynamic.helloWorld(), 1500).show();
} else {
Toast.makeText(getApplicationContext(), "類載入失敗", 1500).show();
}
}
}
private final class SmileGamesOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
if (null != dynamic) {
Toast.makeText(getApplicationContext(), dynamic.smileGames(), 1500).show();
} else {
Toast.makeText(getApplicationContext(), "類載入失敗", 1500).show();
}
}
}
1、執行這段程式碼時4.0.1以上版本會報:java.lang.IllegalArgumentException:
optimizedDirectory not readable/writable: /storage/sdcard0可以通過這個授權解決:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
2、授權之後又會報:java.lang.IllegalArgumentException:
Optimized data directory /storage/sdcard0 is not owned by the current user. Shared storage cannot protect your application from code injection attacks.這個問題的原因是:在4.1系統由於This class loader requires an application-private, writable directory to cache optimized classes為了防止一下問題:
External storage does not provide access controls necessary to protect your application from code injection attacks.
所以加了一個判斷Libcore.os.getuid() != Libcore.os.stat(parent).st_uid判斷兩個程式是不是同一個uid
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
解決方法是:指定dexoutputpath為APP自己的快取目錄File dexOutputDir = context.getDir("dex", 0);
DexClassLoader dexClassLoader = new DexClassLoader(dexPath,dexOutputDir.getAbsolutePath(),null,getClassLoader());
ps:第四步的問題最終是2導致的,所以只需用2的解決方案即可,不需要1的授權。執行結果自行嘗試。。。
(二)PathClassloader
public void dexLoad() {
/** 使用DexClassLoader方式載入類 */
// dex壓縮檔案的路徑(可以是apk,jar,zip格式)
String dexPath = Environment.getExternalStorageDirectory().toString()
+ File.separator + "dynamic_temp.jar";
// dex解壓釋放後的目錄
// String dexOutputDir = getApplicationInfo().dataDir;
// String dexOutputDirs = Environment.getExternalStorageDirectory()
// .toString();
// 定義DexClassLoader
// 第一個引數:是dex壓縮檔案的路徑
// 第二個引數:是dex解壓縮後存放的目錄
// 第三個引數:是C/C++依賴的本地庫檔案目錄,可以為null
// 第四個引數:是上一級的類載入器
File dexOutputDir = getApplicationContext().getDir("dex", 0);
DexClassLoader cl = new DexClassLoader(dexPath,
dexOutputDir.getAbsolutePath(), null, getClassLoader());
Log.i("tag", dexOutputDir.getAbsolutePath());
Class libProviderClazz;
try {
libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
// dynamic = (IDynamic) libProviderClazz.newInstance();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// MyInterface dynamic;
Class dynamic;
public void pathLoad() {
/** 使用PathClassLoader方法載入類 */
// 建立一個意圖,用來找到指定的apk:這裡的"com.dynamic.impl是指定apk中在AndroidMainfest.xml檔案中定義的<action name="com.dynamic.impl"/>
Intent intent = new Intent(
"com.example.androidendyeartest.MainActivity", null);
// 獲得包管理器
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
// 獲得指定的activity的資訊
ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
// 獲得apk的目錄或者jar的目錄
String apkPath = actInfo.applicationInfo.sourceDir;
// native程式碼的目錄
String libPath = actInfo.applicationInfo.nativeLibraryDir;
// 建立類載入器,把dex載入到虛擬機器中
// 第一個引數:是指定apk安裝的路徑,這個路徑要注意只能是通過actInfo.applicationInfo.sourceDir來獲取
// 第二個引數:是C/C++依賴的本地庫檔案目錄,可以為null
// 第三個引數:是上一級的類載入器
PathClassLoader pcl = new PathClassLoader(apkPath, libPath,
this.getClassLoader());
// 載入類
try {
dynamic = pcl.loadClass("com.test.bean.MyBean");
} catch (Exception exception) {
exception.printStackTrace();
}
}
應用:
@OnClick(R.id.b1)
public void t1(View v) {
if (dynamic != null) {
// dynamic.show1(getApplicationContext());
Method method;
try {
method = dynamic.getMethod("show1",Context.class);
//Context context=getApplicationContext();
method.invoke(dynamic.newInstance(), getApplicationContext());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
Toast.makeText(getApplicationContext(), "類載入失敗", 1500).show();
}
}