插件化架构深入剖析<二>-----插庄式实现Service&动态广播启动机制剖析
基础巩固---DexClassLoader是如何加载插件中的类?
在上次https://www.cnblogs.com/webor2006/p/12267707.html插件化的学习中已经实现了Activity的跳转了,在继续开启新的学习之前,先来对这块代码的原理进行一下剖析:
其实关于Android中是如何加载类的在当时https://www.cnblogs.com/webor2006/p/10708754.html手写热修复tinker中已经详细阐述过了,但是时隔这么久了有些细节也忘了,另外还有一些新的细节可以挖掘的,所以这里再重新对其进行梳理一下,温故知新:
先来看一下Android类加载器的类结构:
其中对于我们平常接触最多的就是标红的两个,而对于这次插件化而言咱们是用的DexClassLoader来进行插件类的加载的,如下:
好,接下来从这个构造分析起,点进去发现看不到源码:
由于我们目前是用的9.0的API,而目前没有下9.0关于dalvik的源码,目前只有8.0的,而我本地vpn此时又挂了,改用在线http://androidxref.com/9.0.0_r3/的方式吧体验又比较差,所以有一个笨办法,将相关要读的源码上这个在线网站上查找,然后再手动将其下载下来并放到本地sdk的源码中既可,比如:
然后手动再将相关的源码下到本地,不过这种下载比较麻烦,得一个个进去才行,比如:
不过有个小技巧就是先记住这个下载地址,比如这个类的下载地址为:http://androidxref.com/9.0.0_r3/raw/libcore/dalvik/src/main/java/dalvik/annotation/AnnotationDefault.java
那很显然我们要下载其它类的话,直接只要改路径既可,这样就比一个个要进到java源码中再下还是要稍稍快些,具体就不多说了,纯体力活,当然如果有vpn的话这一些烦锁的操作就省啦,当然可以多学一种看源码的方式有益无害~~
下载完之后,我们再手动将下下来的源码放到SDK中的源码目录中既可,如下:
由于目录工程的tartgetSdk用的是10.0,所以将API改为28:
好,此时就可以在Android Studio正常的读源码啦,先来看DexClassLoader的构造:
调用父类的构造了:
好,此时再看一下DexPathList构造做了啥?
其中dexElements是不是相当的熟,对于像tinker这样热修复的实现主要是靠这个东东:
好,来看一下makeDexElements做了啥?
其实就是遍历apk中的dex文件,每个dex都会封装成一个Element对象,然后此时再看一下loadDexFile()的细节:
此时又创建了一个DexFile对象,看一下它构建的细节:
然后此时打开DexFile文件了:
最终是由NDK底层来打开Dex文件的,当loadDexFile()之后,则会将其放到Element的集合中:
用图总结一下这句话的背后流程:
接下来我们在代理类中会用这个ClassLoader进行类的查找,如下:
其中getClassLoader()是重写过了的,返回的就是我们自己创建的DexClassLoader,如下:
接下来则来看一下整个查找类的过程,这样整个原理就清楚了:
此时它的流程在当时手写tinker博文中已经详细阐述过了,双亲委派机制就展现得淋漓尽致,最终会调用它子类的,如下:
此时跳到子类来看一下查找的细节:
看到它是不是就非常之熟了,它里面肯定就是遍历在初始化Dex的Elements数组中进行类的查找嘛,跟进去再确定一下:
最终查看类还是通过ndk底层来查的,最后跟进去搂一眼:
至此~~关于整个插件中类的查找流程就分析完了,有之前热修复的基础的话其实还是不难的。
插庄式实现Service的调用:
在之前已经实现在插件中Activity的调用了,四大组件中还有一个比较重要的是Service,那它又是如何来调用呢?由于木有上下文,所以肯定还得依赖于插桩的Service,其整体实现思路跟Activity其实差不多,下面来实现一下:
先来在公共模块处定义一个抽象接口:
package com.android.pluginstand; import android.app.Service; import android.content.Intent; import android.content.res.Configuration; import android.os.IBinder; public interface InterfaceService { void onCreate(); void onStart(Intent intent, int startId); int onStartCommand(Intent intent, int flags, int startId); void onDestroy(); void onConfigurationChanged(Configuration newConfig); void onLowMemory(); void onTrimMemory(int level); IBinder onBind(Intent intent); boolean onUnbind(Intent intent); void onRebind(Intent intent); void onTaskRemoved(Intent rootIntent); void attach(Service proxyService); }
在插件那块也得封装一个BaseService,来将所有里面的Service进行统一,如下:
然后里面新建一个具体的子类:
注意:此时也不需要在清单文件中对这个服务进行注册的,好,接下来再回到宿主搞一个插桩服务,其实现逻辑基本上代理的Activity类似,直接贴出代码了:
package com.android.pluginarchstudy; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import com.android.pluginstand.InterfaceService; import java.lang.reflect.Constructor; public class ProxyService extends Service { private String serviceName; private InterfaceService interfaceService; @Override public IBinder onBind(Intent intent) { init(intent); return null; } private void init(Intent intent) { serviceName = intent.getStringExtra("serviceName"); //加载service 类 try { //插件oneService Class<?> aClass = getClassLoader().loadClass(serviceName); Constructor constructor = aClass.getConstructor(new Class[]{}); Object in = constructor.newInstance(new Object[]{}); interfaceService = (InterfaceService) in; interfaceService.attach(this); Bundle bundle = new Bundle(); bundle.putInt("from", 1); interfaceService.onCreate(); } catch (Exception e) { e.printStackTrace(); } } @Override public ClassLoader getClassLoader() { return PluginManager.getInstance().getDexClassLoader(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (interfaceService == null) { init(intent); } return interfaceService.onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { interfaceService.onUnbind(intent); return super.onUnbind(intent); } }
然后在清单中注册一下:
好,接下来应用一下,咱们在插件跳转到第二个界面中来开启咱们的服务:
很显然此时的startService需要重写一下,因为上下文的问题:
然后此时还需要重写个东东:
最终得在代理Activity中重写一下startService才行,因为得转到咱们的ProxyService才行的,如下:
ok,一切就续,验证一下看是否正常,先来build一个插件更新到sdcard中,然后再运行宿主app,运行如下:
完美实现~~
插庄式实现动态广播的调用:
对于Android的广播注册都知道分为静态和动态注册,这里先来实现一下动态广播,因为静态广播的要复杂一些,待下一次再来仔细学习,这里先把动态广播的调用给实现了,其实现思路基本雷同,先来定义公共方法:
然后再在插件中定义一个基类的广播:
此时也不需要往清单中进行广播的注册,但是如果是静态广播肯定还是得注册的,不过这个之后再说,目前焦点是来调用动态广播既可,接下来则来注册一下这个广播,还是在插件中点击跳转到第二个界面时进行注册,如下:
此时同样得重写这个registerReceiver,不多解释了:
好,接下来则需要回到宿主来写一个插桩的Receiver,逻辑类似,直接贴出代码,如下:
然后在清单中注册一下:
最后还得到我们的代理Activity中来重写一下registerReceiver方法,因为我们在插件中这样调用了:
所以:
最后咱们来发送应用一下,咱们还是到插件的主界面中增加一个按钮进行发送测试:
此时又因为上下文的问题得重写一个sendBroadcast才行:
好,代码一切就绪,接下来重新更新一个插件,并运行宿主看一下:
妥妥的,不过这里还有个待优化的代码,就是注册广播了还没有对它做注销处理,比较简单下面来写一下:
再重写一下宿主的unregisterReceiver方法:
ok,还有另一种静态广播的调用下一次再学~~