插件化理解与实现 —— 加载 Activity「类加载篇」

              本文重点:插件化理解与实现 —— 加载 Activity「类加载篇」

              1前言插件化算是比较复杂的一个话题。

              刚一接触的时候,我是一脸懵逼的,网上看了很多博客,一直是似懂非懂,不得其要领。

              期间也尝试看了Small,也是知其然不知其所以然。

              就此搁置一段时间,直到真正拿出勇气,尝试自己实现插件化,成功加载了四大组件之一Activity。

              这才明白它的背后究竟做了什么,以及为什么这么做。

              希望借着这篇文章,谈谈自己的理解。 也希望通过我的小Demo,能帮大家更轻松的理解诸如Small、VirtualApk、Atlas之类的大型框架。 如有纰漏,请留言指出。 2效果预览主apk[]唤起sd卡上的插件apk[]:3源码4原理与实现Activity的加载,可以分为「类加载」和「资源加载」两个主题。 考虑到篇幅比较长,本文主要讨论「类加载」。

              「资源加载」将放到下一篇文章中探讨。 类加载根据Java的类加载机制,我们知道,JVM通过ClassLoader来加载jar包中的.class文件。 并且我们需要注意两点:一个.class文件+一个ClassLoader唯一确定一个java类请遵循「双亲委派模型」通俗的讲就是,系统类诸如java.*全都委派给JVM默认的BootstrapClassLoader加载,其他的由用户自定义的ClassLoader加载。

              这样可以保护java.*下的类,并确保系统类的唯一性。

              否则,会出现("")!=于是,Android提供了两类自定义的ClassLoader来加载dex中的.class文件。 PathClassLoader只能加载已安装(data/app/目录下)的apkDexClassLoader可以加载外部(sd卡上)的apk如下图,打开我们的demo可以看到,它被安装到data/app/下。 然后由PathClassLoader来加载其中的所有类。 可以预览手机上的文件,非常方便。

              创建插件专用的DexClassLoader由于要加载外部的插件apk,你大概猜到了,我们得用new一个DexClassLoader来加载插件apk中的类。 ;.jar}and{.apk}}{DexClassLoader}thatfindsinterpretedandnativedexPaththelistofjar/},which":"}onAndroidoptimizedDirectorydirectorywhereoptimizeddexfilesnull}};maybenull}parenttheparentclassloader{(dexPath,File(optimizedDirectory),librarySearchPath,parent);看一眼它的注释,大意是说「它可以加载.jar/.apk中未安装的代码作为app的一部分」。

              其中四个参数分别为:dexPath:插件apk的路径optimizedDirectory:需指定一个缓存目录librarySearchPath:nativelib,暂时不太需要,直接给nullparent:根据「双亲委派模型」,应该给它宿主apk默认的PathClassLoader。

              于是,我们这样先new一个dexClassLoader备用:{,然后在主app的Application里调用一下:{.onCreate()),(pluginFile))用dexClassLoader加载插件中的Activity已经有了dexClassLoader,我们需要在合适的时机加载插件里的Activity。

              研究一发Activity启动流程,我们会发现Activity从创建到销毁都会经过Instrumentation这个类。

              它非常重要,后面会经常和它打交道。

              我们平常在Manifest里注册的Activity类名,最终会走到,然后反射出Activity实例。

              所以,我们只需hack掉这个方法,new出我们想要的Activity即可。 ;{ActivityInstantiationException,IllegalAccessException,ClassNotFoundException{(Activity)(className).newInstance();我们一起来找这个hack点。

              我们知道,ActivityThread,也就是我们常说的主线程,它是进程级别的单例。

              我们发现,它刚好有一个Instrumentation的成员变量,且参与到startActivity等各种重要的流程中,把它换掉就行啦。

              ;{ActivityThreadsCurrentActivityThread;ActivityThread{sCurrentActivityThread;{ActivityThread(););system){;(!system){{Instrumentation();于是,我们用反射搞一下,换成我们自定义的InstrumentationProxy。 用它来代理原来的Instrumentation:{...}{InstrumentationProxy其他不动,仅仅override(hack)掉newActivity这个方法。 :{:Activity{(,className,intent)哈哈,到此为止,我们的Activity类加载已经完成啦。

              注意此时的插件Activity只能是空Activity,不能访问R资源(此时还没实现),但可以打Log以及Toast。

              在主app里试用一下:应该可以看到,已经成功唤起插件Activity了,生命周期也照常被调用。

              还没有结束且看前面的第3步,如果使用插件还得把所有插件里的Activity事先在宿主apk里注册一遍,那一点也不动态啊。 所以,我们看到的市面上的插件化框架都是不需要注册Activity的,我们也想办法优化掉这一步。

              直接上结论吧,startActivity()走到AMS的时候,它会检查目标Activity是否注册过,并拦截掉未注册的Activity。 说实话,这一段还没有仔细的去跟过。 于是呢,我们可以事先注册一个空Activity,把Intent的目标Activity换成它,用它骗过AMS的检查。 然后在newActivity的时候,new我们真正需要的插件Activity。

              (有点ViewStub的感觉?)代码实现主要是hack掉:{..):{:Activity{(:intent).let{,所以看起来比较奇怪.:ActivityResult{{,(e:Exception){类加载小结类加载告一段落,弄透重要的几点,再去看那些成熟的框架,会轻松很多:DexClassLoaderhackInstrumentationActiivty(四大组件)占坑。