Android热修复及插件化原理是什么


本篇内容介绍了“Android热修复及插件化原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!热修复一直是这几年来很热门的话题,主流方案大致有两种,一种是微信Tinker的dex文件替换,另一种是阿里的Native层的方法替换。这里重点介绍Tinker的大致原理。介绍Tinker原理之前,我们先来回顾一下类加载机制。我们编译好的class文件,需要先加载到虚拟机然后才会执行,这个过程是通过ClassLoader来完成的。双亲委派模型:1.加载某个类的时候,这个类加载器不会自己立刻去加载,它会委托给父类去加载2.如果这个父类还存在父类加载器,则进一步委托,直到最顶层的类加载器3.如果父类加载器可以完成加载任务,就成功返回,否则就再委派给子类加载器4.如果都未加载成功就抛出ClassNotFoundException作用:1.避免类的重复加载。比如有两个类加载器,他们都要加载同一个类,这时候如果不是委托而是自己加载自己的,则会将类重复加载到方法区。2.避免核心类被修改。比如我们在自定义一个 java.lang.String 类,执行的时候会报错,因为 String 是 java.lang 包下的类,应该由启动类加载器加载。JVM并不会一开始就加载所有的类,它是当你使用到的时候才会去通知类加载器去加载。当我们new一个类时,首先是Android的虚拟机(Dalvik/ART虚拟机)通过ClassLoader去加载dex文件到内存。Android中的ClassLoader主要是PathClassLoader和DexClassLoader,这两者都继承自BaseDexClassLoader。它们都可以理解成应用类加载器。PathClassLoader和DexClassLoader的区别:PathClassLoader只能指定加载apk包路径,不能指定dex文件解压路径。该路径是写死的在/data/dalvik-cache/路径下。所以只能用于加载已安装的apk。DexClassLoader可以指定apk包路径和dex文件解压路径(加载jar、apk、dex文件)当ClassLoader加载类时,会调用它的findclass方法去查找该类。下方是BaseDexClassLoader的findClass方法实现:接下来我们再来看看DexPathList的findClass实现:1.使用DexClassLoader加载补丁包的dex文件2.通过反射获取DexClassLoader类的pathList,再次通过反射获得dexElements数组。3.获取加载应用类的PathClassLoader,同样通过反射获取它的dexElements数组。4.合并两个dexElements数组,且将补丁包的dex文件放在前面。
根据类加载机制,一个类只会被加载一次,DexPathList.fi免费云主机域名ndClass方法中是顺序遍历数组,所以将补丁的dex文件放在前面,这样bug修复类会被优先加载,而原来的bug类不会被加载,达到了替换bug类的功能(补丁包中的修复类名、包名要和bug类相同)5.再次通过反射将合并后的dexElements数组赋值给PathClassLoader.dexElements属性。
加载类时,Dalvik/ART虚拟机会通过PathClassLoader去查找已安装的apk文件中的类。Ok,这样就替换成功了,重启App,再调用原来的bug类,将会优先使用补丁包中的修复类。为什么要重启:因为双亲委派模型,一个类只会被ClassLoader加载一次,且加载过后的类不能卸载。接下来我们动手撸一个乞丐版的Tinker。
首先我们写一个bug类。接着我们新建一个module来生成补丁包apk。创建bug修复类,注意包名类名要一样。生成补丁apk,让用户下载这个补丁包。接下来就是加载这个apk文件并替换了。ok,乞丐版Tinker完成了,使用时先在Splash界面检查是否有插件补丁,有的话执行替换,这时你再使用bug类会发现它已经被替换成补丁中的修复类了。插件化开发模式,打包时是一个宿主apk+多个插件apk。组件化开发模式,打包时是一个apk,里面分多个module。优点:安装的主apk包会小好多给开发者提供了业务功能扩展,并且不需要用户进行更新在非主apk包中的功能出现BUG时,可以及时修复用户不需要的功能,完全就不会出现在系统里面,减轻设备的负担需要掌握的知识:1.类加载机制2.四大组件启动流程3.AIDL、Binder机制4.Hook、反射、代理上图是普通的Activity启动流程,和根Activity启动流程的区别是不用创建应用程序进程(Application Thread)。启动过程:应用程序进程中的Activity向AMS请求创建普通ActivityAMS会对这个Activty的生命周期管和栈进行管理,校验Activity等等如果Activity满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动普通Activity他们之间的跨进程通信是通过Binder实现的。通过上面介绍的热修复,我们有办法去加载插件apk里面的类,但是还没有办法去启动插件中的Activity,因为如果要启动一个Activity,那么这个Activity必须在AndroidManifest.xml中注册。这里介绍插件化的一种主流实现方式–Hook技术。1.宿主App预留占坑Activity2.使用classLoader加载dex文件到内存3.先使用占坑Activity绕过AMS验证,接着用插件Activity替换占坑的Activity。步骤1、2这里就不在赘述了,2就是上面讲到的热修复技术。AMS是在SystemServer进程中,我们无法直接进行修改,只能在应用程序进程中做文章。介绍一个类–IActivityManager,IActivityManager它通过AIDL(内部使用的是Binder机制)和SystemServer进程的AMS通讯。所以IActivityManager很适合作为一个hook点。Activity启动时会调用IActivityManager.startActivity方法向AMS发出启动请求,该方法参数包含一个Intent对象,它是原本要启动的Activity的Intent。我们可以动态代理IActivityManager的startActivity方法,将该Intent换为占坑Activity的Intent,并将原来的Intent作为参数传递过去,以此达到欺骗AMS绕开验证。接下来就通过反射的方式,将ActivityManager中的IActivityManager替换成我们的代理对象。Note: 这里获取IActivityManager实例会因为Android版本不同而不同,具体获取方法就需要去看源码了解了。这里的代码Android 8.0是可以运行的。ActivityThread启动Activity的过程如下所示:ActivityThread会通过H在主线程中去启动Activity,H类是ActivityThread的内部类并继承自Handler。H中重写的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,最终会调用Activity的onCreate方法。那么在哪进行替换呢?接着来看Handler的dispatchMessage方法:Handler的dispatchMessage用于处理消息,可以看到如果Handler的Callback类型的mCallback不为null,就会执行mCallback的handleMessage方法。因此,mCallback可以作为Hook点,我们可以用自定义的Callback来替换mCallback,自定义的Callback如下所示。最后一步就是用反射将我们自定义的callBack设置给ActivityThread.sCurrentActivityThread.mH.mCallback。其实要想启动一个Activity到这步还没有完,一个完整的Activity应该还需要布局文件,而我们的宿主APP并不会包含插件的资源。android中的资源大致分为两类:一类是res目录下存在的可编译的资源文件,比如anim,string之类的,第二类是assets目录下存放的原始资源文件。因为Apk编译的时候不会编译这些文件,所以不能通过id来访问,当然也不能通过绝对路径来访问。于是Android系统让我们通过Resources的getAssets方法来获取AssetManager,利用AssetManager来访问这些文件。其实Resource的getString, getText等各种方法都是通过调用AssetManager的私有方法来完成的。 过程就是Resource通过resource.arsc(AAPT工具打包过程中生成的文件)把ID转换成资源文件的名称,然后交由AssetManager来加载文件。AssetManager里有个很重要的方法addAssetPath(String path)方法,App启动的时候会把当前apk的路径传进去,然后AssetManager就能访问这个路径下的所有资源也就是宿主apk的资源了。我们可以通过hook这个方法将插件的path传进去,得到的AssetManager就能同时访问宿主和插件的所有资源了。新的问题又出现了,宿主apk和插件apk是两个不同的apk,他们在编译时都会产生自己的resources.arsc。即他们是两个独立的编译过程。那么它们的resources.arsc中的资源id必定是有相同的情况。这样我们上面生成的新Resources中就出现了资源id重复的情况,这样在运行的时候使用资源id来获取资源就会报错。怎么解决资源Id冲突的问题?这里介绍一下VirtualApk采用的方案。修改aapt的产物。即编译后期重新整理插件Apk的资源,编排ID,更新R文件VirtualApkhook了ProcessAndroidResourcestask。这个task是用来编译Android资源的。VirtualApk拿到这个task的输出结果,做了以下处理:1.根据编译产生的R.txt文件收集插件中所有的资源2.根据编译产生的R.txt文件收集宿主apk中的所有资源3.过滤插件资源:过滤掉在宿主中已经存在的资源4.重新设置插件资源的资源ID5.删除掉插件资源目录下前面已经被过滤掉的资源6.重新编排插件resources.arsc文件中插件资源ID为新设置的资源ID7.重新产生R.java文件大致原理是这样的,但如何保证新的Id不会重复了,这里在介绍一下资源Id的组成。packageId: 前两位是packageId,相当于一个命名空间,主要用来区分不同的包空间(不是不同的module)。目前来看,在编译app的时候,至少会遇到两个包空间:android系统资源包和咱们自己的App资源包。大家可以观察R.java文件,可以看到部分是以0x01开头的,部分是以0x7f开头的。以0x01开头的就是系统已经内置的资源id,以0x7f开头的是咱们自己添加的app资源id。typeId:typeId是指资源的类型id,我们知道android资源有animator、anim、color、drawable、layout,string等等,typeId就是拿来区分不同的资源类型。entryId:entryId是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的Entry ID有可能是相同的,但是由于它们的类型不同,我们仍然可以通过其资源ID来区别开来。所以为了避免冲突,插件的资源id通常会采用0x02 – 0x7e之间的数值。“Android热修复及插件化原理是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注百云主机网站,小编将为大家输出更多高质量的实用文章!

相关推荐: Vue分页组件如何封装

今天小编给大家分享一下Vue分页组件如何封装的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解免费云主机域名一下吧。效果如图话不多说,直接上代码以上就是“V…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 03/27 11:16
下一篇 03/27 11:49

相关推荐