博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android热加载随记
阅读量:6227 次
发布时间:2019-06-21

本文共 13276 字,大约阅读时间需要 44 分钟。

在我们日常的开发过程中,程序难免会出现BUG,一般有集中处理方式,发布新版本APP让用户来升级,或者打补丁来修复bug

前者本文在这里不错讨论,打补丁升级又分为两种一种是需要重启应用,一种是不需要。不需要的也可以叫他热加载。

首先使用热加载需要了解一些基本常识

1、什么是dex

Dex是Dalvik VM executes的全称,和windows上的exe很像,你项目的源码java文件已被编译成了.dex.

在用ide开发的时候编译发布构建工具(ant,gradle)会调用(aapt)将DEX文件,资源文件以及AndroidManifest.xml文件组合成一个应用程序包(APK)

2、安装apk的过程是怎么样的

复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目

ODEX是安卓上的应用程序apk中提取出来的可运行文件,即将APK中的classes.dex文件通过dex优化过程将其优化生成一个.dex文件单独存放,原APK中的classes.dex文件会保留

这样做可以加快软件的启动速度,预先提取,减少对RAM的占用,因为没有odex的话,系统要从apk包中提取dex再运行

3、app怎么运行的

简单的概括一下,就是把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统会选择哪个类进行加载呢?

来看看代码

一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。

理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类,如下图

以上就大致清楚了要做到热加载我们该怎么处理了

下面我们处理一个简单逻辑,用Toast 显示一个 除数为零的  模拟bug

接着我们创建一个application

package com.example.andfix;import android.app.Application;public class App extends Application{    private static Application _app;    public static Application get()    {        return _app;    }    @Override    public void onCreate() {        _app=this;        super.onCreate();    }        }

在建立一个Activity

package com.example.andfix;import java.io.File;import java.io.IOException;import android.app.Activity;import android.content.Context;import android.os.Build;import android.os.Bundle;import android.os.Environment;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.Toast;import com.example.andfix.tools.CalcNum;public class MainActivity extends Activity {    Button btnfix;    Button btntest;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btnfix=(Button)findViewById(R.id.btnfix);        btntest=(Button)findViewById(R.id.btntest);                btntest.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View arg0) {                new CalcNum(getApplicationContext());            }        });        btnfix.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View arg0) {                fix();            }        });    }    private void fix()    {        inject();    }                public void inject() {        String sourceFile = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator                + "classes2.dex";        String targetFile = this.getDir("odex", Context.MODE_PRIVATE).getAbsolutePath() + File.separator                + "classes2.dex";        try {                 FileUtils.copyFile(sourceFile, targetFile);            FixDexUtils.loadFixDex(this.getApplication());        } catch (IOException e) {            e.printStackTrace();        }    }                        }

 

一个工具类

package com.example.andfix;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;public class FileUtils {    public static void copyFile(String sourceFile, String targetFile) throws IOException {        InputStream is = new FileInputStream(sourceFile);        File outFile = new File(targetFile);                if(outFile.exists()){            outFile.delete();        }                OutputStream os = new FileOutputStream(targetFile);        int len = 0;        byte[] buffer = new byte[1024];        while ((len = is.read(buffer)) != -1) {            os.write(buffer, 0, len);        }        os.close();        is.close();    }}

 一个热修复逻辑

package com.example.andfix;import java.io.File;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.util.HashSet;import dalvik.system.DexClassLoader;import dalvik.system.PathClassLoader;import android.content.Context;public class FixDexUtils {    private static HashSet
loadedDex = new HashSet
(); static { loadedDex.clear(); } public static void loadFixDex(Context context) { // 获取到系统的odex 目录 File fileDir = context.getDir("odex", Context.MODE_PRIVATE); File[] listFiles = fileDir.listFiles(); for (File file : listFiles) { if (file.getName().endsWith(".dex")) { // 存储该目录下的.dex文件(补丁) loadedDex.add(file); } } doDexInject(context, fileDir); } private static void doDexInject(Context context, File fileDir) { // .dex 的加载需要一个临时目录 String optimizeDir = fileDir.getAbsolutePath() + File.separator + "opt_dex"; File fopt = new File(optimizeDir); if (!fopt.exists()) fopt.mkdirs(); // 根据.dex 文件创建对应的DexClassLoader 类 for (File file : loadedDex) { DexClassLoader classLoader = new DexClassLoader(file.getAbsolutePath(), fopt.getAbsolutePath(), null, context.getClassLoader()); //注入 inject(classLoader, context); } } private static void inject(DexClassLoader classLoader, Context context) { // 获取到系统的DexClassLoader 类 PathClassLoader pathLoader = (PathClassLoader) context.getClassLoader(); try { // 分别获取到补丁的dexElements和系统的dexElements Object dexElements = combineArray(getDexElements(getPathList(classLoader)), getDexElements(getPathList(pathLoader))); // 获取到系统的pathList 对象 Object pathList = getPathList(pathLoader); // 设置系统的dexElements 的值 setField(pathList, pathList.getClass(), "dexElements", dexElements); } catch (Exception e) { e.printStackTrace(); } } /** * 通过反射设置字段值 */ private static void setField(Object obj, Class
cl, String field, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); localField.set(obj, value); } /** * 通过反射获取 BaseDexClassLoader中的PathList对象 */ private static Object getPathList(Object baseDexClassLoader) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList"); } /** * 通过反射获取指定字段的值 */ private static Object getField(Object obj, Class
cl, String field) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); return localField.get(obj); } /** * 通过反射获取DexPathList中dexElements */ private static Object getDexElements(Object paramObject) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException { return getField(paramObject, paramObject.getClass(), "dexElements"); } /** * 合并两个数组 * @param arrayLhs * @param arrayRhs * @return */ private static Object combineArray(Object arrayLhs, Object arrayRhs) { Class
localClass = arrayLhs.getClass().getComponentType(); int i = Array.getLength(arrayLhs); int j = i + Array.getLength(arrayRhs); Object result = Array.newInstance(localClass, j); for (int k = 0; k < j; ++k) { if (k < i) { Array.set(result, k, Array.get(arrayLhs, k)); } else { Array.set(result, k, Array.get(arrayRhs, k - i)); } } return result; }}

