概述

关联文章

JVM 类加载机制

Android 中的ClassLoader

假如刚发布的版本出现了bug,我们就需要解决bug,并且重新发布新的版本,这样会浪费很多的人力物力,有没有一种可以不重新发布App,不需要用户覆盖安装,就可以解决bug。

热修复就是为了解决上方的问题出现的,热修复主要分为三种修复,分别是

  • 代码修复
  • 资源修复
  • 动态链接库的修复(so修复)

我们一次说一下他们的原理

代码修复

代码修复主要有三个方案

  • 底层替换方案
  • 类加载方案
  • Instant Run方案

我们今天主要讲类加载方案

类加载方案

类加载方案基于dex分包,由于应用的功能越来越复杂,代码不断的增大,可能会导致65536限制异常,这说明应用中的方法数超过了65536个,产生这个问题的原因就是DVM Bytecode的限制,DVM指令集方法调用指令invoke-kind索引为16bits,最多能引用65536个方法

为了解决65536限制,从而产生了dex分包方案,dex分包方案主要做的是,在打包的时候把代码分成多个dex,将启动时必须用到的类直接放到主dex中,其他代码放到次dex中,当应用启动时先加载主dex,然后再动态加载次dex,从而缓解了65536限制

在上篇文章Android中的ClassLoader,中讲到DexPathListfindClass方法

 public Class<?> findClass(String name, List<Throwable> suppressed) {      //注释1for (Element element : dexElements) {//注释2Class<?> clazz = element.findClass(name, definingContext, suppressed);if (clazz != null) {return clazz;}}if (dexElementsSuppressedExceptions != null) {suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}return null;}

Element内部封装了DexFile,DexFile用于加载dex文件,每一个dex文件对应于一个Element,多个Element组成了有序数组dexElements,当我们在查找类时,会在注释1处遍历dexElements数组,注释2处调用ElementfindClass查找类,如果在dex找到了就返回该类,如果没有找到就在下一个dex查找

根据上方的流程我们把有bug的key.class类进行修改,然后把修改后的Key.class打包成含dex的补丁包patch.jar,放在dexElements数组的第一个元素,这样会首先找到patch.jar的key.class来替换有bug的key.class

类加载方案需要重启AppClassLoader重新加载类,所以采用此方案的不能即时生效

资源修复

资源修复并没有代码修复这么复杂,基本上就是对AssetManager进行修改,很多热修复参考了instant run的原理,我们直接分析一下instant run原理就行

instant run源码

    public static void monkeyPatchExistingResources(@Nullable Context context,@Nullable String externalResourceFile,@Nullable Collection<Activity> activities) {if (externalResourceFile == null) {return;}try {//利用反射创建一个新的AssetManagerAssetManager newAssetManager = AssetManager.class.getConstructor().newInstance();//利用反射获取addAssetPath方法Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);mAddAssetPath.setAccessible(true);//利用反射调用addAssetPath方法加载外部的资源(SD卡)if (((Integer) mAddAssetPath.invoke(newAssetManager, externalResourceFile)) == 0) {throw new IllegalStateException("Could not create new AssetManager");}// Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm// in L, so we do it unconditionally.Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");mEnsureStringBlocks.setAccessible(true);mEnsureStringBlocks.invoke(newAssetManager);if (activities != null) {//遍历activitiesfor (Activity activity : activities) {//拿到Activity的ResourcesResources resources = activity.getResources();try {//获取Resources的成员变量mAssetsField mAssets = Resources.class.getDeclaredField("mAssets");mAssets.setAccessible(true);//给成员变量mAssets重新赋值为自己创建的newAssetManagermAssets.set(resources, newAssetManager);} catch (Throwable ignore) {Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");mResourcesImpl.setAccessible(true);Object resourceImpl = mResourcesImpl.get(resources);Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");implAssets.setAccessible(true);implAssets.set(resourceImpl, newAssetManager);}//获取activity的themeResources.Theme theme = activity.getTheme();try {try {//反射得到Resources.Theme的mAssets变量Field ma = Resources.Theme.class.getDeclaredField("mAssets");ma.setAccessible(true);//将Resources.Theme的mAssets替换成newAssetManagerma.set(theme, newAssetManager);} catch (NoSuchFieldException ignore) {Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");themeField.setAccessible(true);Object impl = themeField.get(theme);Field ma = impl.getClass().getDeclaredField("mAssets");ma.setAccessible(true);ma.set(impl, newAssetManager);}Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme");mt.setAccessible(true);mt.set(activity, null);Method mtm = ContextThemeWrapper.class.getDeclaredMethod("initializeTheme");mtm.setAccessible(true);mtm.invoke(activity);Method mCreateTheme = AssetManager.class.getDeclaredMethod("createTheme");mCreateTheme.setAccessible(true);Object internalTheme = mCreateTheme.invoke(newAssetManager);Field mTheme = Resources.Theme.class.getDeclaredField("mTheme");mTheme.setAccessible(true);mTheme.set(theme, internalTheme);} catch (Throwable e) {Log.e(LOG_TAG, "Failed to update existing theme for activity " + activity,e);}pruneResourceCaches(resources);}}// 根据sdk版本的不同,用不同的方式获取Resources的弱引用集合Collection<WeakReference<Resources>> references;if (SDK_INT >= KITKAT) {// Find the singleton instance of ResourcesManagerClass<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance");mGetInstance.setAccessible(true);Object resourcesManager = mGetInstance.invoke(null);try {Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");fMActiveResources.setAccessible(true);@SuppressWarnings("unchecked")ArrayMap<?, WeakReference<Resources>> arrayMap =(ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);references = arrayMap.values();} catch (NoSuchFieldException ignore) {Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");mResourceReferences.setAccessible(true);//noinspection uncheckedreferences = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);}} else {Class<?> activityThread = Class.forName("android.app.ActivityThread");Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");fMActiveResources.setAccessible(true);Object thread = getActivityThread(context, activityThread);@SuppressWarnings("unchecked")HashMap<?, WeakReference<Resources>> map =(HashMap<?, WeakReference<Resources>>) fMActiveResources.get(thread);references = map.values();}//将的到的弱引用集合遍历得到Resources,将Resources中的mAssets字段替换为newAssetManagerfor (WeakReference<Resources> wr : references) {Resources resources = wr.get();if (resources != null) {// Set the AssetManager of the Resources instance to our brand new onetry {Field mAssets = Resources.class.getDeclaredField("mAssets");mAssets.setAccessible(true);mAssets.set(resources, newAssetManager);} catch (Throwable ignore) {Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");mResourcesImpl.setAccessible(true);Object resourceImpl = mResourcesImpl.get(resources);Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");implAssets.setAccessible(true);implAssets.set(resourceImpl, newAssetManager);}resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());}}} catch (Throwable e) {throw new IllegalStateException(e);}}

可以看出instance run热修复可以简单的总结为俩个步骤

  • 创建新的AssetManager,并通过反射调用addAssetPath方法加载外部资源,这样新建的AssetManager就包含了外部资源
  • AssetManager类型的mAsset字段的引用全部替换为新创建的AssetManager

动态链接库的修复(so修复)

so修复有俩种方式可以达到目的

  • 加载so方法的替换
  • 反射注入so路径

加载so方法的替换

Android平台加载so库主要用到了2个方法

System.load:可以加载自定义路径下的so
System.loadLibaray:用来加载已经安装APK中的so

通过上面俩个方法我们可以想到,如果有补丁so下发,就调用System.load去加载,如果没有补丁下发就用System.loadLibaray去加载,原理比较简单

反射注入so路径

这个需要我们分析一下System.loadLibaray的源码,他会调用Runtime的loadLibrary0方法

   synchronized void loadLibrary0(ClassLoader loader, String libname) {if (libname.indexOf((int)File.separatorChar) != -1) {throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname);}String libraryName = libname;if (loader != null) {//注释1String filename = loader.findLibrary(libraryName);if (filename == null) {// It's not necessarily true that the ClassLoader used// System.mapLibraryName, but the default setup does, and it's// misleading to say we didn't find "libMyLibrary.so" when we// actually searched for "liblibMyLibrary.so.so".throw new UnsatisfiedLinkError(loader + " couldn't find \"" +System.mapLibraryName(libraryName) + "\"");}//注释2String error = nativeLoad(filename, loader);if (error != null) {throw new UnsatisfiedLinkError(error);}return;}String filename = System.mapLibraryName(libraryName);List<String> candidates = new ArrayList<String>();String lastError = null;//注释3for (String directory : getLibPaths()) {//注释4String candidate = directory + filename;candidates.add(candidate);if (IoUtils.canOpenReadOnly(candidate)) {//注释5String error = nativeLoad(candidate, loader);if (error == null) {return; // We successfully loaded the library. Job done.}lastError = error;}}if (lastError != null) {throw new UnsatisfiedLinkError(lastError);}throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);}

这个方法分为俩部分,当ClassLoader为null的时候,注释3 遍历getLibPaths方法,这个方法会返回java.library.path选项配置的路径数组,在注释4拼接出so路径并传入注释5处nativeLoad方法

ClassLoader不为null的时候,在注释2处也调用了nativeLoad方法,不过他的参数是通过注释1处findLibrary方法获取的,我们看下这个方法

 public String findLibrary(String libraryName) {String fileName = System.mapLibraryName(libraryName);for (NativeLibraryElement element : nativeLibraryPathElements) {//注释1String path = element.findNativeLibrary(fileName);if (path != null) {return path;}}return null;}

这个和上面讲的findClass方法类似,nativeLibraryPathElements中的每一个NativeLibraryElement元素都对应一个so库,在注释1处调用findNativeLibrary,就会返回so的路径,这个就可以根据类加载方案一样,插入nativeLibraryPathElements数组前部,让补丁的so的路径先返回

参考:《Android 进阶解密》

全面解析 Android 热修复原理

Android 热补丁技术——资源的热修复

Android 热修复原理解析相关推荐

  1. 热修复原理学习(1)热修复技术介绍

    今天开始学习热修复的原理知识,学习方向是阿里团队编写的<深入探索Android热修复技术原理>,所以研究的热修复框架是Sophix. 之前对热修复的知识做过了解,具体是这一篇:热修复原理学 ...

  2. Android 插件化原理解析——Activity生命周期管理

    之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...

  3. Android代码入侵原理解析(一)

    Original 2017-05-06 付超红 滴滴安全应急响应中心 2017年初,在滴滴安全沙龙上,滴滴出行安全专家--付超红,针对App的攻与防进行了分享.会后大家对这个议题反响热烈,纷纷求详情求 ...

  4. Android 插件化原理解析——Hook机制之AMSPMS

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...

  5. Android动画-Animation原理解析

    Android动画-Animation原理解析 一.概述 在android中动画分为3类,帧动画.补间动画.属性动画 今天要说的就是"补间动画",补间动画的基类是Animation ...

  6. Android 广播实现原理解析

    Android 广播实现原理解析 前言 Android四大组件中的BroadcastReceiver,在我们实际工作中被频繁的使用,我们可以利用系统的开机广播,网络状态改变的广播等等实现我们的业务逻辑 ...

  7. Android之Butterknife原理解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 Butterknife是一个专注于Android系统的View注入框架, ...

  8. Android插件化原理解析——ContentProvider的插件化

    目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案:那么对于Co ...

  9. Android 插件化原理解析——Service的插件化

    在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity.BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Andr ...

最新文章

  1. 给老婆写个Python教程
  2. Redis集群:redis cluster方案
  3. 本周最热 AI 论文大集合,还不快一键收藏?
  4. Entity Framework Core 使用HiLo生成主键
  5. 冷知识:摄影艺术与图像处理算法之间的纠葛关系你知道吗
  6. 移动端安全 - 安卓Android - 漏洞修复方案整理
  7. 1.3.2 Jetty 的基本配置(2)
  8. 阿里云建成全国最大数据中心集群 全面应用自研硬核技术
  9. pip install rrdtool
  10. 数据分析在内容运营中的作用
  11. win10家庭版,双击bat文件无法运行_20200120
  12. 单片机的多路温度采集系统
  13. 直线插补算法---matlab仿真程序
  14. Navicat: Cannot create filec:\Users\***\Documens\Navicat\MySql.....文件名、目录名或卷标语法不正确
  15. 代码时间换空间以及空间换时间
  16. 中文词典的扩充和组织
  17. css的after右箭头,偷师成功,纯CSS绘制右箭头及其他——灵动外卖开发笔记
  18. Sonar代码规则之TOP30详解
  19. Wex5打包报错的解决办法
  20. Failed to load the JNI shared library 的解决方法

热门文章

  1. QT编写的嵌入式工业控制系统
  2. Win11玩游戏掉帧的解决方法
  3. TP5——workerman在线客服
  4. 骗子公司 黑中介 黑到几时?
  5. RobotFramework入门(一)简要介绍及使用
  6. 羽毛球的主要击球技术
  7. Anonympy——使用Python进行数据匿名化
  8. 小白学习老九君C++笔记(10) 使用类创建对象
  9. 免费域名证书最新申请方式大全
  10. Chrome网页接口测试工具