Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android動態加載Dex機制解析

Android動態加載Dex機制解析

編輯:關於Android編程

1.什麼是類加載器?

類加載器(class loader)是 Java?中的一個很重要的概念。類加載器負責加載 Java 類的字節代碼到 Java 虛擬機中。
Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創建出該類的一個對象。實際的情況可能更加復雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。
 

2.Dalvik虛擬機類加載機制

Dalvik虛擬機如同其他Java虛擬機一樣,在運行程序時首先需要將對應的類加載到內存中。而在Java標准的虛擬機中,類加載可以從class文件中讀取,也可以是其他形式的二進制流,因此,我們常常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的,但是Dalvik虛擬機畢竟不算是標准的Java虛擬機,因此在類加載機制上,它們有相同的地方,也有不同之處。   我們先看下下面這張關於Android Classload機制的圖。   \  

與JVM不同,Dalvik的虛擬機不能用ClassCload直接加載.dex,Android從ClassLoader派生出了兩個類:DexClassLoader和PathClassLoader;而這兩個類就是我們加載dex文件的關鍵,這兩者的區別是:

1.DexClassLoader:可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk;
2.PathClassLoader:要傳入系統中apk的存放Path,所以只能加載已經安裝的apk文件。

准備工作開始

一、打開Android studio 新建工程: \     工程目錄是這樣的: \   動態加載進來的class如何使用,一般有2種辦法,一種是使用反射調用,這種我不多做介紹;還有一種是使用接口編程的方式來調用對應的方法,畢竟.dex文件也是我們自己維護的,所以可以把方法抽象成公共接口,把這些接口也復制到主項目裡面去,就可以通過這些接口調用動態加載得到的實例的方法了。 接下來我們源碼包下面新建一個包名稱是dynamic,然後在dynamic下新建一個interface接口Dynamic,裡面有個接口方法,就叫sayHello()吧,返回一個String,到時候我們可以通過Toast彈出來,Dynamic.java:
package wangyang.zun.com.mydexdemo.dynamic;

/**
 * Created by WangYang on 2016/3/11.
 */
public interface Dynamic {
    String sayHello();
}
接著我們新建一個impl包,並實現Dynamic接口,DynamicImpl.java:
package wangyang.zun.com.mydexdemo.dynamic.impl;

import wangyang.zun.com.mydexdemo.dynamic.Dynamic;

/**
 * Created by WangYang on 2016/3/11.
 */
public class DynamicImpl implements Dynamic {

    @Override
    public String sayHello() {
        return new StringBuilder(getClass().getName()).append(" is loaded by DexClassLoader").toString();
    }
}
很簡單輸出一句話,"DynamicImpl is loaded by DexClassLoader." 具體的工程目錄如下圖: \   點擊Build -> make project,這時候會在build\intermediates\classes\debug目錄下生成對應的classes文件。   \   好了我們要把DynamicImpl這個class轉換成Dalvik可識別的dex文件,分兩步: 1.先導出DynamicImpl這個類為jar包的形式; 2.通過android sdk自帶的dx.jar工具轉換jar包為dex文件。 完成第一步,當時遇到點麻煩,由於eclipse是基於ant並且有可視化工具,可以直接導出指定文件的jar包,但是android studio不行,那怎麼辦呢? 我們可以通過gradle task來打包,打開app目錄下的build.gradle文件,切記不是根目錄的build.gradle文件,加上以下代碼:
//刪除dynamic.jar包任務
task clearJar(type: Delete) {
    delete 'libs/dynamic.jar'
}

//打包任務
task makeJar(type:org.gradle.api.tasks.bundling.Jar) {
    //指定生成的jar名
    baseName 'dynamic'
    //從哪裡打包class文件
    from('build/intermediates/classes/debug/wangyang/zun/com/mydexdemo/dynamic/')
    //打包到jar後的目錄結構
    into('wangyang/zun/com/mydexdemo/dynamic/')
    //去掉不需要打包的目錄和文件
    exclude('test/', 'Dynamic.class', 'BuildConfig.class', 'R.class')
    //去掉R$開頭的文件
    exclude{ it.name.startsWith('R$');}
}
makeJar.dependsOn(clearJar, build)

打開AS的 terminal窗口: cd app進入app目錄,執行gradle makeJar,然後等待直到出現Build Successfully,這時會在build目錄下出現libs/dynamic.jar文件,這個文件就是我們要用的jar包,我們可以使用jd-gui打開看下是不是只有DynamicImpl這個class; 第二步,使用sdk提供的dx.jar將導出的dynamic.jar轉換成Dalvik可識別的dex格式,新版的sdk已經將dx.jar放到build-tools\23.0.2\lib目錄下,我們在dos下或者在Android studio terminal下面進入到此目錄,然後運行下面的命令: dx--dex--output=dynamic_dex.jar dynamic.jar \   output是你的輸出目錄,默認就是在當前的根目錄下,執行完成後我們就在當前目錄下生成了Davilk虛擬機可執行的dex文件,因為這條命令同時會打包dex文件,因此後綴是jar,我們用jd-gui打開dynamic.jar和dynamic_dex.jar這兩個文件,看下他們有的結構。   \     \   可以看到,打包後的文件其實是一個classes.dex文件,目前為止我們要做的工作已經准備就緒了,接下來就是要在demo中使用這個dex文件。   二、刪除剛剛新建的impl包以及包內的文件: 因為等下我們要使用的是dex下面的IDynamic實現類,所以我們需要刪除當前工程下的IDynamic.java文件和impl包,避免運行時出錯。同時,我們要把剛剛生成的dynamic_dex.jar文件放到assets目錄下,等下需要把它copy到app/data下使用,刪除後的整個工程目錄如下:   \   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 i;
            while ((i = in.read(bytes)) != -1)
                out.write(bytes, 0 , i);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    public static boolean hasExternalStorage() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

    /**
     * 獲取緩存路徑
     *
     * @param context
     * @return 返回緩存文件路徑
     */
    public static File getCacheDir(Context context) {
        File cache;
        if (hasExternalStorage()) {
            cache = context.getExternalCacheDir();
        } else {
            cache = context.getCacheDir();
        }
        if (!cache.exists())
            cache.mkdirs();
        return cache;
    }

}
打開MainActivity:
public class MainActivity extends AppCompatActivity {

    private Dynamic dynamic;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //添加一個點擊事件
        findViewById(R.id.tx).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadDexClass();
            }
        });
    }

    /**
     * 加載dex文件中的class,並調用其中的sayHello方法
     */
    private void loadDexClass() {
        File cacheFile = FileUtils.getCacheDir(getApplicationContext());
        String internalPath = cacheFile.getAbsolutePath() + File.separator + "dynamic_dex.jar";
        File desFile = new File(internalPath);
        try {
            if (!desFile.exists()) {
                desFile.createNewFile();
                FileUtils.copyFiles(this, "dynamic_dex.jar", desFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //下面開始加載dex class
        DexClassLoader dexClassLoader = new DexClassLoader(internalPath, cacheFile.getAbsolutePath(), null, getClassLoader());
        try {
            Class libClazz = dexClassLoader.loadClass("wangyang.zun.com.mydexdemo.dynamic.impl.IDynamic");
            dynamic = (Dynamic) libClazz.newInstance();
            if (dynamic != null)
                Toast.makeText(this, dynamic.sayHelloy(), Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
程序運行的效果圖如下: \ 至此,我們關於Android Dex動態加載機制的原理講到這裡,接下來我會分析下通過Dex實現熱修復的基本原理。    
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved