本文述敘 DexClassLoader 之使用方法 ,概略內容如下 :
範例程式將在執行時下載 dex 檔案並於載入後呼叫 dex 提供的函數來開啟新的 Activity,此 Activity 將會利用 Toast 顯示一段啟動成功的訊息。
1. 製作 dex 檔案 :
將底下程式碼另存成檔案 PayloadActivity.java :
package com.example;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
public class PayloadActivity extends Activity {
public static void starter(Activity parentActivity){
Intent tmp_intent =new Intent(parentActivity, PayloadActivity.class);
parentActivity.startActivity(tmp_intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(this, "Instantiate PayloadAcitivty Successful", Toast.LENGTH_LONG).show();
}
}
PayloadActivity class 提供靜態函數 starter,此函數被呼叫之後會啟動 Payload Activity。把 PayloadActivity.java 編譯成 dex 檔案 (Mac OSX 環境) :
首先將 java 檔案編譯成 class 檔案 (其中 path_to_sdk 指向 android sdk 之路徑):
javac ./PayloadActivity.java -cp /path_to_sdk/adt-bundle-mac-x86_64-20130917/sdk/platforms/android-10/android.jar -d .將 class 檔案壓縮成 PayloadActivity.jar 檔案 :
jar cvfM ./PayloadActivity.jar com/刪除執行編譯指令時產生的 com 資料夾 :
rm -rf ./com將 jar 檔案轉成 dex 檔案 :
/path_to_sdk/adt-bundle-mac-x86_64-20130917/sdk/build-tools/android-4.3/dx --dex --output=PayloadActivity.dex.jar ./PayloadActivity.jar
將完成的 PayloadActivity.dex.jar 擺在網路空間。 ( 此範例假設可利用 url http://localhost/PayloadActivity.dex.jar 取得檔案 )
2. 程式執行時期下載 dex 檔案 :
建立一個 com.example.dexclassloader 之 Android Application Project
主要啟動的 Activity 設定為 MainActivity
在 Eclipse 裡新創 Android Application Project,將主要 Activity ( MainActivity.java ) 替換如下 :
package com.example.dexclassloader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import dalvik.system.DexClassLoader;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Menu;
public class MainActivity extends Activity {
public static ClassLoader payloadClassLoader =null;
private Smith<ClassLoader> sClassLoader =null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Thread works =new Thread(){
@Override
public void run(){
//download and save the jar
downloadDexFile("http://localhost/PayloadActivity.dex.jar", "PayloadActivity.dex.jar");
//override class loader
overrideClassLoader();
//load the jar
loadAndInvokeMethod();
}
};
works.start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void downloadDexFile(String url, String filename){
try {
URL u = new URL(url);
HttpURLConnection c = (HttpURLConnection) u.openConnection();
c.setRequestMethod("GET");
c.setDoOutput(true);
c.connect();
FileOutputStream f =openFileOutput(filename, MODE_PRIVATE);
InputStream in = c.getInputStream();
byte[] buffer = new byte[1024];
int len1 = 0;
while ((len1 = in.read(buffer)) > 0) {
f.write(buffer, 0, len1);
}
f.close();
} catch (Exception e) {
//error dialog
Log.d("mainactivity", "error -"+e.toString());
}
}
private void overrideClassLoader(){
try{
Context mBase =new Smith<Context>(this, "mBase").get();
Object mPackageInfo =new Smith<Object>(mBase, "mPackageInfo").get();
sClassLoader =new Smith<ClassLoader>(mPackageInfo, "mClassLoader");
ClassLoader mClassLoader =sClassLoader.get();
MyClassLoader cl =new MyClassLoader(mClassLoader);
sClassLoader.set(cl);
}catch(Exception e){
}
}
private void loadAndInvokeMethod(){
try{
String libPath =getFilesDir()+"/PayloadActivity.dex.jar";
File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
DexClassLoader dcl=new DexClassLoader(libPath, optimizedDexOutputPath.getAbsolutePath(), null, getClass().getClassLoader());
payloadClassLoader =dcl;
//LOAD CLASS
Class<Object> payloadClass =(Class<Object>)dcl.loadClass("com.example.PayloadActivity");
//LOAD METHOD
Method starterFunc =payloadClass.getMethod("starter", Activity.class);
//INVOKE METHOD
starterFunc.invoke(null, this);
}catch(Exception e){
}
}
}
觀察 MainActivity 的 onCreate 函數可知程式啟動時將從 url ( http://localhost/Payload.dex.jar ) 下載檔案 (downloadDexFile 函數),並將檔案暫存在裝置記憶體裡。
完成後使用 DexClassLoader 載入 dex 檔案並執行檔案裡提供的 starter 函數 (loadAndInvokeMethod 函數)。
下一節解說函數 overrideClassLoader 的功能。
3. 從 dex 檔案啟動 Activity (修改 ClassLoader) :
藉由 DexClassLoader 載入的程式在啟動 Activity 時 (Activity Class) 不會搜尋 DexClassLoader 所載入的 dex 檔案,因此會出現找不到 Class 的 Exception。為解決這個問題,透過修改 ClassLoader ,我們可以讓程式先搜尋 dex 檔案,若找不到目標則再搜尋預設的 jar 檔案。
底下是覆寫 loadClass 函數部分的程式碼 :
package com.example.dexclassloader;
public class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent){
super(parent);
}
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException{
if (MainActivity.payloadClassLoader !=null){
try{
Class<?> c = MainActivity.payloadClassLoader.loadClass(className);
if (c != null){
return c;
}
} catch (ClassNotFoundException e) {
}
}
return super.loadClass(className);
}
}
此外 overrideClassLoader 函數中的 Smith Class 可以幫助我們取得 ClassLoader,其程式碼可以參考這裡。
4. AndroidManifest.xml
雖然可以透過 dex 檔案更新並且啟動 activity,但注意的是此 activity 在安裝 apk 之前,必須先定義在 apk 的 AndroidManifest.xml 檔案裡,AndroidManifest.xml 檔案內容如下 :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.dexclassloader"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.dexclassloader.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.example.PayloadActivity" android:label="@string/app_name" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
</manifest
ref:http://stackoverflow.com/questions/6857807/is-it-possible-to-dynamically-load-a-library-at-runtime-from-an-android-applicat
ref:http://stackoverflow.com/questions/2519760/android-how-to-use-dexclassloader-to-dynamically-replace-an-activity-or-service


0 意見 :
張貼留言