Android开发环境|Android热更新全认识

热更新 应用场景 刚发布的应用出现了比较严重的bug
有一些小的功能想即时的推送给用户去使用
热更新流程 1、线上检测到严重的crash
2、拉出bugfix分支并在分支上修复问题
3、jenkins构建和补丁生成
4、app通过推送或者主动拉取补丁文件
5、将bugfix代码合到master分支上
热更新原理

BaseDexClassLoader PathClassLoader DexClassLoader

? Android中使用PathClassLoader类作为Android的默认的类加载器,其功能是简单的从文件系统中加载类文件
源码分析
//BaseDexClassLoader重写了findClass() @Override protected Class findClass(String name) throws ClassNotFoundException { List suppressedExceptions = new ArrayList(); //通过pathList对象的findClass()获取Class对象。pathList是在BaseDexClassLoader的构造函数中new出来的 Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; } //pathList的findClass() public Class findClass(String name, List suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { //遍历dexElements列表,调用loadClassBinaryName()来加载类 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { //dexElements前面出现的dex会被优先加载,一旦Class被加载成功 return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }

具体实现
? 案例见本文件夹
构造一个DexClassLoader对象来加载新的dex文件,调用一次dexClassLoader.loadClass(dummyClassName)让dexClassLoader.pathList.dexElements中,包含新的dex
通过把dexClassLoader.pathList.dexElements**(整个列表)**插入到系统默认的classLoader.pathList.dexElements列表前面,就可以让系统优先加载新的dex中的类
优点
Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者字节码重写器
Dexposed实现的hooking,不仅可以hook应用中的自定义函数,也可以hook应用中调用的Android框架的函数
使用场景
【Android开发环境|Android热更新全认识】AOP编程
插桩(例如测试,性能监控等)
在线热更新,修复严重的,紧急的或者安全性的bug
SDK hooking以提供更好的开发体验
集成
//添加依赖 buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.10.+' classpath 'com.nabilhachicha:android-native-dependencies:0.1' } } ... //导入.so包,有本地文件可直接拷贝,此处可省略 native_dependencies { artifact 'com.taobao.dexposed:dexposed_l:0.2+:armeabi' artifact 'com.taobao.dexposed:dexposed:0.2+:armeabi' } //导入jar包 dependencies { compile files('libs/dexposedbridge.jar') } //初始化 public class MyApplication extends Application { private boolean mIsSupported = false; // 设备是否支持dexposed private boolean mIsLDevice = false; // 是否是Android 5.0及以上 @Override public void onCreate() { super.onCreate(); // check device if support and auto load libs mIsSupported = DexposedBridge.canDexposed(this); mIsLDevice = Build.VERSION.SDK_INT >= 21; } public boolean isSupported() { return mIsSupported; } public boolean isLDevice() { return mIsLDevice; } }

在线热更新案例
//添加依赖,见上述 //宿主apk,类路径:com.dexposed.MainActivity public class MainActivity extends Activity { private void showDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("宿主的标题") .setMessage("宿主的信息") .setPositiveButton("宿主的按钮", newDialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) {} }).create().show(); } } //补丁apk,替换 //添加依赖 dependencies { provided files('libs/dexposedbridge.jar') //使用provided防止冲突 provided files('libs/patchloader.jar') } public class DialogPatch implements IPatch { @Override public void handlePatch(final PatchParam arg0) throws Throwable { Class cls = null; try { cls= arg0.context.getClassLoader() .loadClass("com.dexposed.MainActivity"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } DexposedBridge.findAndHookMethod(cls, "showDialog", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { Activity mainActivity = (Activity) param.thisObject; AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity); builder.setTitle("补丁的标题") .setMessage("补丁的信息") .setPositiveButton("ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) {} }).create().show(); return null; } }); } } //宿主apk public void runPatchApk(View view) { Log.d("dexposed", "runPatchApk button clicked."); if (isLDevice) { showLog("dexposed", "It doesn't support this function on L device."); return; } if (!isSupport) { Log.d("dexposed", "This device doesn't support dexposed!"); return; } File cacheDir = getExternalCacheDir(); if(cacheDir != null){ //fullpath: /data/user/0/com.example.myhotfix/cache/patch.apk String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk"; PatchResult result = PatchMain.load(this, fullpath, null); if (result.isSuccess()) { Log.e("Hotpatch", "patch success!"); } else { Log.e("Hotpatch", "patch error is " + result.getErrorInfo()); } } showDialog(); }

    推荐阅读