Android插件化(二):使用DexClassLoader動態載入assets中的apk


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源代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM