android插件化(BroadCast、Service篇)
来源:互联网 发布:类似wpe的软件 编辑:程序博客网 时间:2024/06/14 01:46
android对于静态广播和动态广播的处理逻辑基本相同,只是注册方式有区别,静态广播要在AndroidManifest中注册,注册的信息存储在PMS中,动态广播只需要在程序中执行registerReceiver,注册的信息存储在AMS中,但是,广播是不可以用Stub(替身)来解决的,主要是BroadcastReceiver的概念,AndroidManifest中声明的Stub是有限的,我们不可能穷举出所有的IntentFilter,但是因为广播可以动态注册,我们可以把插件中的广播动态注册到主App中,其实,我们对于Activity的处理方式也有这个问题;如果你尝试用IntentFilter的方式启动Activity,这并不能成功,这算得上是DroidPlugin的缺陷之一
ContextImpl.registerReceiver --> ContextImpl.registerReceiverInternal(在该函数中,有一个IIntentReceiver对象,相当于IApplicationThread)--> ActivityManagerNative.getDefault().registerReceiver --> ActivityManagerService.registerReceiver(BroadcastReceiver以BroadcastFilter的形式存储在AMS的mReceiverResolver变量中)
ContextImpl.sendBroadcast --> ActivityManagerService.broadcastIntent --> ActivityManagerService.broadcastIntentLocked(在broadcastIntentLocked中,广播的发送和接收是融为一体的,某个广播被发送之后,AMS会找出所有注册过的BroadcastReceiver中与这个广播匹配的接收者,然后将这个广播分发给相应的接收者处理,其中,receivers存储了静态注册的BroadcastReceiver列表,mReceiverResolver存储了动态注册的BroadcastReceiver列表,接着我们创建了一个BroadcastRecord代表此次发送的广播,然后把它丢进一个队列,最后通过scheduleBroadcastsLocked通知队列对广播进行处理,在BroadcastQueue中通过Handler调度了对于广播处理的消息,调度过程由processNextBroadcast方法完成,而这个方法通过performReceiveLocked最终调用了IIntentReceiver的performReceive方法)
详细原理参见:http://weishu.me/2016/04/12/understand-plugin-framework-receiver/
ContextImpl.bindService(IServiceConnection与IApplicationThread以及IIntentReceiver相同,都是ActivityThread给AMS提供的用来与之进行通信的Binder对象)--> ActivityManager.getDefault().bindService --> ActivityManagerService.bindService(首先通过retrieveServiceLocked方法获取到了intent匹配到的需要Binder到的Service组件res,然后把ActivityThread传递过来的IServiceConnection使用ConnectionRecord进行了包装,方便接下来使用,最后如果启动的FLAG为BIND_AUTO_CREATE,那么调用bringUpServiceLocked开始创建Service)--> ActivityManagerService.bringUpServiceLocked(如果Service所在的进程已经启动,那么直接调用realStartServiceLocked方法来「真正」启动Service组件,如果Service所在的进程还没有启动,那么先在AMS中记下这个要启动的Service组件,然后通过startProcessLocked启动新的进程),接着会进行多次进程间通讯,自行分析
Activity组件的生命周期受用户交互影响,而Service组件不受交互影响,所以可以完全由代码模拟生命周期,而且Service没有任务栈的概念,所以可以有无数个实例,而且对于Service来说,多次调用startService并不会创建多个实例,因此一个StubService只能对应一个真正的Service
完全手动接管Service:我们必须在startService,stopService等方法被调用的时候拿到控制权,才能手动去控制Service的生命周期,方法很简单——Hook ActivityManagerNative即可,在Activity的插件化方案中我们就通过这种方式接管了startActivity调用,相信读者并不陌生,我们Hook掉ActivityManagerNative之后,可以拦截对于startService以及stopService等方法的调用;拦截之后,我们可以直接对插件Service进行操作:拦截到startService之后,如果Service还没有创建就直接创建Service对象(可能需要加载插件),然后调用这个Service的onCreate,onStartCommond方法;如果Service已经创建,获取到原来创建的Service对象并执行其onStartCommand方法。拦截到stopService之后,获取到对应的Service对象,直接调用这个Service的onDestroy方法。这种方案很简直,但是,这么干是不行的,首先,Service存在的意义在于它作为一个后台任务,拥有相对较高运行时优先级;除非在内存及其不足威胁到前台Activity的时候,这个组件才会被系统杀死。上述这种实现完全把Service当作一个普通的Java对象使用了,因此并没有完全实现Service所具备的能力。其次,Activity以及Service等组件是可以指定进程的,而让Service运行在某个特定进程的情况非常常见——所谓的远程Service;用上述这种办法压根儿没有办法让某个Service对象运行在一个别的进程。Android系统给开发者控制进程的机会太少了,要么在AndroidManifest.xml中通过process属性指定,要么借助Java的Runtime类或者native的fork;这几种方式都无法让我们以一种简单的方式配合上述方案达到目的
代理分发技术:既然我们希望插件的Service具有一定的运行时优先级,那么一个货真价实的Service组件是必不可少的——只有这种被系统认可的真正的Service组件才具有所谓的运行时优先级,因此,我们可以注册一个真正的Service组件ProxyService,让这个Service承载一个真正的Service组件所具备的能力(进程优先级等);当启动插件的服务比如PluginService的时候,我们统一启动这个ProxyService,当这个ProxyService运行起来之后,再在它的onStartCommand等方法里面进行分发,执行PluginService的onStartCommond等对应的方法;代理分发技术也可以完美解决插件Service可以运行在不同的进程的问题——我们可以在AndroidManifest.xml中注册多个ProxyService,指定它们的process属性,让它们运行在不同的进程;当启动的插件Service希望运行在一个新的进程时,我们可以选择某一个合适的ProxyService进行分发。也许有童鞋会说,那得注册多少个ProxyService才能满足需求啊?理论上确实存在这问题,但事实上,一个App使用超过10个进程的几乎没有;因此这种方案是可行的
if ("startService".equals(method.getName())) { Pair<Integer, Intent> integerIntentPair = foundFirstIntentOfArgs(args); Intent newIntent = new Intent(); ComponentName componentName = new ComponentName(UPFApplication.getContext().getPackageName(), ProxyService.class.getName()); newIntent.setComponent(componentName); newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, integerIntentPair.second); args[integerIntentPair.first] = newIntent; return method.invoke(mBase, args);}if ("stopService".equals(method.getName())) { Intent raw = foundFirstIntentOfArgs(args).second; String pkgName = UPFApplication.getContext().getPackageName(); String destComponent = raw.getComponent().getPackageName(); if (!TextUtils.equals(pkgName, destComponent)) { Log.v(TAG, "hook method stopService success " + pkgName + " " + destComponent); return ServiceManager.getInstance().stopService(raw); }}
public int stopService(Intent targetIntent) { ServiceInfo serviceInfo = selectPluginService(targetIntent); if (serviceInfo == null) { Log.w(TAG, "can not found service: " + targetIntent.getComponent()); return 0; } Service service = mServiceMap.get(serviceInfo.name); if (service == null) { Log.w(TAG, "can not running, are you stopped it multi-times?"); return 0; } service.onDestroy(); mServiceMap.remove(serviceInfo.name); if (mServiceMap.isEmpty()) { Log.d(TAG, "service all stopped, stop proxy"); Context appContext = UPFApplication.getContext(); appContext.stopService(new Intent().setComponent(new ComponentName(appContext.getPackageName(), ProxyService.class.getName()))); } return 1;}
@Overridepublic void onStart(Intent intent, int startId) { Log.d(TAG, "onStart() called with " + "intent = [" + intent + "], startId = [" + startId + "]"); // 分发Service ServiceManager.getInstance().onStart(intent, startId); super.onStart(intent, startId);}
void onStart(Intent proxyIntent, int startId) { Intent targetIntent = proxyIntent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT); ServiceInfo serviceInfo = selectPluginService(targetIntent); if (serviceInfo == null) { Log.w(TAG, "can not found service : " + targetIntent.getComponent()); return; } try { if (!mServiceMap.containsKey(serviceInfo.name)) { // service还不存在, 先创建 proxyCreateService(serviceInfo); } Service service = mServiceMap.get(serviceInfo.name); service.onStart(targetIntent, startId); } catch (Exception e) { e.printStackTrace(); }}
Service作为Android系统的组件,最重要的特点是它具有Context,所以,直接通过反射创建出来的PluginService就是一个壳子——没有Context的Service能干什么?因此我们需要给将要创建的Service类创建出Conetxt,Context都是系统给我们创建好的。既然这样,我们可以参照一下系统是如何创建Service对象的;在上文的Service源码分析中,在ActivityThread类的handleCreateService完成了这个步骤,摘要如下:
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault());
service.onCreate();
可以看到,系统也是通过反射创建出了对应的Service对象,然后也创建了对应的Context,并给Service注入了活力。如果我们模拟系统创建Context这个过程,势必需要进行一系列反射调用,那么我们何不直接反射handleCreateService方法呢?当然,handleCreateService这个方法并没有把创建出来的Service对象作为返回值返回,而是存放在ActivityThread的成员变量 mService 之中,这个是小case,我们反射取出来就行
private void proxyCreateService(ServiceInfo serviceInfo) throws Exception { IBinder token = new Binder(); // 创建createServiceData Class<?> createServiceDataClass = Class.forName("android.app.ActivityThread$CreateServiceData"); Constructor<?> constructor = createServiceDataClass.getDeclaredConstructor(); constructor.setAccessible(true); Object createServiceData = constructor.newInstance(); // 写入token字段, ActivityThread的handleCreateService用这个作为key存储Service Field tokenField = createServiceDataClass.getDeclaredField("token"); tokenField.setAccessible(true); tokenField.set(createServiceData, token); // 写入info字段, 这个修改是为了loadClass的时候, LoadedApk会是主程序的ClassLoader, 我们选择Hook BaseDexClassLoader的方式加载插件 serviceInfo.applicationInfo.packageName = UPFApplication.getContext().getPackageName(); Field infoField = createServiceDataClass.getDeclaredField("info"); infoField.setAccessible(true); infoField.set(createServiceData, serviceInfo); // 写入compatInfo字段,获取默认的compatibility配置 Class<?> compatibilityClass = Class.forName("android.content.res.CompatibilityInfo"); Field defaultCompatibilityField = compatibilityClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO"); Object defaultCompatibility = defaultCompatibilityField.get(null); Field compatInfoField = createServiceDataClass.getDeclaredField("compatInfo"); compatInfoField.setAccessible(true); compatInfoField.set(createServiceData, defaultCompatibility); Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); Object currentActivityThread = currentActivityThreadMethod.invoke(null); Method handleCreateServiceMethod = activityThreadClass.getDeclaredMethod("handleCreateService", createServiceDataClass); handleCreateServiceMethod.setAccessible(true); handleCreateServiceMethod.invoke(currentActivityThread, createServiceData); // handleCreateService创建出来的Service对象并没有返回, 而是存储在ActivityThread的mServices字段里面, 这里我们手动把它取出来 Field mServicesField = activityThreadClass.getDeclaredField("mServices"); mServicesField.setAccessible(true); Map mServices = (Map) mServicesField.get(currentActivityThread); Service service = (Service) mServices.get(token); // 获取到之后, 移除这个service, 我们只是借花献佛 mServices.remove(token); // 将此Service存储起来 mServiceMap.put(serviceInfo.name, service);}
详细原理参见:http://weishu.me/2016/05/11/understand-plugin-framework-service/
- android插件化(BroadCast、Service篇)
- android service broadcast notification
- android service and broadcast
- android的Broadcast Service
- android服务(Service)与广播(Broadcast)
- android实现开机自动播放音乐实例(Broadcast、Service)
- Android 实战 (一) Remote Service,Thread,Handler,Broadcast
- Android Service 不被杀掉方法(broadcast+system/app)
- Android学习笔记 13 Broadcast & Service(part.1)
- android实现开机自动播放音乐实例(Broadcast、Service)
- Android 保证Service不被杀掉(broadcast+system/app)
- android service插件化之一
- broadcast Service
- Android Activity& service&Broadcast receiver 生命周期
- Android Service and Broadcast Receiver Tutorial
- android中service、activity、broadcast的使用
- Android:adb 启动activity、service,发送broadcast
- Android:adb 启动activity、service,发送broadcast
- 原生Android也能做Web开发了
- LEETCODE 46. Permutations
- SQL truncate 、delete与drop区别
- 特征选择之relief及reliefF算法
- 图片拼接
- android插件化(BroadCast、Service篇)
- 欢迎使用CSDN-markdown编辑器
- 网络编程:I/O复用
- UML图符号详解
- replace 第二个参数为函数的情况
- WM_COPYDATA消息回顾和总结,以及如何解决WM_COPYDATA 发送CString消息的过程中,UNICODE字符不能被正确解析的错误
- 【MATLAB图像处理3】 canny边缘检测 (附源码)
- Linux添加用户
- Spring Boot参考文档(6)Starters