原理:使用 DexClassLoader 動態載入 apk 中 dex file,並產生 classes 然後執行其 method
※ 此方法必須先知道 apk 中 dex file 有哪些 classes 與 method
※ 此方法必須先知道 apk 中 dex file 有哪些 classes 與 method
Code 解說
目的:將編譯好的 classB 之 apk 放到 SD 卡目錄下,在使用 classA 動態啟動(載入) classB
class A
class A
String path = Environment.getExternalStorageDirectory() + File.separator; //File.separator 表示"/"符號,系統自動產生對應的 "\" 或 "/"
String filename = "TestClassLoadBDex.apk";
String optimizedDirectory = path + File.separator + "dex_temp" ;
//為解決 .dex文件在 4.1 之後不能存在 SD 卡下,否則會出現 IllegalArgumentException 問題,這裡透過 getDir 來取得 file
File file = getDir("dex", 0) ;
DexClassLoader cl = new DexClassLoader(path + filename, file.getAbsolutePath(),null, getClassLoader());
Class libProviderClazz = null;
try {
libProviderClazz = cl.loadClass("com.example.testclassloadb.MainActivity"); //class 完整名稱
Method onCreateMethod = libProviderClazz.getDeclaredMethod("onCreate", Bundle.class); //呼叫 class 中 method
onCreateMethod.setAccessible(true); //設定 method 是否可訪問
onCreateMethod.invoke(this, savedInstanceState); //第一個参数為,一般 method 傳入 context,靜態 method 傳入 null
Method changeTitle = libProviderClazz.getDeclaredMethod("changeTitle", String.class); //呼叫 class 中 method
changeTitle.setAccessible(true); //設定 method 是否可訪問
changeTitle.invoke(this, "class A call change title"); //第一個参数為,一般 method 傳入 context,靜態 method 傳入 null
} catch (Exception exception) {
exception.printStackTrace();
}
class B (此部分代碼為錯誤示範)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_title = (TextView) findViewById(R.id.tv_title);
btn_showToast = (Button) findViewById(R.id.btn_showToast);
Log.i("ruby", "path="+this.getFilesDir());
btn_showToast.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "in ClassLoadB", Toast.LENGTH_SHORT).show();
}
});
}
private void changeTitle(String str){
tv_title.setText(str);
}
執行步驟:
- 將 classB 編譯成 apk 檔案
- 將 apk 放置手機 SD 卡跟目錄下 (這裡示範位置在根目錄,要自訂的請自行修改路徑)
- 執行 classA
執行結果:
輸入完以上代碼後執行,發現雖然成功執行了,但是 classA 並沒有載入 classB 的 apk,也沒有執行預定的 method (onCreate 與 changeTitle)
其實不安装apk,直接動態加載 dex 中的類別,是屬於透過反射的方式手動的創建 Activity
這種方式跟系统創建的 Activity 是不一樣的,没有系统創建之 Activity 的特性,而且 R 也找不到(無法透過 layout 來畫 UI),其實他並不是啟動 Apk 中的 Activity,而是在 classA Activity 上劃出 Apk 中 Activity 的 View
其實不安装apk,直接動態加載 dex 中的類別,是屬於透過反射的方式手動的創建 Activity
這種方式跟系统創建的 Activity 是不一樣的,没有系统創建之 Activity 的特性,而且 R 也找不到(無法透過 layout 來畫 UI),其實他並不是啟動 Apk 中的 Activity,而是在 classA Activity 上劃出 Apk 中 Activity 的 View
解決辦法:
由上述內容可知,其實只要手動產生一個 activity 並傳入 classB 即可正常執行 classB 及其 method
首先,增加一個 setActivity 的 method
class B
class B
public void setActivity(Activity paramActivity) {
Log.i("ruby", "setActivity..." + paramActivity);
this.activity = paramActivity;
}
接著,在 classB OnCreate method 中修改 view 的呈現
※ classB 載入的 view 不可使用 resources 只能動態產生,因為載入 classB 時無法做 resources 連結
※ classB 載入的 view 不可使用 resources 只能動態產生,因為載入 classB 時無法做 resources 連結
classB
boolean b = false;
TextView t = new TextView(this.activity);
t.setText("class B");
if (savedInstanceState != null) {
b = savedInstanceState.getBoolean("KEY_START_FROM_OTHER_ACTIVITY", false);
if (b) {
this.activity.setContentView(t);
}
}
if (!b) {
super.onCreate(savedInstanceState);
setContentView(t);
}
最後,在 classA 呼叫 classB 的 onCreate method 時,先呼叫 setActivity method
classA
Constructor localConstructor = libProviderClazz.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
Method localMethodSetActivity = libProviderClazz.getDeclaredMethod(
"setActivity", new Class[] { Activity.class });
localMethodSetActivity.setAccessible(true);
localMethodSetActivity.invoke(instance, new Object[] { this });
應用範圍:
app 整合平台,例如:game center classB 就像是遊戲大廳,classA 就像是裡面的各個小遊戲,讓不同公司開發出的小遊戲可以直接存取底層的遊戲大廳(classB)
getDeclaredMethod 與 getMethod 差異
- getDeclaredMethod:取得本身 class 中的所有方法,包含 public、protected 和 private 方法
- getMethod:取得 class 中的所有共有方法,包括自己的 public 方法、繼承來的方法、實作介面的 method
沒有留言:
張貼留言