Android外掛程式化(二):使用DexClassLoader動態載入assets中的apk

來源:互聯網
上載者:User

標籤:

Android外掛程式化(二):使用DexClassLoader動態載入assets中的apk簡介

上一篇部落格講到,我們可以使用MultiDex.java載入離線的apk檔案。需要注意的是,apk中的類是載入到當前的PathClassLoader當中的,如果apk檔案過多,可能會出現ANR的情況。那麼,我們能不能使用DexClassLoader載入apk呢?當然是可以的!首先看一下Doc文檔.

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

也就是說,DexClassLoader可以載入一個含有classes.dex檔案的壓縮包,既可以是jar也可以是apk。那麼載入一個離線的apk檔案需要注意哪些呢?

  • 1.DexClassLoader的構造方法:
    DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

  • 2.私人目錄
    This class loader requires an application-private, writable directory to cache optimized classes.

瞭解到上述兩點,我們就可以根據DexClassLoader所需要的參數,動態載入assets中的apk了。

源碼
BundleClassLoaderManager

該類主要是負責管理這些DexClassLoader的,首先,我們定義了一個叫做BundleDexClassLoader的類,它繼承自DexClassLoader,用於載入離線的apk檔案。每一個apk檔案對應一個BundleDexClassLoader,而BundleClassLoaderManager則儲存了一個List,在載入的時候,用於尋找類。具體代碼如下:

package net.mobctrl.hostapk;import java.io.File;import java.io.FilenameFilter;import java.util.ArrayList;import java.util.List;import android.annotation.TargetApi;import android.content.Context;import android.os.Build;/** * @Author Zheng Haibo * @PersonalWebsite http://www.mobctrl.net * @version $Id: BundleClassLoaderManager.java, v 0.1 2015年12月11日 下午7:30:59 *          mochuan.zhb Exp $ * @Description */@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)public class BundleClassLoaderManager {    public static List<BundleDexClassLoader> bundleDexClassLoaderList = new ArrayList<BundleDexClassLoader>();    /**     * 載入Assets裡的apk檔案     * @param context     */    public static void install(Context context) {        AssetsManager.copyAllAssetsApk(context);        // 擷取dex檔案清單        File dexDir = context.getDir(AssetsManager.APK_DIR,                Context.MODE_PRIVATE);        File[] szFiles = dexDir.listFiles(new FilenameFilter() {            @Override            public boolean accept(File dir, String filename) {                return filename.endsWith(AssetsManager.FILE_FILTER);            }        });        for (File f : szFiles) {            System.out.println("debug:load file:" + f.getName());            BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(                    f.getAbsolutePath(), dexDir.getAbsolutePath(), null,                    context.getClassLoader());            bundleDexClassLoaderList.add(bundleDexClassLoader);        }    }    /**     * 尋找類     *      * @param className     * @return     * @throws ClassNotFoundException     */    public static Class<?> loadClass(Context context,String className) throws ClassNotFoundException {        try {            Class<?> clazz = context.getClassLoader().loadClass(className);            if (clazz != null) {                System.out.println("debug: class find in main classLoader");                return clazz;            }        } catch (Exception e) {            e.printStackTrace();        }        for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {            try {                Class<?> clazz = bundleDexClassLoader.loadClass(className);                if (clazz != null) {                    System.out.println("debug: class find in bundle classLoader");                    return clazz;                }            } catch (Exception e) {                e.printStackTrace();            }        }        throw new ClassCastException(className + " not found exception");    }}

注意點:

  • 1.install方法
    install方法主要是將assets中的apk全部拷貝到私人目錄,然後再遍曆私人目錄,使用BundleDexClassLoader載入apk檔案,然後將這些BundleDexClassLoader儲存到數組中。

  • 2.loadClass方法
    該方法先從當前的ClassLoader中尋找需要的類,如果找不到,在從List中遍曆尋找。

DEMO運行

在MainActivity中,我們可以通過如下方式,調用apk類中的方法:

      private void loadApk() {        try {            Class<?> clazz = BundleClassLoaderManager.loadClass(getApplicationContext(),                    "net.mobctrl.normal.apk.Utils");            Constructor<?> constructor = clazz.getConstructor();            Object bundleUtils = constructor.newInstance();            Method printSumMethod = clazz.getMethod("printSum", Context.class,                    int.class, int.class, String.class);            printSumMethod.setAccessible(true);            Integer sum = (Integer) printSumMethod.invoke(bundleUtils,                    getApplicationContext(), 10, 20, "計算結果");            System.out.println("debug:sum = " + sum);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SecurityException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        }    }

與MultiDex不同時,我們是通過BundleClassLoaderManager來載入類的,而不是當前的ClassLoader。

改進方案

正如BundleClassLoaderManager中的loadClass方法,其實我們建立一個ClassLoader對象,通過重寫當前ClassLoader的findClass方法即可,然後在Override的findClass方法中,首先從當前ClassLoader中尋找類,然後再從BundleDexClassLoader中遍曆尋找,這樣既可以在Host項目中調用Bundle中的類,也能夠在Bundle中調用Host中的類。

       mClassLoader = new ClassLoader(super.getClassLoader()) {            @Override            protected Class<?> findClass(String className)                    throws ClassNotFoundException {                Class clazz = BundleClassLoaderManager.loadClass(context,className);                if (clazz == null) {                    throw new ClassNotFoundException(className);                }                return clazz;            }        };
總結

上一篇部落格和這一篇部落格將的都是類的載入。如果所需要載入的類都是工具類,不需要載入資源等,那麼上面的方案都沒啥問題。但是如果載入的類是Fragment或者Activity等UI,需要引用資源檔,這又改如何處理呢?

下一篇博文:Android資源的離線載入。

參考

1.BaseDexClassLoader源碼

Android外掛程式化(二):使用DexClassLoader動態載入assets中的apk

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.