Android動態加載Dex過程

 一、綜述

       Android使用Dalvik虛擬機加載可執行程序,所以不能直接加載基於class的jar,而是需要將class轉化爲dex字節碼,從而執行代碼。優化後的字節碼文件可以存在一個*.jar中,只要其內部存放的是*.dex即可使用。

       將class的jar包轉化爲dex需要用到命令dx(在*\android-sdk\build-tools\version[23.0.1] 或 *\android-sdk\platform-tools下能找到);命令使用方式爲:dx --dex --output=output.jar origin.jar,該命令將包含class的origin.jar轉化爲包含dex的output.jar文件。

        Android支持動態加載的兩種方式是:DexClassLoader和PathClassLoader,DexClassLoader可加載jar/apk/dex,且支持從SD卡加載;PathClassLoader據說只能加載已經安裝在Android系統內APK文件

 PathClassLoader 的限制要更多一些,它只能加載已經安裝到 Android 系統中的 apk 文件,也就是 /data/app 目錄下的 apk 文件。其它位置的文件加載的時候都會出現 ClassNotFoundException. 例如:

PathClassLoader cl = new PathClassLoader(jarFile.toString(), "/data/app/", ClassLoader.getSystemClassLoader());  


二、實驗步驟

假如你已經有一個工程,需要把一些功能代碼什麼用於動態加載於其他應用上,則可以新建一個module,或者你可以選擇  新建一個Android工程,並進行如下操作:

            2.1 定義一個接口IShowToast.java

/**
 * 動態加載測試接口
 */
public interface IShowToast {
    int showToast(Context context);
}

   2.2 再定義一個簡單實現ShowToastImpl.java

/**
 * 動態加載測試實現
 */
public class ShowToastImpl implements IShowToast {
    @Override
    public int showToast(Context context) {
        Toast.makeText(context, "我來自另一個dex文件", Toast.LENGTH_LONG).show();
        return 100;
    }
}

很簡單輸出一句話,"我來自另一個dex文件"

整體工程目錄如下:



假如是新建的module,則點擊Build--make module,不然點擊make Project。


這時會在對應module或者project的build--intermediates--生成classes文件夾,內部結構如下:


好了我們要把IShowToastImpl這個class轉換成Dalvik可識別的dex文件,分兩步:
1.先導出IShowToastImpl這個類爲jar包的形式;
2.通過android sdk自帶的dx.jar工具轉換jar包爲dex文件。
完成第一步,當時遇到點麻煩,由於eclipse是基於ant並且有可視化工具,可以直接導出指定文件的jar包,但是android studio不行,那怎麼辦呢?
程序運行的效果圖如下:們可以通過gradle task來打包,打開app目錄下的build.gradle文件,切記不是根目錄的build.gradle文件,加上以下代碼:

(注:假如是新建module,則在該module下的build.gradle下加入代碼)

//刪除isshowtoast.jar包任務
task clearJar(type: Delete) {
    delete 'libs/ishowtoast.jar'
}
task makeJar(type:org.gradle.api.tasks.bundling.Jar){
    //指定生成的jar名
    baseName 'ishowtoast'
    //從哪裏打包class文件
    from('build/intermediates/classes/debug/com/example/dexlibs/')
    //打包到jar後的目錄結構
    into('com/example/dexlibs/')
    //去掉不需要打包的目錄和文件
    exclude('test/','IShowToast.class','BuildConfig.class','R.class')
    //去掉R$開頭的文件
    exclude{it.name.startsWith('R$')}
}
makeJar.dependsOn(clearJar,build)


打開AS的 terminal窗口: cd app進入app目錄,執行gradlew makeJar,然後等待直到出現Build Successfully,這時會在build目錄下出現libs/ishowtoast.jar文件,這個文件就是我們要用的jar包。

第二步,使用sdk提供的dx.jar將導出的ishowtoast.jar轉換成Dalvik可識別的dex格式,新版的sdk已經將dx.jar放到build-tools\23.0.2\lib目錄下,我們在dos下或者在Android studio terminal下面進入到此目錄,然後運行下面的命令:
(備註;請把ishowtoast.jar複製到C:\Android\android-sdk-windows\build-tools\23.0.2目錄下,不然會提示找不到該文件)

output是你的輸出目錄,默認就是在當前的根目錄下,執行完成後我們就在當前目錄下生成了Davilk虛擬機可執行的dex文件,因爲這條命令同時會打包dex文件,因此後綴是jar,如圖所示,生成了ishowtoast_dex的jar文件,右鍵使用WinRaR即可看到有class.dex文件



接着我們再新建一個項目,準備動態加載剛纔的ishowtoast_dex的jar文件。


務必要注意,需要複製過來的接口IShowToast所在的包名需要跟前一個app一樣,不然會報錯

fileUtils是一個工具類,用於讀取sssets中我們複製過來的那個ishowtoast_dex的jar文件,上圖所示,下圖則是具體fileUtils的代碼

/**
 * 將assets文件copy到app/data/cache目錄
 */
public class FileUtils {
    public static void copyFiles(Context context, String fileName, File desFile){
        InputStream in=null;
        OutputStream out=null;

        try {
            in=context.getApplicationContext().getAssets().open(fileName);
            out=new FileOutputStream(desFile.getAbsolutePath());
            byte[] bytes=new byte[1024];
            int len=0;
            while ((len=in.read(bytes))!=-1)
                out.write(bytes,0,len);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (in!=null)
                    in.close();
                if (out!=null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

打開MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //  //添加一個點擊事件
        findViewById(R.id.tv_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadDexClass();
            }
        });
    }
    /**
     * 加載dex文件中的class,並調用其中的showToast方法
     */
    private void loadDexClass() {
        File cacheFile = getDir("dex",0);
        String internalPath = cacheFile.getAbsolutePath() + File.separator + "ishowtoast_dex.jar";
        File desFile=new File(internalPath);
        try {
            if (!desFile.exists()) {
                desFile.createNewFile();
                FileUtils.copyFiles(this,"ishowtoast_dex.jar",desFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //下面開始加載dex class
        //1.待加載的dex文件路徑,如果是外存路徑,一定要加上讀外存文件的權限,
        //2.解壓後的dex存放位置,此位置一定要是可讀寫且僅該應用可讀寫
        //3.指向包含本地庫(so)的文件夾路徑,可以設爲null
        //4.父級類加載器,一般可以通過Context.getClassLoader獲取到,也可以通過ClassLoader.getSystemClassLoader()取到。
        DexClassLoader dexClassLoader=new DexClassLoader(internalPath,cacheFile.getAbsolutePath(),null,getClassLoader());
        try {
            //該name就是internalPath路徑下的dex文件裏面的ShowToastImpl這個類的包名+類名
            Class<?> clz = dexClassLoader.loadClass("com.example.dexlibs.ShowToastImpl");
            IShowToast impl= (IShowToast) clz.newInstance();//通過該方法得到IShowToast類
            if (impl!=null)
                impl.showToast(this);//調用打開彈窗
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

程序運行的效果圖如下:



動圖不好搞,mp4轉gif還要下格式工廠,所以就用圖片代替了

至此,我們關於Android Dex動態加載機制的過程就結束了。

Demo源碼地址:https://download.csdn.net/download/a2923790861/10452976
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章