这样就可以实现热修复了 此过程是在eclipse 上完成的

通过ant构建

${dexfile} is not handle
${dexfile} is handle
APK is released.path:${signed-apk-path}

主dex文件包含的类说明

com/example/andfix/MainActivity.class

com/example/andfix/App.class
com/example/andfix/FileUtils.class
com/example/andfix/FixDexUtils.class

文档结构如下

实现过程中也有很多坑

比如:

com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)

解决方法就是降低你的编译版本(jdk)

如果你在过程中遇到其他问题,不要怕麻烦一点一点采坑。走过来就是一种收获

当然本文只是描述热加载的过程和原理

ps:现在这样的框架也有很多

1.DroidPlugin用途:动态加载使用案例:360手机助手GitHub地址:https://github.com/Qihoo360/DroidPluginppt介绍:https://github.com/Qihoo360/DroidPlugin/tree/master/DOCDemo:https://github.com/SpikeKing/wcl-plugin-test-app详解:http://blog.csdn.net/yzzst/article/details/48093567 http://v2ex.com/t/2164942.AndFix用途:热修复GitHub地址:https://github.com/alibaba/AndFix讲解:http://blog.csdn.net/yzzst/article/details/48465031http://blog.csdn.net/qxs965266509/article/details/49816007http://blog.csdn.net/yaya_soft/article/details/504601023.dexposed用途:热修复GitHub地址:https://github.com/alibaba/dexposed讲解:                 http://blog.csdn.net/yzzst/article/details/47954479     http://blog.csdn.net/yzzst/article/details/47659987     http://www.jianshu.com/p/14edcb444c514.Small用途:动态加载GitHub地址:https://github.com/wequick/SmallDemo:https://github.com/cayden/MySmall5. DynamicAPK用途:动态加载、热修复案例:携程GitHub地址:https://github.com/CtripMobile/DynamicAPK详解:http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading6.ClassPatch用途:热修复GitHub地址:https://github.com/Jarlene/ClassPatch详解:http://blog.csdn.net/xwl198937/article/details/498019757.ACDD用途:动态加载GitHub地址:https://github.com/bunnyblue/ACDD8.HotFix用途:热修复GitHub地址:https://github.com/dodola/HotFix该项目是基于QQ空间终端开发团队的技术文章实现的9.Nuwa用途:热修复GitHub地址:https://github.com/jasonross/Nuwa详解:http://www.jianshu.com/p/72c17fb76f21/comments/128004610.DroidFix用途:热修复GitHub地址:https://github.com/bunnyblue/DroidFix详解:http://bunnyblue.github.io/DroidFix/11.AndroidDynamicLoader用途:动态加载GitHub地址:https://github.com/mmin18/AndroidDynamicLoaderDemo:https://github.com/mmin18/AndroidDynamicLoader/raw/master/host.apk

 

你可能感兴趣的文章
android DPI与分辨率的关系及计算方式
查看>>
forward_list
查看>>
伪分布式网络爬虫框架的设计与自定义实现(一)
查看>>
解决npm ERR! Unexpected end of JSON input while parsing near的方法汇总
查看>>
MySQL 入门
查看>>
js的操作及css样式
查看>>
bootstrapValidator关于js,jquery动态赋值不触发验证(不能捕获“程序赋值事件”)解决办法...
查看>>
数据库设计基础>范式
查看>>
POJ 3461 Oulipo(模式串在主串中出现的次数)
查看>>
Openstack的镜像属性
查看>>
【分享】用Canvas实现画板功能
查看>>
C++走向远洋——46(教师兼干部类、多重继承、派生)
查看>>
spring IOC源码分析(1)
查看>>
「深入理解计算系统」从Hello World开始
查看>>
手写Json转换
查看>>
Xception
查看>>
MySQL——约束(constraint)详解---转载
查看>>
模板函数
查看>>
phpcms v9实现wap单页教程
查看>>
浅析Java中的内存机制
查看>>