Android Dex65536方法数,热补丁方案,动态加载apk小结

Dex65536超出限制

原理神马的也不说了,方法使用google的android-support-multidex方案解。
当然也可以不使用android-support-multidex,可以自定义拆包,那么apk中就有多个dex了
先使用javac命令把源文件编译为class字节码文件,然后再用dx命令分别对class文件放入不同的dex中
原理:
Apk在运行的时候,有一个dexpathlist,而Multidex的源码中,会根据你的系统版本号对dexpathlist做修改,将所有的dex都添加到dexpathlist中。使用该方案在apk目录下中就有两个dex

当然需要使用混淆,混淆可以把Jar包中无用的class删掉,这样就减少了方法数

热补丁

注意此时热补丁是存储在存储卡中,apk安装在data/app/…./ 目录下
参考这里吧,Android 热补丁动态修复框架小结

1、动态改变BaseDexClassLoader对象间接引用的dexElements; –>这里的其实跟上面的拆包是一样的
2、在app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志。 –>解决方案:所有的类都引用另外一个单独dex中的类,就不会被打这个标志了,这个单独的dex就只有一个类

一种是在源码中插入,一种直接使用javassist插入字节码calss文件 如果在源代码中直接插入的话,
1. 首先所有的类的构造函数都要插入,很麻烦
2. 就算每个构造函数都插入了,那么必须手动把这一个类编译后的class单独打包dex,注意此时AntilazyLoad.class不能放入到主dex中,这个单独的dex包跟主dex一起放入apk中,然后在application中注入
所以一般采取javassist插入字节码class文件方式
app/build.gradle中建一个任务,那么在把打包成dex之前会先执行这个任务,那么最后所有的class的默认构造函数就多了一句
所以如果这个apk最终在机器上安装的时候,dex转换为odex过程中,发现每个类都引用另一个dex中的class,所以不打CLASS_ISPREVERIFIED标志
CLASS_ISPREVERIFIED标志是什么时候被打上去的??
dex转化成odex(dexopt)的代码中的一段,我们知道当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,然后才会拿去执行
如果类中的方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED:

补丁dex,通过网络下载,放在存储卡目录,应用启动的时候在application的onCreate中通过ClassLoader加载

注意事项: 因为项目一般都是混淆的,所以补丁包也必须混淆才不会出错
解决方案: 混淆之后,会生成一份mapping.txt文档,这个文档里记录了混淆过程中,所有的类名、方法名、变量名的变化;在混淆的时候,可以使用-applymapping选项,然后对该补丁按照指定的mapping.txt文件进行混淆

只能修复java类文件,不能对xml等资源文件进行热补丁 因为资源文件并不放在dex文件中

动态apk

动态apk放在存储卡目录中
两大问题:

1. 资源的访问
不能通过R访问了,因为不在安装目录下/data/app/…的arsc文件中
解决方案
通过调用AssetManager中的addAssetPath方法,我们可以将一个apk中的资源加载到Resources中,由于addAssetPath是隐藏api我们无法直接调用,所以只能通过反射,下面是它的声明,通过注释我们可以看出,传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,所以直接将apk的路径传给它,资源就加载到AssetManager中了,然后再通过AssetManager来创建一个新的Resources对象,这个对象就是我们可以使用的apk中的资源了,这样我们的问题就解决了。
DLProxyActivity这个代理activity是从动态apk中查找加载资源了,而不是当前context上下文中查找资源了

public class DLPluginManager {
    //每个apk对应一个DLPluginPackage对象
    private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {
        DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
        if (pluginPackage != null) {
            return pluginPackage;
        }
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        // create pluginPackage
        pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
        mPackagesHolder.put(packageInfo.packageName, pluginPackage);
        return pluginPackage;
    }
    //反射调用
    private AssetManager createAssetManager(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            return assetManager;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    //根据AssetManager生成resource
    private Resources createResources(AssetManager assetManager) {
        Resources superRes = mContext.getResources();
        Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        return resources;
    }
}
//DLProxyActivity重写getAssets和getResources方法,那么DLProxyActivity就会从动态apk中查找加载资源了,而不是当前context上下文中查找资源了,实际上getResources内部还是通过AssetManager去查找资源的啦
public class DLProxyActivity extends Activity implements DLAttachable {
    @Override
    public AssetManager getAssets() {
        return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
    }

    @Override
    public Resources getResources() {
        return impl.getResources() == null ? super.getResources() : impl.getResources();
    }
}

2. activity生命周期的管理
动态apk中的activity不是真正意义上的activity(没有在宿主程序中注册且没有完全初始化),所以这几个生命周期的方法系统就不会去自动调用了,比如此时按menu进入桌面,此时调用的是代理activity的onstop方法,动态apk中的activity的生命周期不会调用,只是一个简单的class而已
解决方案
将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用反射调用每个生命周期方法,当我们想增加一个新的生命周期方法的时候,只需要在接口中声明一下同时在代理activity中实现一下即可

public class DLProxyImpl {
    public void onCreate(Intent intent) {
        ...........
        launchTargetActivity();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    protected void launchTargetActivity() {
        try {
            Class<?> localClass = getClassLoader().loadClass(mClass);
            Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
            Object instance = localConstructor.newInstance(new Object[] {}); //反射实现newInstance该Activity实例
            mPluginActivity = (DLPlugin) instance;
            ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager); //调用DLProxyActivity的attach方法
            mPluginActivity.attach(mProxyActivity, mPluginPackage);
            Bundle bundle = new Bundle();
            bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
            mPluginActivity.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class DLProxyActivity extends Activity implements DLAttachable {
    protected DLPlugin mRemoteActivity;
    private DLProxyImpl impl = new DLProxyImpl(this);
    @Override
    public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
        mRemoteActivity = remoteActivity;
    }
    @Override
    protected void onStart() {
        mRemoteActivity.onStart(); //调用插件的onStart方法,从而实现生命周期管理
        super.onStart();
    }
}

另外一种方案,使用fragment,但是缺点很明显,界面布局只能通过fragment了

这里我只分析了DynamicLoadApk的源码
参考:
1. DynamicLoadApk 源码解析
2. DL动态加载框架技术文档

原理:
DexClassLoader加Activity代理,可以看看。 即在容器中注册几个代理的 Activity,启动插件的 Activity 时实际启动的都是代理的 Activity

总结

都绕不开Android中的ClassLoader,注意跟java默认的classloader不一样哦
1. PathClassLoader,从文档上的注释来看:
Provides a simple {@link ClassLoader} implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).
可以看出,Android是使用这个类作为其系统类和应用类的加载器。并且对于这个类呢,只能去加载已经安装到Android系统中的apk文件
2. DexClassLoader,依然看下注释:
A class loader that loads classes from {@code .jar} and {@code .apk} files containing a {@code classes.dex} entry. This can be used to execute code not installed as part of an application.

可以看出,该类呢,可以用来从.jar和.apk类型的文件内部加载classes.dex文件。可以用来执行非安装的程序代码

最后参考,我前面的两篇
1. Java ClassLoader机制
2. Android ClassLoader机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值