在我们日常的开发过程中,程序难免会出现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;}@Overridepublic 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;@Overrideprotected 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() {@Overridepublic void onClick(View arg0) {new CalcNum(getApplicationContext());}});btnfix.setOnClickListener(new OnClickListener() {@Overridepublic 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<File> loadedDex = new HashSet<File>();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和系统的dexElementsObject 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构建

<?xml version="1.0" encoding="UTF-8"?>
<!-- project项目标签 -->
<projectname="MultiDex"default="release" ><!-- 项目编译环境配置 --><propertyname="sdk-folder"value="D:\Android\SDK" /><propertyname="platform-folder"value="${sdk-folder}\platforms\android-20" /><propertyname="platform-tools-folder"value="${sdk-folder}\build-tools\20.0.0" /><propertyname="jdk-folder"value="C:\Program Files\Java\jdk1.8.0_77" /><propertyname="android-jar"value="${platform-folder}\android.jar" /><propertyname="tools.aapt"value="${platform-tools-folder}/aapt.exe" /><propertyname="tools.javac"value="${jdk-folder}\bin\javac.exe" /><propertyname="tools.dx"value="${platform-tools-folder}\dx.bat" /><propertyname="tools.apkbuilder"value="${sdk-folder}\tools\apkbuilder.bat" /><propertyname="tools.jarsigner"value="${jdk-folder}\bin\jarsigner.exe" /><!-- 项目输入目录配置 --><propertyname="project-dir"value="." /><propertyname="assets"value="${project-dir}\assets" /><propertyname="res"value="${project-dir}\res" /><propertyname="src"value="${project-dir}\src" /><propertyname="libs"value="${project-dir}\libs" /><!-- 项目输出目录配置 --><propertyname="bin"value="${project-dir}\bin" /><propertyname="gen"value="${project-dir}\gen" /><propertyname="manifest"value="${project-dir}\AndroidManifest.xml" /><!-- 生成文件放置地方 --><propertyname="java-file-gen"value="${gen}\com\example\andfix\*.java" /><propertyname="java-file-src"value="${src}\com\example\andfix\*.java" /><propertyname="main-dex-name"value="${bin}\classes.dex" /><propertyname="sub-dex-name"value="${bin}\classes2.dex" /><propertyname="package-temp-name"value="${bin}\${ant.project.name}.arsc" /><!-- 未签名包 --><propertyname="unsigned-apk-name"value="${ant.project.name}_unsigned.apk" /><propertyname="unsigned-apk-path"value="${bin}\${unsigned-apk-name}" /><!-- 签名包 --><propertyname="signed-apk-name"value="${ant.project.name}.apk" /><propertyname="signed-apk-path"value="${bin}\${signed-apk-name}" /><!-- 密钥 --><propertyname="keystore-name"value="${project-dir}\rearviewkey.keystore" /><propertyname="keystore-alias"value="rearview" /><propertyname="main-dex-rule"value="${project-dir}\main-dex-rule.txt" /><taskdef resource="net/sf/antcontrib/antlib.xml" ><classpath> <pathelement location="I:\ant-contrib.jar"/> </classpath> </taskdef><!-- 初始化target --><target name="init" ><echo message="init..." /><delete includeemptydirs="true" ><fileset dir="${bin}" ><include name="**/*" ></include></fileset></delete><mkdir dir="${bin}" /></target><!-- 生成R.java类文件 --><targetname="gen-R"depends="init" ><echo message="Generating R.java from the resources." /><execexecutable="${tools.aapt}"failοnerrοr="true" ><!-- package表示打包 --><arg value="package" /><arg value="-f" /><arg value="-m" /><arg value="-J" /><arg value="${gen}" /><arg value="-S" /><arg value="${res}" /><arg value="-M" /><arg value="${manifest}" /><arg value="-I" /><arg value="${android-jar}" /></exec></target><!-- 编译源文件生成对应的class文件 --><targetname="compile"depends="gen-R" ><echo message="compile..." /><javacbootclasspath="${android-jar}"destdir="${bin}"compiler="javac1.8"encoding="utf-8"includeantruntime="false"listfiles="true"target="1.6"><src path="${project-dir}" /><classpath><!-- 引入第三方jar包所需要引用,用于辅助编译,并没有将jar打包进去。 --><filesetdir="${libs}"includes="*.jar" /></classpath></javac></target><!-- 构建多分包dex文件 --><targetname="multi-dex"depends="compile" ><echo message="Generate multi-dex..." /><execexecutable="${tools.dx}"failοnerrοr="true" ><arg value="--dex" /><arg value="--multi-dex" /><!-- 多分包命令,每个包最大的方法数为10000 --><arg value="--set-max-idx-number=10000" /><arg value="--main-dex-list" /><!-- 主包包含class文件列表 --><arg value="${main-dex-rule}" /><arg value="--minimal-main-dex" /><arg value="--output=${bin}" /><!-- 把bin下所有class打包 --><arg value="${bin}" /><!-- 把libs下所有jar打包 --><!-- <arg value="${libs}" /> --></exec></target><!-- 打包资源文件(包括res、assets、AndroidManifest.xml) --><targetname="package"depends="multi-dex" ><echo message="package-res-and-assets..." /><execexecutable="${tools.aapt}"failοnerrοr="true" ><arg value="package" /><arg value="-f" /><arg value="-S" /><arg value="${res}" /><arg value="-A" /><arg value="${assets}" /><arg value="-M" /><arg value="${manifest}" /><arg value="-I" /><arg value="${android-jar}" /><arg value="-F" /><!-- 放到临时目录中 --><arg value="${package-temp-name}" /></exec></target><!-- 对临时目录进行打包 --><targetname="build-unsigned-apk"depends="package" ><echo message="Build-unsigned-apk" /><javaclassname="com.android.sdklib.build.ApkBuilderMain"classpath="${sdk-folder}/tools/lib/sdklib.jar" ><!-- 输出路径 --><arg value="${unsigned-apk-path}" /><arg value="-u" /><arg value="-z" /><arg value="${package-temp-name}" /><arg value="-f" /><arg value="${main-dex-name}" /><arg value="-rf" /><arg value="${src}" /><arg value="-rj" /><arg value="${libs}" /></java></target><!-- 拷贝文件到apk项目的根目录下 --><targetname="copy_dex"depends="build-unsigned-apk" ><echo message="copy dex..." /><copy todir="${project-dir}" ><fileset dir="${bin}" ><include name="classes*.dex" /></fileset></copy></target><!-- 循环遍历bin目录下的所有dex文件 --><targetname="add-subdex-toapk"depends="copy_dex" ><echo message="Add subdex to apk..." /><foreachparam="dir.name"target="aapt-add-dex" ><path><filesetdir="${bin}"includes="classes*.dex" /></path></foreach></target><!-- 使用aapt命令添加dex文件 --><target name="aapt-add-dex" ><echo message="${dir.name}" /><echo message="执行了app" /><!-- 使用正则表达式获取classes的文件名 --><propertyregexcasesensitive="false"input="${dir.name}"property="dexfile"regexp="classes(.*).dex"select="\0" /><if><equalsarg1="${dexfile}"arg2="classes.dex" /><then><echo>${dexfile} is not handle</echo></then><else><echo>${dexfile} is handle</echo><execexecutable="${tools.aapt}"failοnerrοr="true" ><arg value="add" /><arg value="${unsigned-apk-path}" /><arg value="${dexfile}" /></exec></else></if><delete file="${project-dir}\${dexfile}" /></target><!-- 生成签名的apk --><targetname="sign-apk"depends="add-subdex-toapk" ><echo message="Sign apk..." /><execexecutable="${tools.jarsigner}"failοnerrοr="true" ><!-- keystore --><arg value="-keystore" /><arg value="${keystore-name}" /><!-- 秘钥 --><arg value="-storepass" /><arg value="111111" /><!-- 秘钥口令 --><arg value="-keypass" /><arg value="111111" /><arg value="-signedjar" /><!-- 签名的apk --><arg value="${signed-apk-path}" /><!-- 未签名的apk --><arg value="${unsigned-apk-path}" /><!-- 别名 --><arg value="${keystore-alias}" /></exec></target><!-- 签名发布 --><targetname="release"depends="sign-apk" ><delete file="${package-temp-name}" /><delete file="${unsigned-apk-path}" /><echo>APK is released.path:${signed-apk-path}</echo></target></project> 

主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热加载随记相关推荐

  1. 【Flutter】Flutter 混合开发 ( 混合开发中 Flutter 的 热重启 / 热加载 )

    文章目录 前言 一.混合开发中启用 Flutter 的 热重启 / 热加载 二.混合开发中 Flutter 的 热重启 / 热加载 命令测试 三.指定混合应用连接的设备 四.相关资源 前言 上一篇博客 ...

  2. Android动态加载进阶 代理Activity模式

    基本信息 作者:kaedea 项目:android-dynamical-loading 技术背景 简单模式中,使用ClassLoader加载外部的Dex或Apk文件,可以加载一些本地APP不存在的类, ...

  3. Android动态加载技术

    基本信息 Author:kaedea GitHub:android-dynamical-loading 我们很早开始就在Android项目中采用了动态加载技术,主要目的是为了达到让用户不用重新安装AP ...

  4. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53939176 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  5. Flutter开发(二十九):Flutter热重启、热加载、调试与发布应用

    1.Flutter热重启.热加载 2.混合开发调试 dart 代码 3.发布应用 1.Flutter热重启.热加载 Flutter自带热重启/热加载的功能,但是到了 Android 项目中集成 Flu ...

  6. ANDROID动态加载 使用SO库时要注意的一些问题

    转载自:http://blog.csdn.net/qq_23331691/article/details/51699888 基本信息 作者:kaedea 项目:android-dynamical-lo ...

  7. Flutter热更新与热加载

    之前偶然从Flutter官方文档上看到了支持热更新,当然这是从2019年才开始的 Dynamic updates The Dart Platform, on which Flutter is buil ...

  8. 热加载和热部署,没听过?看看 Tomcat 是怎么实现的

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者 | 不学无数的程序员 来源 | https://urlify ...

  9. 前端使用fis3开启本地服务器,并实现热加载功能

    为了摆脱调试页面的时候需要不断切换刷新的痛苦,热加载就是因此而生,自动刷新是如此快乐 使用fis3便可以帮我们轻松实现,如果不知道什么是fis3,你可以猛点这里:fis3 接下来就要开始了: 安装fi ...

  10. android edittext html 图片,Android EditText加载HTML内容(内容包含网络图片) -电脑资料...

    android中的Html.fromHtml可以用来加载HTML的内容,fromHtml有三个参数需要设置,第一个是要显示的html内容,第二个就是要说的重点,ImageGetter,用来处理图片加载 ...

最新文章

  1. 机器视觉检测中的图像预处理方法
  2. 寻找XenApp的下载
  3. 构建JSE 开发环境(图文并茂)
  4. 网页简单配置捉取网购信息
  5. 我是如何从一个新闻狗转行成为程序猿的?
  6. [Qt教程] 第35篇 网络(五)获取本机网络信息
  7. shell 非_Shell基本操作(一)
  8. 前端_网页编程 HTTP协议(进阶)
  9. 生成ltx文件命令_利用二次开发工具批量生成PCDMIS程序
  10. iOS的消息机制和消息转发
  11. python和java学哪个好-Python和Java学哪个好?大家是怎么选的
  12. 程序员面试金典——4.7最近公共祖先
  13. Atitit 图片 验证码生成attilax总结
  14. VTK(五)---内窥镜漫游(基于VMTK血管中心线提取)
  15. 手机麦克风结构原理图_麦克风的分类和工作原理
  16. html5 canvas 涂鸦画板
  17. 程序员、架构师、技术总监、CTO
  18. 计算机日常英语句子,计算机英语句子
  19. matplotlib.pyplot超详细入门总结
  20. ASM介绍及简易教程

热门文章

  1. selenium 如何处理table
  2. MacDev.GarbageCollectionIsDeprecated-WhenXcodeCompileMacAppProject
  3. MVC3----配置连接数据库
  4. 奇葩错误SLF4J: Failed to load class org.slf4j。。的修复
  5. 一个高性能的key/value存储服务器 - Tarantool/Box
  6. Oracle数据泵对已经存在的表加载索引
  7. Android WiFi Dhcp 获取到 IP 和配置的过程
  8. CentOS 6系统FreeSwitch和RTMP服务 安装及演示(四)
  9. linux的审计功能(audit)
  10. 注册(三)之设置Contact地址的过期参数