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源码

时间: 2024-11-07 15:03:18

Android插件化(二):使用DexClassLoader动态加载assets中的apk的相关文章

Android插件化开发之DexClassLoader动态加载dex、jar小Demo

一.温故动态加载ClassLoader机制 如果对Android的ClassLoader加载机制不熟悉,猛戳Android插件化开发动态加载基础之ClassLoader工作机制 http://blog.csdn.net/u011068702/article/details/53248960 二.介绍 我们知道在Android中可以跟java一样实现动态加载jar,但是Android使用德海Dalvik VM,不能直接加载java打包jar的byte code,需要通过dx工具来优化Dalvik

android DexClassLoader动态加载技术详解

介绍 做项目到一定庞大的时候就会发现方法数太多,安装包根本就装不上去了,这个也不足为奇,我们都知道当方法数目超过65536这个数目限制的时候,挡在2.x的系统上面就会出现无法安装的情况,这个时候动态加载技术就显得非的重要了,我们的项目中为了兼容2.x的手机也是用到了android的动态加载技术,这里我会详细的讲解一下怎么去用,怎么实战,我感觉,空谈理论不如动手来得实在. 实例 下面就通过一个例子反复的说明怎么来实现动态加载,通过不同的方法来调用. 准备工作 1:新建一个java工程(我比较懒我就

Android动态加载ListView中的Item

我这周上网看到动态增加listview的每一项item的布局,今天抽空自己写了一个,方便自己日后使用,这个效果还是很不错的,用到了Adapter的notifyDataSetChanged()方法,当点击每一个Item的时候,就通知adapter更新getView,系统得到通知就相应的加载一遍布局,就达到了动态加载item布局的效果.希望给大家带来点启迪,有问题或想要代码的可以留言,欢迎大家留言谈论listview的一些知识,以求共同进步,转载请标明出处: http://blog.csdn.net

利用DexClassLoader动态加载dex文件

Java中也有类加载器ClassLoader,其作用是动态装载Class文件,当我们从网络下载Class文件,或者在编译时不参与而在运行时动态调用时就需要用类加载器.由于Android对class文件进行了重新打包和优化,最终APK文件中包含的是dex文件,加载这种文件就需要用到DexClassLoader. DexClassLoader(dexPath, optimizedDirectory, libraryPath, parent) dexPath:目标类所在的APK或者jar包,/.../

通过DexClassLoader动态加载代码

动态加载代码,会有多种需求,有的是APK过大,想缩小点:有的是部分代码需要灵活变动,例如视频站点的解析规则. 奉上一个Demo,在这个demo中验证了从dexclassloader加载 1.db 2.,sharedpreference 3.webview 4. so库 5.context 6.传入listener回调 7.加载不同package下的类 欢迎大家下载: http://download.csdn.net/download/ameryzhu/8970167 版权声明:本文为博主原创文章

echarts在.Net中使用实例(二) 使用ajax动态加载数据

前一篇文章链接:echarts在.Net中使用实例(一) 简单的Demo 通过上一篇文章可以知道和echarts参考手册可知,series字段就是用来存储我们显示的数据,所以我们只需要用ajax来获取series的值就可以. option 名称 描述 {color}backgroundColor 全图默认背景,(详见backgroundColor),支持rgba,默认为无,透明 {Array} color 数值系列的颜色列表,(详见color),可配数组,eg:['#87cefa', 'rgba

jquery的ztree插件的使用(完成动态加载树型结构)

第一步:在left.jsp中 <script language="JavaScript" src="${pageContext.request.contextPath }/script/jquery-1.4.2.js"></script> <script language="JavaScript" src="${pageContext.request.contextPath }/script/jquery-

MVC4中AJAX Html页面打开调用后台方法实现动态加载数据库中的数据

之前一直用window.onload方法来调用js方法来实现,今天纠结能不能换个方法实现. 很明显是可以的. 在html前台页面引用js代码如下 @Scripts.Render("~/Scripts/User/AddUser.js") 这就算引用了,看adduser.js代码如果调用后台方法 $(document).ready( function (){ $.ajax({ type: 'POST', url: "/EditUserPassWord/UserGroupQuery

Android插件化探索(二)资源加载

前情提要 在探索资源加载方式之前,我们先来看看上一篇中没细讲的东西.还没看过的建议先看上一篇Android插件化探索(一)类加载器DexClassLoader. PathClassLoader和DexClassLoader的区别 DexClassLoader的源码如下: public class DexClassLoader extends BaseDexClassLoader { //支持从任何地方的apk/jar/dex中读取 public DexClassLoader(String dex