文章目录

  • 一、代理模式和Hook原理
    • 1.1 Hook 原理
    • 1.2 代理模式
  • 二、Binder Hook
    • 2.1 分析:系统服务的获取过程
    • 2.2 寻找Hook点
    • 2.3 hook Binder示例
      • 2.3.1 伪造剪切版服务对象
      • 2.3.2 伪造IBinder 对象
      • 2.3.3 替换ServiceManager的IBinder对象
      • 2.3.4 具体的使用
  • 三、Hook AMS&PMS
    • 3.1 Hook ActivityManagerNative实现钩子AMS
    • 3.2 hook IPackageManager 实现钩子PMS
    • 3.3 具体使用
  • 四、Activity的启动流程
    • 4.1 练手:hook实现Hook掉Context.startActivity输出日志
      • 4.1.1 示例
    • 4.2 伪注册启动的实现: 基于IActivityManager和Handle.mcallback的实现
      • 4.2.1 hook了ActivityManagerNative.getDefaule返回的IActivityManager去重写startActivity()函数,调包ActivityStackSupervisor的校验注册环节
      • 4.2.2 hook了ActivityThread.handler的mCallBack恢复ActivityThread.handler启动PlugActivity
      • 4.2.5 其他生命周期的影响
      • 4.2.6 使用示例
      • 4.2.7 注意
    • 4.3 伪注册中:加载插件中的PlugActivity
      • 4.3.1 分析
      • 4.3.2 激进方案:借助mPackages对象Hook掉ClassLoader,自己操刀
        • 4.3.2.1 构建插件ApplicationInfo信息
        • 4.3.2.2 构建CompatibilityInfo
      • 4.3.3 保守方案:委托系统,让系统帮忙加载
        • 4.3.3.1 宿主的ClassLoader在哪里,是唯一的吗?
        • 4.3.3.2.给默认PathClassLoader打补丁
      • 4.3.3.3 示例
    • 4.4 伪注册启动时:绕过 PMS校验
    • 4.5 伪注册启动的实现: 基于Hook Instrumentation的execStartActivity和newAcitivity方案实现
      • 4.5.1 原理
      • 4.5.2 实现
    • 4.6 总结
  • 五、广播的处理方式
    • 5.1 源码分析
      • 5.1.1 注册过程
      • 5.1.2 发送过程context#sendBroadcast
        • 5.1.2.1 广播的匹配AMS#broadcastIntentLocked
      • 5.1.3 接收过程AMS#broadcastIntentLocked
    • 5.2 hook广播的思路
    • 5.3 插件中静态广播的非静态实现
      • 5.3.1 解析插件Apk中的PulginReceiver
      • 5.3.2 注册插件PulginReceiver
    • 5.4 插件动态广播的注册实现
    • 5.5 示例
  • 六、Service的插件化
    • 6.1 源码解析
        • 6.1.1.2 AMS#requestServiceBindingLocked实现Bind和connect
      • 6.1.3 进程不存在时service启动方式
    • 6.2 Service的插件化思路
      • 6.2.1 Service与Activity的异同
        • 6.2.1.1 用户交互对于生命周期的影响
        • 6.2.1.2 Activity的任务栈导致的差异
      • 6.2.2 如何实现Service的插件化?
        • 6.2.2.1 ~~完全手动控制~~
        • 6.2.2.2 代理分发技术
    • 6.3 Service插件化的实现
      • 6.3.1 注册代理Service
      • 6.3.2 拦截AMS#startService等调用过程
      • 6.3.3 编写并注册代理ProxyService实现Service匹配和创建分发
      • 6.3.4 匹配过程
        • 6.3.4.1 预处理:读取插件中的Service组件信息并存储
        • 6.3.4.2 匹配本地的mServiceMap缓存确认目标PluginService
      • 6.3.5 创建以及分发
        • 6.3.5.1 预处理:系统BaseDexClassLoader支持自动加载插件中的Service
        • 6.3.5.2 创建
        • 6.3.5.3 分发
    • 6.4 示例
      • 6.4.1 Application中 先初始化hook一些系统函数
      • 6.4.2 Activity中常规方式启动Service
  • 七、ContentProvider的插件化
    • 7.1 共享性
    • 7.2 ContentProvider工作原理
      • 7.2.1 当前进程中获取ActivityThread#ContentProvider的过程
      • 7.2.2 进程内部ContentProvider获取和安装过程
        • 7.2.2.1 ~~思路尝试——本地安装~~
      • 7.2.3 AMS获取对应ContentProvider
        • 7.2.3.1 查询ContentProvider组件的ProviderInfo信息过程
        • 7.2.3.2 安装ContentProvider组件的过程
      • 7.2.4 ContentProvider工作原理总结
    • 7.3 思路分析
    • 7.4 实现
      • 7.4.1 预先installProvider
      • 7.4.2 代理分发以及协议解析
      • 7.4.3 示例使用
    • 7.5 小结
  • 参考文献

我们上文提到了,必须在Manifest中注册后才能以标准Intent的方式启动的,这一部分的实现其实是通过 framework层的java代码进行实现的

那么这是否就意味着,我们通过hook一些关键的代码,就可以实现让PlugActivity看上去就和注册过的Activity一样,从而正常的启动?

这里需要的就是 动态代理的hook机制

一、代理模式和Hook原理

1.1 Hook 原理

整个Hook过程简要总结如下:

  1. 寻找Hook点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法,非public不保证每个版本都一样,需要适配。
  2. 选择合适的代理方式
    • 如果是接口可以用动态代理; 因为动态代理只用于接口编程
    • 如果是类可以手动写代理,或者直接继承重写函数,也可以使用cglib
  3. 偷梁换柱——用代理对象替换原始对象,往往使用反射

1.2 代理模式

JDK提供了动态代理方式Proxy类,给我们一种面向接口的代理模式实现方式,可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类,这样我们就不需要手写每一个静态的代理类了

项目地址:understand-plugin-framework/dynamic-proxy-hook/

  • 常规的静态代理为:
public interface Shopping {Object[] doShopping(long money);
}public class ShoppingImpl implements Shopping {@Overridepublic Object[] doShopping(long money) {System.out.println("逛淘宝 ,逛商场,买买买!!");System.out.println(String.format("花了%s块钱", money));return new Object[] { "鞋子", "衣服", "零食" };}
}public class ProxyShopping implements Shopping {Shopping base;ProxyShopping(Shopping base) {this.base = base;}@Overridepublic Object[] doShopping(long money) {// 先黑点钱(修改输入参数)long readCost = (long) (money * 0.5);System.out.println(String.format("花了%s块钱", readCost));// 帮忙买东西Object[] things = base.doShopping(readCost);// 偷梁换柱(修改返回值)if (things != null && things.length > 1) {things[0] = "被掉包的东西!!";}return things;}public class TestStatic {public static void main(String[] args) {// 原始的厂家Shopping women = new ShoppingImpl();System.out.println(Arrays.toString(women.doShopping(100)));// 换成代购women = new ProxyShopping(women);System.out.println(Arrays.toString(women.doShopping(100)));}
}
  • 动态代理
public class TestDynamic {public static void main(String[] args) {Shopping women = new ShoppingImpl();// 正常购物System.out.println(Arrays.toString(women.doShopping(100)));// 招代理women = (Shopping) Proxy.newProxyInstance(Shopping.class.getClassLoader(),women.getClass().getInterfaces(), new ShoppingHandler(women));System.out.println(Arrays.toString(women.doShopping(100)));}
}public class ShoppingHandler implements InvocationHandler {/*** 被代理的原始对象*/Object base;public ShoppingHandler(Object base) {this.base = base;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("doShopping".equals(method.getName())) {// 这里是代理Shopping接口的对象// 先黑点钱(修改输入参数)Long money = (Long) args[0];long readCost = (long) (money * 0.5);System.out.println(String.format("花了%s块钱", readCost));// 帮忙买东西Object[] things = (Object[]) method.invoke(base, readCost);// 偷梁换柱(修改返回值)if (things != null && things.length > 1) {things[0] = "被掉包的东西!!";}return things;}if ("doSomething".equals(method.getName())) {// 可以代理别的,做些别的事情return null;}if ("doSomethingElse".equals(method.getName())) {// 做些别的事情return null;}return null;}
}
  • Proxy
  /*** @param   loader 代理类的ClassLoader* @param   代理类要实现的接口* @param   h 持有真实Target实例base,并触发分发函数调用*/public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{}

二、Binder Hook

我们知道,Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如ActivityManagerService,ClipboardManager, AudioManager等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。

为了使得插件能够无缝地使用这些系统服务,可以对这些系统服务做出一定的改造(Hook),使得插件的开发和使用更加方便,从而大大降低插件的开发和维护成本。比如,Hook住ActivityManagerService可以让插件无缝地使用startActivity方法而不是使用特定的方式(比如that语法)来启动插件或者主程序的任意界面

由于应用进程和服务进程的交互方式是使用Binder进行的,因此我们把这种Hook系统服务的机制称之为Binder Hook,因为本质上这些服务提供者都是存在于系统各个进程的Binder对象

Binder Hook使得插件就像是主程序一样,譬如插件需要使用主程序的剪切版,插件之间也会共用剪切版,其他的一些系统服务也类似,这样就可以达到插件和宿主程序之间的天衣服缝。

ActivityManager以及PackageManager这两个系统服务虽然也可以通过这种方式hook,但是由于它们的重要性和特殊性,DroidPlugin使用了另外一种方式

2.1 分析:系统服务的获取过程

我们知道系统的各个远程service对象都是以Binder的形式存在的,而这些Binder有一个管理者,那就是ServiceManager

我们要Hook掉这些service,自然要从这个ServiceManager下手,不然星罗棋布的Binder广泛存在于系统的各个角落,要一个个找出来还真是大海捞针。

回想一下我们使用系统服务的时候是怎么干的,想必这个大家一定再熟悉不过了:

//通过Context对象的getSystemService方法;比如要使用ActivityManager:
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);//查看Contextlmpl#getSystemService方法;(Context的实现在ContextImpl里面):
public Object getSystemService(String name) {ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);return fetcher == null ? null : fetcher.getService(this);
}//所有的service对象都保存在一张map里面,我们再看这个map是怎么初始化的:
registerService(ACCOUNT_SERVICE, new ServiceFetcher() {public Object createService(ContextImpl ctx) {IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);//【步骤1】IAccountManager service = IAccountManager.Stub.asInterface(b);//【步骤2】return new AccountManager(ctx, service);//【步骤3】 再封装}});

在ContextImpl的静态初始化块里面,有的Service是像上面这样初始化的;可以看到,确实使用了ServiceManager;当然还有一些service并没有直接使用ServiceManager,而是做了一层包装并返回了这个包装对象,比如我们的ActivityManager,它返回的是ActivityManager这个包装对象:

registerService(ACTIVITY_SERVICE, new ServiceFetcher() {public Object createService(ContextImpl ctx) {return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());}});//但是在ActivityManager这个类内部,也使用了ServiceManager;具体来说,因为ActivityManager里面所有的核心操作都是使用ActivityManagerNative.getDefault()完成的
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {protected IActivityManager create() {IBinder b = ServiceManager.getService("activity");//【步骤1】IActivityManager am = asInterface(b);//【步骤2】return am;}};

最终,通过分析我们得知,系统Service的使用其实就分为两步:

IBinder b = ServiceManager.getService("service_name"); // 获取原始的IBinder对象
IXXInterface in = IXXInterface.Stub.asInterface(b); // 转换为Service接口

2.2 寻找Hook点

由于系统服务的使用者都是对第二步获取到的IXXInterface进行操作,因此如果我们要hook掉某个系统服务,只需要把第二步的asInterface方法返回的对象修改为为我们Hook过的对象就可以了。

这里我们以系统剪切版服务为例,源码位置为android.content.IClipboard,IClipboard.Stub.asInterface方法代码如下:

  • 先查看本进程是否存在对应的服务Service对象,如果有那么直接就是本进程调用了,直接返回服务对象;如果不存在那么创建一个代理对象,持有binder通信,让代理对象委托驱动完成跨进程调用。

    • 前面的那个if语句判空返回肯定动不了手脚;最后一句调用构造函数然后直接返回我们也是无从下手
    • 要修改asInterface方法的返回值,只能是 obj.queryLocalInterface
    • 我们可以尝试修改这个obj对象的queryLocalInterface方法的返回值,并保证这个返回值符合接下来的if条件检测,那么就达到了修改asInterface方法返回值的目的
public static android.content.IClipboard asInterface(android.os.IBinder obj) {if ((obj == null)) {return null; }android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点if (((iin != null) && (iin instanceof android.content.IClipboard))) {return ((android.content.IClipboard) iin);}return new android.content.IClipboard.Stub.Proxy(obj);
}
  • 【确认hook对象:】因此我们知道了,我们需要hook ob对象的queryLocalInterface方法,而这个obj对象刚好是我们第一步返回ServiceManager.getService("service_name");的IBinder对象

    • 我们希望能修改这个getService方法的返回值,让这个方法返回一个我们伪造过的IBinder对象;这样,我们可以在自己伪造的IBinder对象的queryLocalInterface方法作处理,进而使得asInterface方法返回在queryLocalInterface方法里面处理过的值,最终实现hook系统服务的目的

在跟踪这个getService方法之前我们思考一下,由于系统服务是一系列的远程Service,它们的本体,也就是Binder本地对象一般都存在于某个单独的进程,在这个进程之外的其他进程存在的都是这些Binder本地对象的代理。因此在我们的进程里面,存在的也只是这个Binder代理对象,我们也只能对这些Binder代理对象下手

public static IBinder getService(String name) {try {IBinder service = sCache.get(name);if (service != null) {return service;} else {return getIServiceManager().getService(name);}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;
}

这个getService是一个静态方法,如果此方法什么都不做,拿到Binder代理对象之后直接返回;我们没有办法拦截一个静态方法,也没有办法获取到这个静态方法里面的局部变量
然而ServiceManager为了避免每次都进行跨进程通信,把这些Binder代理对象缓存在一张map里面。

我们可以替换这个map里面的内容为Hook过的IBinder对象,由于系统在getService的时候每次都会优先查找缓存,因此返回给使用者的都是被我们修改过的对象,从而达到瞒天过海的目的

总结一下,要达到修改系统服务的目的,我们需要如下两步:

  1. 首先肯定需要伪造一个系统服务对象PluginService,接下来就要想办法让asInterface能够返回我们的这个伪造对象而不是原始的系统服务对象。
  2. 通过上文分析我们知道,只要让getService返回IBinder对象的queryLocalInterface方法直接返回我们伪造过的系统服务对象就能达到目的。
    • 所以,我们需要伪造一个IBinder对象,主要是修改它的queryLocalInterface方法,让它返回我们伪造的系统服务对象;然后把这个伪造IBinder对象放置在ServiceManager的缓存map里面即可。

我们通过Binder机制的优先查找本地Binder对象的这个特性达到了Hook掉系统服务对象的目的。

因此queryLocalInterface也失去了它原本的意义(只查找本地Binder对象,没有本地对象返回null),这个方法只是一个傀儡,是我们实现hook系统对象的桥梁:我们通过这个“漏洞”让asInterface永远都返回我们伪造过的对象。

由于我们接管了asInterface这个方法的全部,我们伪造过的这个系统服务对象不能是只拥有本地Binder对象(原始queryLocalInterface方法返回的对象)的能力,还要有Binder代理对象操纵驱动的能力。

2.3 hook Binder示例

接下来我们就以Hook系统的剪切版服务为例,用实际代码来说明,如何Hook掉系统服务。

项目地址:understand-plugin-framework/binder-hook/

在app里面使用剪切版,比如长按进行粘贴之后,剪切版的内容永远都是you are hooked了;

2.3.1 伪造剪切版服务对象

首先我们用代理的方式伪造一个剪切版服务对象,具体代码如下,我们用动态代理的方式Hook掉了hasPrimaryClip(),getPrimaryClip()这两个方法:

public class BinderHookHandler implements InvocationHandler {private static final String TAG = "BinderHookHandler";// 原始的服务Service对象 (IInterface)Object base;public BinderHookHandler(IBinder base, Class<?> stubClass) {try {Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);// IClipboard.Stub.asInterface(base);   传进来的是原始IBinder, 通过调用#asInterface函数,能拿到真正的服务对象this.base = asInterfaceMethod.invoke(null, base);} catch (Exception e) {throw new RuntimeException("hooked failed!");}}@TargetApi(Build.VERSION_CODES.HONEYCOMB)@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 把剪切版的内容替换为 "you are hooked"if ("getPrimaryClip".equals(method.getName())) {Log.d(TAG, "hook getPrimaryClip");return ClipData.newPlainText(null, "you are hooked");}// 欺骗系统,使之认为剪切版上一直有内容if ("hasPrimaryClip".equals(method.getName())) {return true;}return method.invoke(base, args);}
}

传进来的是原始IBinder, 通过调用#asInterface函数,能拿到真正的服务对象

注意,我们拿到原始的IBinder对象之后,如果我们希望使用被Hook之前的系统服务,并不能直接使用这个IBinder对象,而是需要使用asInterface方法将它转换为IClipboard接口;

  • 因为getService方法返回的IBinder实际上是一个裸Binder代理对象,它只有与驱动打交道的能力,但是它并不能独立工作,需要人指挥它;
  • asInterface方法返回的IClipboard.Stub.Proxy类的对象通过操纵这个裸BinderProxy对象从而实现了具体的IClipboard接口定义的操作。

2.3.2 伪造IBinder 对象

在上一步中,我们已经伪造好了系统服务对象,现在要做的就是想办法让asInterface方法返回我们伪造的对象了;我们伪造一个IBinder对象:

public class BinderProxyHookHandler implements InvocationHandler {private static final String TAG = "BinderProxyHookHandler";// 绝大部分情况下,这是一个BinderProxy对象// 只有当Service和我们在同一个进程的时候才是Binder本地对象// 这个基本不可能IBinder base;Class<?> stub;Class<?> iinterface;public BinderProxyHookHandler(IBinder base) {this.base = base;try {this.stub = Class.forName("android.content.IClipboard$Stub");this.iinterface = Class.forName("android.content.IClipboard");} catch (ClassNotFoundException e) {e.printStackTrace();}}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("queryLocalInterface".equals(method.getName())) {Log.d(TAG, "hook queryLocalInterface");// 这里直接返回真正被Hook掉的Service接口// 这里的 queryLocalInterface 就不是原本的意思了// 我们肯定不会真的返回一个本地接口, 因为我们接管了 asInterface方法的作用// 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象// 这只是一个Hook点而已, 它原始的含义已经被我们重定义了; 因为我们会永远确保这个方法不返回null// 让 IClipboard.Stub.asInterface 永远走到if语句的else分支里面return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),// asInterface 的时候会检测是否是特定类型的接口然后进行强制转换// 因此这里的动态代理生成的类型信息的类型必须是正确的new Class[] { IBinder.class, IInterface.class, this.iinterface },new BinderHookHandler(base, stub));}Log.d(TAG, "method:" + method.getName());return method.invoke(base, args);}
}

我们使用动态代理的方式伪造了一个跟原始IBinder一模一样的对象,然后在这个伪造的IBinder对象的queryLocalInterface方法里面返回了我们第一步创建的伪造过的系统服务对象;

2.3.3 替换ServiceManager的IBinder对象

现在就是万事具备,只欠东风了;

  • 我们使用反射的方式修改ServiceManager类里面缓存的Binder对象,使得getService方法返回我们伪造的IBinder对象
  • 进而asInterface方法使用伪造IBinder对象的queryLocalInterface方法返回了我们伪造的系统服务对象。

代码较简单,如下:

public class BinderHookHelper {public static void hookClipboardService() throws Exception {final String CLIPBOARD_SERVICE = "clipboard";// 下面这一段的意思实际就是: ServiceManager.getService("clipboard");// 只不过 ServiceManager这个类是@hide的Class<?> serviceManager = Class.forName("android.os.ServiceManager");Method getService = serviceManager.getDeclaredMethod("getService", String.class);// ServiceManager里面管理的原始的Clipboard Binder对象// 一般来说这是一个Binder代理对象IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);// Hook 掉这个Binder代理对象的 queryLocalInterface 方法// 然后在 queryLocalInterface 返回一个IInterface对象, hook掉我们感兴趣的方法即可.IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),new Class<?>[] { IBinder.class },new BinderProxyHookHandler(rawBinder));// 把这个hook过的Binder代理对象放进ServiceManager的cache里面// 以后查询的时候 会优先查询缓存里面的Binder, 这样就会使用被我们修改过的Binder了Field cacheField = serviceManager.getDeclaredField("sCache");cacheField.setAccessible(true);Map<String, IBinder> cache = (Map) cacheField.get(null);cache.put(CLIPBOARD_SERVICE, hookedBinder);}
}

2.3.4 具体的使用

  • 适当的实际hook底层源码
  • 触发剪切板操作
public class MainActivity extends Activity {@TargetApi(Build.VERSION_CODES.HONEYCOMB)@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);try {BinderHookHelper.hookClipboardService();} catch (Exception e) {e.printStackTrace();}EditText editText = new EditText(this);setContentView(editText);}
}

三、Hook AMS&PMS

understand-plugin-framework/ams-pms-hook/

  • ActivityManagerService对于FrameWork层的重要性不言而喻,Android的四大组件无一不与它打交道:

    • startActivity最终调用了AMS的startActivity系列方法,实现了Activity的启动;Activity的生命周期回调,也在AMS中完成;
    • startService,bindService最终调用到AMS的startService和bindService方法;
    • 动态广播的注册和接收在AMS中完成(静态广播在PMS中完成)
    • getContentResolver最终从AMS的getContentProvider获取到ContentProvider
  • PMS则完成了诸如权限校捡(checkPermission,checkUidPermission),Apk meta信息获取(getApplicationInfo等),四大组件信息获取(query系列方法)等重要功能

3.1 Hook ActivityManagerNative实现钩子AMS

ActivityManagerNative

ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用

其实startActivity最终通过ActivityManagerNative这个方法远程调用了AMS的startActivity方法。那么这个ActivityManagerNative是什么呢?

public abstract class ActivityManagerNative extends Binder implements IActivityManager{static public IActivityManager getDefault() {return gDefault.get();}private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {protected IActivityManager create() {IBinder b = ServiceManager.getService("activity");if (false) {Log.v("ActivityManager", "default service binder = " + b);}IActivityManager am = asInterface(b);if (false) {Log.v("ActivityManager", "default service = " + am);}return am;}};static public IActivityManager asInterface(IBinder obj) {if (obj == null) {return null;}IActivityManager in =(IActivityManager)obj.queryLocalInterface(descriptor);if (in != null) {return in;}return new ActivityManagerProxy(obj);}
}

具体分析见下文的《伪注册启动的实现》; 我们先看hook的实现

public final class HookHelper {public static void hookActivityManager() {try {Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");// 获取 gDefault 这个字段, 想办法替换它Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");gDefaultField.setAccessible(true);Object gDefault = gDefaultField.get(null);// 4.x以上的gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段Class<?> singleton = Class.forName("android.util.Singleton");Field mInstanceField = singleton.getDeclaredField("mInstance");mInstanceField.setAccessible(true);// ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象Object rawIActivityManager = mInstanceField.get(gDefault);// 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[] { iActivityManagerInterface }, new HookHandler(rawIActivityManager));mInstanceField.set(gDefault, proxy);} catch (Exception e) {throw new RuntimeException("Hook Failed", e);}}}

HookHandler代码如下:


/*** 一个简单的用来演示的动态代理对象 (PMS和AMS都使用这一个类)* 只是打印日志和参数; 以后可以修改参数等达到更加高级的功能*/
class HookHandler implements InvocationHandler {private static final String TAG = "HookHandler";private Object mBase;public HookHandler(Object base) {mBase = base;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Log.d(TAG, "hey, baby; you are hooked!!");Log.d(TAG, "method:" + method.getName() + " called with args:" + Arrays.toString(args));return method.invoke(mBase, args);}
}

3.2 hook IPackageManager 实现钩子PMS

PMS的获取也是通过Context完成的,具体就是getPackageManager这个方法;

//直奔ContextImpl类的getPackageManager方法:
public PackageManager getPackageManager() {if (mPackageManager != null) {return mPackageManager;}IPackageManager pm = ActivityThread.getPackageManager();if (pm != null) {// Doesn't matter if we make more than one instance.return (mPackageManager = new ApplicationPackageManager(this, pm));}return null;
}

可以看到,这里干了两件事:

  1. 真正的PMS的代理对象在ActivityThread类里面
  2. ContextImpl通过ApplicationPackageManager对它还进行了一层包装

继续查看ActivityThread类的getPackageManager方法

   static IPackageManager sPackageManager;public static IPackageManager getPackageManager() {if (sPackageManager != null) {return sPackageManager;}IBinder b = ServiceManager.getService("package");sPackageManager = IPackageManager.Stub.asInterface(b);return sPackageManager;
}

可以看到,和AMS一样,PMS的Binder代理对象也是一个全局变量存放在一个静态字段中;我们可以如法炮制,Hook掉PMS。

现在我们的目的很明切,如果需要Hook PMS有两个地方需要Hook掉:

  1. ActivityThread的静态字段sPackageManager
  2. 通过Context类的getPackageManager方法获取到的ApplicationPackageManager对象里面的mPM字段。
public final class HookHelper {public static void hookPackageManager(Context context) {// 获取全局的ActivityThread对象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");Object currentActivityThread = currentActivityThreadMethod.invoke(null);// 获取ActivityThread里面原始的 sPackageManagerField sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");sPackageManagerField.setAccessible(true);Object sPackageManager = sPackageManagerField.get(currentActivityThread);// 准备好代理对象, 用来替换原始的对象Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),new Class<?>[] { iPackageManagerInterface },new HookHandler(sPackageManager));// 1. 替换掉ActivityThread里面的 sPackageManager 字段sPackageManagerField.set(currentActivityThread, proxy);// 2. 替换 ApplicationPackageManager里面的 mPM对象PackageManager pm = context.getPackageManager();Field mPmField = pm.getClass().getDeclaredField("mPM");mPmField.setAccessible(true);mPmField.set(pm, proxy);}
public final class HookHelper {

有一个麻烦的,那就是Context的实现类里面没有使用静态全局变量来保存PMS的代理对象,而是每拥有一个Context的实例就持有了一个PMS代理对象的引用;所以这里有个很蛋疼的事情,那就是我们如果想要完全Hook住PMS,需要精确控制整个进程内部创建的Context对象;

3.3 具体使用

  1. attachBaseContext中,预处理,hook一些底层代码
  2. 触发AMS 和 PMS操作
public class MainActivity extends Activity implements OnClickListener{// 这个方法比onCreate调用早; 在这里Hook比较好.@Overrideprotected void attachBaseContext(Context newBase) {HookHelper.hookActivityManager();HookHelper.hookPackageManager(newBase);super.attachBaseContext(newBase);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);findViewById(R.id.btn1).setOnClickListener(this);findViewById(R.id.btn2).setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn1:// 测试AMS HOOK (调用其相关方法)Uri uri = Uri.parse("http://wwww.baidu.com");Intent t = new Intent(Intent.ACTION_VIEW);t.setData(uri);startActivity(t);break;case R.id.btn2:// 测试PMS HOOK (调用其相关方法)getPackageManager().getInstalledApplications(0);break;}}
}

四、Activity的启动流程

(4.1.52)Android启动流程分析中有详细的启动流程分析,在这里,我们简单的回顾下


【图android启动流程简述】

Zygote进程 –> SystemServer进程 –> 各种系统服务
–> 应用进程

我们使用startActivity有两种形式:

  1. 直接调用Context类的startActivity方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个Flag。

  2. 调用被Activity类重载过的startActivity方法,通常在我们的Activity中直接调用这个方法就是这种形式;

无论是哪种方法,在Actvity启动过程中,其实都是是应用进程与SystemServer进程相互配合启动Activity的过程

涉及到多个进程之间的通讯这里主要是ActivityThread与ActivityManagerService之间的通讯,其中:

  1. 应用进程主要用于执行具体的Activity的启动过程,回调生命周期方法等操作

    • ActivityThread响应ActivityManagerService的ActivityStack等的远程调用,并触发Instrumentation 进行Activity生命周期管理
    • 使用ActivityManagerNative(IActivityManager)向ActivityManagerService申请远端通信,并远程调用ActivityStack、ActivityStackSupervisor进行Activity进出栈等操作
  2. SystemServer进程则主要是调用其中的各种服务,将Activity保存在栈中,协调各种系统资源等操作
    • ActivityManagerService响应ActivityManagerNative的远程调用,并使用ActivityStack、ActivityStackSupervisor进行管理
    • 使用app.thread申请远端通信,触发ActivityThread的schedulexxx方法,并远程调用Instrumentation 进行Activity生命周期管理

4.1 练手:hook实现Hook掉Context.startActivity输出日志

项目地址:understand-plugin-framework/dynamic-proxy-hook/

我们知道对于Context.startActivity(Activity.startActivity的调用链与之不同),由于Context的实现实际上是ContextImpl;我们看ConetxtImpl类的startActivity方法:

@Override
public void startActivity(Intent intent, Bundle options) {warnIfCallingFromSystemProcess();//【1】在Service等非Activity的Context里面启动Activity为什么需要添加FLAG_ACTIVITY_NEW_TASK;if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {throw new AndroidRuntimeException("Calling startActivity() from outside of an Activity "+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."+ " Is this really what you want?");}//【2】真正的startActivity使用了Instrumentation类的execStartActivity方法mMainThread.getInstrumentation().execStartActivity(getOuterContext(), mMainThread.getApplicationThread(), null,(Activity)null, intent, -1, options);
}//Instrumentation#execStartActivity方法 非本章节的研究点,仅列出给大家看下
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {// ... 省略无关代码try {intent.migrateExtraStreamToClipData();intent.prepareToLeaveProcess();//【3】ActivityManagerNative的startActivity方法int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, target != null ? target.mEmbeddedID : null,requestCode, 0, null, null, options);checkStartActivityResult(result, intent);} catch (RemoteException e) {}return null;
}

这里,实际上使用了ActivityThread类的mInstrumentation成员的execStartActivity方法;- 【hook对象】注意到,ActivityThread 实际上是主线程,而主线程一个进程只有一个,因此这里是一个良好的Hook点

接下来就是想要Hook掉我们的主线程对象,也就是把这个主线程对象里面的mInstrumentation给替换成我们修改过的代理对象;要替换主线程对象里面的字段,首先我们得拿到主线程对象的引用,如何获取呢?

  • 【步骤1:hook点的确认】ActivityThread类里面有一个静态方法currentActivityThread可以帮助我们拿到这个对象类;但是ActivityThread是一个隐藏类,我们需要用反射去获取
// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
  • 【步骤2:hook代理方式的确认】拿到这个currentActivityThread之后,我们需要修改它的mInstrumentation这个字段为我们的代理对象,我们先实现这个代理对象

    • 由于JDK动态代理只支持接口,而这个Instrumentation是一个类,没办法,我们只有手动写静态代理类,覆盖掉原始的方法即可。(cglib可以做到基于类的动态代理,这里先不介绍)
  public class EvilInstrumentation extends Instrumentation {private static final String TAG = "EvilInstrumentation";// ActivityThread中原始的对象, 保存起来Instrumentation mBase;public EvilInstrumentation(Instrumentation base) {mBase = base;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {// Hook之前, XXX到此一游!Log.d(TAG, "\n执行了startActivity, 参数如下: \n" + "who = [" + who + "], " +"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +"\ntarget = [" + target + "], \nintent = [" + intent +"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");// 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法try {Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);execStartActivity.setAccessible(true);return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options);} catch (Exception e) {// 某该死的rom修改了  需要手动适配throw new RuntimeException("do not support!!! pls adapt it");}}
}
  • 【步骤3:偷梁换柱】代码比较简单,采用反射直接修改:
public class HookHelper {public static void attachContext() throws Exception{// 先获取到当前的ActivityThread对象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");currentActivityThreadMethod.setAccessible(true);Object currentActivityThread = currentActivityThreadMethod.invoke(null);// 拿到原始的 mInstrumentation字段Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);// 创建代理对象Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);// 偷梁换柱mInstrumentationField.set(currentActivityThread, evilInstrumentation);}
}

4.1.1 示例

  1. 在attachBaseContext中触发预处理,初始化hook底层库
  2. 正常启动一个Activity
public class TestActivity extends Activity {@Overrideprotected void attachBaseContext(Context newBase) {super.attachBaseContext(newBase);try {// 在这里进行HookHookHelper.attachContext();} catch (Exception e) {e.printStackTrace();}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// TODO: 16/1/28 支持Activity直接跳转请在这里Hook// 家庭作业,留给读者完成.Button tv = new Button(this);tv.setText("测试界面");setContentView(tv);tv.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setData(Uri.parse("http://www.baidu.com"));// 注意这里使用的ApplicationContext 启动的Activity// 因为Activity对象的startActivity使用的并不是ContextImpl的mInstrumentation// 而是自己的mInstrumentation, 如果你需要这样, 可以自己Hook// 比较简单, 直接替换这个Activity的此字段即可.getApplicationContext().startActivity(intent);}});}}

4.2 伪注册启动的实现: 基于IActivityManager和Handle.mcallback的实现

understand-plugin-framework/intercept-activity/

阅读源码我们可以得知:

  • ActivityStackSupervisor这个类存在于远程服务,主要是对Activity栈进行管理,各种权限验证,manifest是否注册了该Activity了等

    • startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等.启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的
  • ActivityThread存在在应用进程,它的Handler最终启动了 对应Acitivity:new实例,并触发生命周期

既然AndroidManifest文件中必须声明,那么我就声明一个(或者有限个)替身Activity好了,当需要启动插件的某个Activity的时候,先让系统以为启动的是AndroidManifest中声明的那个替身,暂时骗过系统ActivityStackSupervisor;然后到合适的时候(回到ActivityThread)又替换回我们需要启动的真正的Activity;所谓瞒天过海,莫过如此!

那么,在启动PlugActivity(未在manifer注册)时:

  1. hook ActivityStackSupervisor实现:在ActivityStackSupervisor的校验注册环节,把PlugActivity替换为已注册HasRegActivity,实现绕过

    • 实现层面,是在进入ActivityStackSupervisor前,也就是IActivityManager发起远程请求时就替换
  2. hook ActivityThread的Handler实现:在收到启动HasRegActivity的信号时,反射替换为启动 PlugActivity,从而具备一样的生命周期

4.2.1 hook了ActivityManagerNative.getDefaule返回的IActivityManager去重写startActivity()函数,调包ActivityStackSupervisor的校验注册环节

Activity的启动是要经过AMS进行验证的,要判断其是否在manifest里注册过。所以,我们可以事先在manifest里注册一个备用的HasRegActivity,然后在应用进程传递给AMS信息是把里面的Intent掉包,把启动的插件PlugActivity信息替换为manifest里的HasRegActivity,欺骗AMS。

具体的实现过程如下:

因为在应用进程本地启动Activity最终是调用ActivityManagerNative里的gDefault里的IActivityManager对象的startActivity方法来把信息交给AMS的

那我们可以把这个IActivityManager对象给hook掉,替换为我们自己的代理对象,然后修改startActivity方法

public class MainActivity extends Activity {@Overrideprotected void attachBaseContext(Context newBase) {super.attachBaseContext(newBase);try {AMSHookHelper.hookActivityManagerNative();AMSHookHelper.hookActivityThreadHandler();} catch (Throwable throwable) {throw new RuntimeException("hook failed", throwable);}}public void onButtonClick(){//【1】启动ActivityIntent t = new Intent();  t.setComponent(new ComponentName("com.test.plugin", "com.test.plugin.PlugActivity"));  startActivity(t); }}
public class AMSHookHelper {/*** Hook AMS* <p/>* 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity"* <p/>* 进而骗过AMS*/public static void hookActivityManagerNative() throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException,IllegalAccessException, NoSuchFieldException {//【2.获取gDefault对象  】获取ActivityThreadNative里的gDefault对象,创建一个IActivityManager代理,并替换掉原来的IActivityManager对象Field gDefaultField =null;if (Build.VERSION.SDK_INT >= 26) {Class<?> activityManager = Class.forName("android.app.ActivityManager");gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");}else{Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");}gDefaultField.setAccessible(true);  //4.x以上的gDefault是一个 android.util.Singleton对象;我们取出这个单例里面的字段,获取IActivityManager对象Object gDefaultObj = gDefaultField.get(null);  Class<?> singleton = Class.forName("android.util.Singleton");  Field mInstance = singleton.getDeclaredField("mInstance");  mInstance.setAccessible(true);ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象Object rawIActivityManager = mInstance.get(gDefaultObj);  //使用反射里的代理模式创建我们的IActivityManager对象,更多Proxy信息请自行查询Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");  Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),  new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));  //替换掉应用本身的IActivityManager为我们自己的对象mInstance.set(gDefaultObj, proxy); }
}

其中,我们自己定义的IActivityManagerHandler如下,主要就是替换为HasRegActivity:

//【2.1】代理IActivityManagerHandler里处理startActivity方法
class IActivityManagerHandler implements InvocationHandler {  Object mBase;  public IActivityManagerHandler(Object base) {  mBase = base;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  if ("startActivity".equals(method.getName())) {  // 只拦截这个方法// 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱// API 23:// public final Activity startActivityNow(Activity parent, String id,// Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,// Activity.NonConfigurationInstances lastNonConfigurationInstances) {// 找到参数里面的第一个Intent 对象Intent raw;  int index = 0;  for (int i = 0; i < args.length; i++) {  if (args[i] instanceof Intent) {  index = i;  break;  }  }  //获取原来的Intentraw = (Intent) args[index];  //构造新的IntentIntent newIntent = new Intent();  String stubPackage = "主app应用包名";  ComponentName componentName = new ComponentName(stubPackage, HasRegActivity.class.getName());  newIntent.setComponent(componentName);  //【核心】保存原来的Intent,后面恢复要用newIntent.putExtra("rawIntent", raw);  args[index] = newIntent;  return method.invoke(mBase, args);  }  return method.invoke(mBase, args);  }
}  

4.2.2 hook了ActivityThread.handler的mCallBack恢复ActivityThread.handler启动PlugActivity

上面已经成功把要启动的Activity掉包了,接下来就是在AMS里进行各种处理验证,而后又返回到应用本地进程来了,我们这时候要把真正要启动的Activity恢复过来

经过上面Activity的启动流程分析,这里会到达ActivityThread里来,然后发送一个异步消息给Handler,我们可以hook掉该Handler,修改它里面启动Activity的方法,把真正要启动的Activity换回去。通过下面的掉包,系统会认为这个Activity是合法的,所以其生命周期也就会由系统帮我们回调了。

  • 获取ActivityThread对象,再获取它里面的Handler对象,然后修改其处理启动Activity的方法。
public class AMSHookHelper {public static final String EXTRA_TARGET_INTENT = "extra_target_intent";/*** 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity* <p/>* 不然就真的启动替身了, 狸猫换太子...* <p/>* 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成* H 会完成这个消息转发; 最终调用它的callback*/public static void hookActivityThreadHandler() throws Exception {//获取ActivityThread对象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");  Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");  currentActivityThreadMethod.setAccessible(true);  Object currentActivityThread = currentActivityThreadMethod.invoke(null);  //获取Handler(mH)对象:由于ActivityThread一个进程只有一个,我们获取这个对象的mHField mHField = activityThreadClass.getDeclaredField("mH");  mHField.setAccessible(true);  Handler mH = (Handler) mHField.get(currentActivityThread);  //修改Handler里面处理启动Activity的方法Field mCallBackField = Handler.class.getDeclaredField("mCallback");  mCallBackField.setAccessible(true);  mCallBackField.set(mH, new ActivityThreadHandlerCallback(mH));  }
}
  • ActivityThreadHandlerCallback类里面修改了Handler处理启动Activity方法handleLaunchActivity先执行我们的替换方法,在执行原生方法

    • 在宿主apk里能加载到数据Activity类,还需要hook应用类加载器ClassLoader, 后文会讲到
class ActivityThreadHandlerCallback implements Handler.Callback {  Handler mBase;  public ActivityThreadHandlerCallback(Handler base) {  mBase = base;  }  @Override  public boolean handleMessage(Message msg) {  switch (msg.what) {  // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100case 100:  //仅是修改Intent的信息handleLaunchActivity(msg);  break;  }  //【注意】此处还是会触发原来的 真正跳转 handleLaunchActivity--performLaunchActivitymBase.handleMessage(msg);          return true;  }  private void handleLaunchActivity(Message msg) {  // 这里简单起见,直接取出TargetActivity;Object obj = msg.obj; try {  Field intent = obj.getClass().getDeclaredField("intent");  intent.setAccessible(true);  Intent raw = (Intent) intent.get(obj);  Intent target = raw.getParcelableExtra("rawIntent");  //恢复原来启动的插件Activityraw.setComponent(target.getComponent());  Field activityInfoField = obj.getClass().getDeclaredField("activityInfo");  activityInfoField.setAccessible(true);  // 根据 getPackageInfo 根据这个 包名获取 LoadedApk的信息; 因此这里我们需要手动填上, 从而能够命中缓存ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(obj);  activityInfo.applicationInfo.packageName = target.getPackage() == null ?  target.getComponent().getPackageName() : target.getPackage();//还需要hook掉PMS。//因为PMS里面会验证启动类的包名是否是安装过的,如果没安装会报错,而我们插件里的Activity这些类都是没安装过的,所以要hook掉它//具体分析见后文hookPackageManager();  } catch (Exception e) {  throw new RuntimeException("hook launch activity failed", e);  }
}            

4.2.5 其他生命周期的影响

插件TargetActivity确实启动了,但是它有生命周期吗?

也就是说我们在实现了 AMSNative启动时替换,ActivityThread创建时恢复后, 针对PlugActivity的其他生命周期管理,譬如onPause,onStop会受影响么?AMS能自动找到真正的 PlugActivity么?

答案当然是能的,我们以finish为例来看看:

public void finish() {finish(DONT_FINISH_TASK_WITH_ACTIVITY);}
private void finish(int finishTask) {if (mParent == null) {int resultCode;Intent resultData;synchronized (this) {resultCode = mResultCode;resultData = mResultData;}if (false) Log.v(TAG, "Finishing self: token=" + mToken);try {if (resultData != null) {resultData.prepareToLeaveProcess(this);}if (ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)) {//1mFinished = true;}} catch (RemoteException e) {// Empty}} else {mParent.finishFromChild(this);}}

finish方法的调用链和Activity的启动过程是类似的,注释1处会调用的AMS的finishActivity方法,接着是AMS通过ApplicationThread调用ActivityThread,ActivityThread向H发送DESTROY_ACTIVITY类型的消息,H接收到这个消息会执行handleDestroyActivity方法,handleDestroyActivity方法又调用了performDestroyActivity方法,如下所示。

private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,int configChanges, boolean getNonConfigInstance) {ActivityClientRecord r = mActivities.get(token);//1...try {r.activity.mCalled = false;mInstrumentation.callActivityOnDestroy(r.activity);//2...} catch (SuperNotCalledException e) {...}}mActivities.remove(token);StrictMode.decrementExpectedActivityCount(activityClass);return r;
  1. 注释1处通过IBinder类型的token来获取ActivityClientRecord,ActivityClientRecord用于描述应用进程中的Activity。
  2. 在注释2处调用Instrumentation的callActivityOnDestroy方法来调用Activity的OnDestroy方法,并传入了r.activity。在注释2处调用Instrumentation的callActivityOnDestroy方法来调用Activity的OnDestroy方法,并传入了r.activity。

我们看在去看这个token对应的数据是什么时候埋进去的?

前面的例子我们用HasRegActivity替换了TargetActivity通过了AMS的校验,这样AMS只知道HasRegActivity的存在,那么AMS是如何能控制TargetActivity生命周期的回调的?我们接着往下看,启动Activity时会调用ActivityThread的performLaunchActivity方法,如下所示。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...java.lang.ClassLoader cl = appContext.getClassLoader();activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);//1...activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback);...mActivities.put(r.token, r);//2...return activity;}
  1. 注释1处根据Activity的类名用ClassLoader加载Acitivty,接着调用Activity的attach方法,将r.token赋值给Activity的成员变量mToken。
  2. 在注释2处将ActivityClientRecord根据r.token存在mActivities中(mActivities类型为ArrayMap<IBinder, ActivityClientRecord> )在注释2处将ActivityClientRecord根据r.token存在mActivities中(mActivities类型为ArrayMap<IBinder, ActivityClientRecord> )

再结合Activity的finish方法的注释1处,可以得出结论:AMS和ActivityThread之间的通信采用了token来对Activity进行标识,并且此后的Activity的生命周期处理也是根据token来对Activity进行标识的。

回到我们这个例子来,我们在Hook ActivityThread#Handler时用正确的插件TargetActivity替换占坑HasRegActivity,这一过程在performLaunchActivity方法调用之前,因此注释2处的r.token指向的是TargetActivity,在performDestroyActivity的注释1处获取的就是代表TargetActivity的ActivityClientRecord,可见TargetActivity是具有生命周期的。

4.2.6 使用示例

  1. attac hBaseContext 初始化,hook底层源码hBaseContext 初始化,hook底层源码
  2. 正常触发

public class MainActivity extends Activity {

@Override
protected void attachBaseContext(Context newBase) {super.attachBaseContext(newBase);try {AMSHookHelper.hookActivityManagerNative();AMSHookHelper.hookActivityThreadHandler();} catch (Throwable throwable) {throw new RuntimeException("hook failed", throwable);}
}@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button button = new Button(this);button.setText("启动TargetActivity");button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 启动目标Activity; 注意这个Activity是没有在AndroidManifest.xml中显式声明的// 但是调用者并不需要知道, 就像一个普通的Activity一样startActivity(new Intent(MainActivity.this, PluginActivity.class));}});setContentView(button);}

}

其中:

  • PluginActivity

    • 这个Activity并没有在Manifest中注册
  • HasRegActivity
    • 这个Activity在Manifest中注册
    • 当本地进程启动这个Activity的时候, 我们会把它拦截;然后跳转到PluginActivity

4.2.7 注意

每启动一个插件的Activity都需要一个 HasRegActivity,但是AndroidManifest.xml中肯定只能声明有限个,如果一直startActivity而不finish的话,那么理论上就需要无限个HasRegActivity;

这个问题该如何解决呢?

事实上,这个问题在技术上没有好的解决办法。但是,如果你的App startActivity了十几次,而没有finish任何一个Activity,这样在Activity的回退栈里面有十几个Activity,用户难道按back十几次回到主页吗?有这种需求说明你的产品设计有问题;一个App一级页面,二级页面…到五六级的页面已经影响体验了,所以,每种LauchMode声明十个StubActivity绝对能满足需求了

4.3 伪注册中:加载插件中的PlugActivity

understand-plugin-framework/classloader-hook/

在上文所述例子中,我们hook了ActivityThread.handler的mCallBack#launcheActivity
恢复ActivityThread.handler启动PlugActivity,如果PluginActivity与HasRegActivity存在于同一个Apk,因此系统的ClassLoader能够成功加载并创建PluginActivity的实例

但是在实际的插件系统中,要启动的目标Activity肯定存在于一个单独的文件中,系统默认的ClassLoader无法加载插件中的Activity类——系统压根儿就不知道要加载的插件在哪,这就需要到 DexClassLoader

我们从ActivityThread的handler接收到Launch Message后,开始分析

  • handler在接收到信息后:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  • 在真正的handleLaunchActivity中,内部有一句非常重要:
Activity a = performLaunchActivity(r, customIntent);
  • 这个方法做了两件很重要的事情:
//1. 使用ClassLoader加载并通过反射创建Activity对象
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);//2. 如果Application还没有创建,那么创建Application对象并回调相应的生命周期方法;
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {mInstrumentation.callActivityOnCreate(activity, r.state);
}

也就是说,系统通过ClassLoader加载了需要的Activity类并通过反射调用构造函数创建出了Activity对象。如果Activity组件存在于独立于宿主程序的文件之中,系统的ClassLoader怎么知道去哪里加载呢?

因此,如果不做额外的处理,插件中的Activity对象甚至都没有办法创建出来,谈何启动?

因此我们需要吧此处的 ClassLoader hook为我们自己的 ClassLoader

4.3.1 分析

  • cl这个ClasssLoader对象通过r.packageInfo对象的getClassLoader()方法得到,r.packageInfo是一个LoadedApk类的对象;

  • LoadedApk对象是APK文件在内存中的表示。 Apk文件的相关信息,诸如Apk文件的代码和资源,甚至代码里面的Activity,Service等组件的信息我们都可以通过此对象获取。

  • r是一个ActivityClientRecord对象,它的实例过程中可以看到 packageInfo的生成

final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);//
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,CompatibilityInfo compatInfo) {return getPackageInfo(ai, compatInfo, null, false, true, false);
}//
final ArrayMap<String, WeakReference<LoadedApk>> mPackages= new ArrayMap<String, WeakReference<LoadedApk>>();
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,ClassLoader baseLoader, boolean securityViolation, boolean includeCode,boolean registerPackage) {// 获取userid信息final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));synchronized (mResourcesManager) {// 【核心】尝试获取缓存信息WeakReference<LoadedApk> ref;if (differentUser) {// Caching not supported across usersref = null;} else if (includeCode) {ref = mPackages.get(aInfo.packageName);} else {ref = mResourcePackages.get(aInfo.packageName);}//【核心:激进方案】缓存是否命中LoadedApk packageInfo = ref != null ? ref.get() : null;if (packageInfo == null || (packageInfo.mResources != null&& !packageInfo.mResources.getAssets().isUpToDate())) {// 【核心:保守方案】缓存没有命中,直接newpackageInfo =new LoadedApk(this, aInfo, compatInfo, baseLoader,securityViolation, includeCode &&(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);// 省略。。更新缓存return packageInfo;}
}
  • getPackageInfo方法中,设置了classLoader

    • 判断了调用方和或许App信息的一方是不是同一个userId;如果是同一个user,那么可以共享缓存数据(要么缓存的代码数据,要么缓存的资源数据)
    • 接下来尝试获取缓存数据;
    • 如果没有命中缓存数据,才通过LoadedApk的构造函数创建了LoadedApk对象;创建成功之后,如果是同一个uid还放入了缓存

那么我们有两种方案:

  • 『激进方案』:借助缓存

    1. 我们自定义实例化一个LoadedApk(持有自定义的ClassLoader)
    2. 加入ActivityThread的mPackages缓存,而被系统命中;
  • 『保守方案』:由于构建LoadedApk时的aInfo是宿主程序的Application信息,最终引发用宿主的ClasLoader加载相关class,让宿主的ClasLoader获得加载插件类的能力

从而达到我们使用自己的ClassLoader加载插件中的类的目的

4.3.2 激进方案:借助mPackages对象Hook掉ClassLoader,自己操刀

从上述分析中我们得知,在获取LoadedApk的过程中使用了一份缓存数据mPackages.get(aInfo.packageName);;这个缓存数据是一个Map,从包名到LoadedApk的一个映射。

通常情况下,我们的插件肯定不会存在于这个对象里面;但是如果我们手动把我们插件的信息添加到里面呢?

系统在查找缓存的过程中,会直接命中缓存!进而使用我们添加进去的LoadedApk的ClassLoader来加载这个特定的Activity类!这样我们就能接管我们自己插件类的加载过程了!

  • 【1】这个缓存对象mPackages存在于ActivityThread类中;老方法,我们首先获取这个对象:
public class LoadedApkClassLoaderHookHelper {public static Map<String, Object> sLoadedApk = new HashMap<String, Object>();public static void hookLoadedApkInActivityThread(File apkFile) throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {// 先获取到当前的ActivityThread对象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");currentActivityThreadMethod.setAccessible(true);Object currentActivityThread = currentActivityThreadMethod.invoke(null);// 获取到 mPackages 这个静态成员变量, 这里缓存了dex包的信息Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");mPackagesField.setAccessible(true);Map mPackages = (Map) mPackagesField.get(currentActivityThread);} }
  • 【2】用包名:自定义LoadedApk对象,填充这个map

    • 我们需要使用与系统完全相同的方式创建LoadedApk对象,以保证对象使用起来和系统一样
    • 从上文分析得知,系统创建LoadedApk对象是通过getPackageInfo来完成的,因此我们可以调用这个函数来创建LoadedApk对象
    • 但是这个函数是private的,表明这个函数是内部实现,如果有反射的话,不能保证不同版本的稳定性
    • 间接调用getPackageInfo这个私有函数的public函数有同名的getPackageInfo系列和getPackageInfoNoCheck;简单查看源代码发现,getPackageInfo除了获取包的信息,还检查了包的一些组件;
    • 为了绕过这些验证,我们选择使用getPackageInfoNoCheck获取LoadedApk信息
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,CompatibilityInfo compatInfo) {
  • 【2.1】为了调用这个函数构建LoaderApk对象,我们需要构造两个参数

    • 其一是ApplicationInfo
    • 其二是CompatibilityInfo;第二个参数顾名思义,代表这个App的兼容性信息,比如targetSDK版本等等,这里我们只需要提取出app的信息,因此直接使用默认的兼容性即可;在CompatibilityInfo类里面有一个公有字段DEFAULT_COMPATIBILITY_INFO代表默认兼容性信息;
public class LoadedApkClassLoaderHookHelper {public static Map<String, Object> sLoadedApk = new HashMap<String, Object>();public static void hookLoadedApkInActivityThread(File apkFile) throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {// 参数2:android.content.res.CompatibilityInfoClass<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");defaultCompatibilityInfoField.setAccessible(true);Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);//参数1的构建,具体见下文:ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);//反射构建 LoadApkMethod getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);}
}
  • 【3】我们成功地构造出了LoadedAPK, 接下来我们需要替换其中的ClassLoader,然后把它添加进ActivityThread的mPackages中:
public class LoadedApkClassLoaderHookHelper {public static Map<String, Object> sLoadedApk = new HashMap<String, Object>();public static void hookLoadedApkInActivityThread(File apkFile) throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath();String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath();//此处构建自己的ClassLaoderClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader());Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader");mClassLoaderField.setAccessible(true);mClassLoaderField.set(loadedApk, classLoader);// 由于是弱引用, 因此我们必须在某个地方存一份, 不然容易被GC; 那么就前功尽弃了.sLoadedApk.put(applicationInfo.packageName, loadedApk);WeakReference weakReference = new WeakReference(loadedApk);mPackages.put(applicationInfo.packageName, weakReference);}
}

到这里,我们已经成功地把把插件的信息放入ActivityThread中,这样我们插件中的类能够成功地被加载;因此插件中的Activity实例能被成功第创建;由于整个流程较为复杂,我们简单梳理一下:

  1. 在ActivityThread接收到IApplication的 scheduleLaunchActivity远程调用之后,将消息转发给H
  2. H类在handleMessage的时候,调用了getPackageInfoNoCheck方法来获取待启动的组件信息。
    • 在这个方法中会优先查找mPackages中的缓存信息,而我们已经手动把插件信息添加进去;因此能够成功命中缓存,获取到独立存在的插件信息。
  3. H类然后调用handleLaunchActivity最终转发到performLaunchActivity方法;
    • 这个方法使用从getPackageInfoNoCheck中拿到LoadedApk中的mClassLoader来加载Activity类
    • 进而使用反射创建Activity实例;
    • 接着创建Application,Context等完成Activity组件的启动。

4.3.2.1 构建插件ApplicationInfo信息

我们首先看看ApplicationInfo代表什么,这个类的文档说的很清楚:

Information you can retrieve about a particular application. This corresponds to information collected from the AndroidManifest.xml’s tag.

也就是说,这个类就是AndroidManifest.xml里面的 这个标签下面的信息;这个AndroidManifest.xml无疑是一个标准的xml文件,因此我们完全可以自己使用parse来解析这个信息。

那么,系统是如何获取这个信息的呢?其实Framework就有一个这样的parser,也即PackageParser;理论上,我们也可以借用系统的parser来解析AndroidMAnifest.xml从而得到ApplicationInfo的信息。但遗憾的是,这个类的兼容性很差;

Google几乎在每一个Android版本都对这个类动刀子,如果坚持使用系统的解析方式,必须写一系列兼容行代码!!DroidPlugin就选择了这种方式,相关类如下:


【图DroidPlugin的PackageParser】

这也是我们之前提到的私有或者隐藏的API可以使用,但必须处理好兼容性问题

public static ApplicationInfo generateApplicationInfo(Package p, int flags,PackageUserState state)

我们以使用API 23作为演示,版本不同的可能无法运行请自行查阅 DroidPlugin 不同版本如何处理。

  • 调用generateApplicationInfo的反射代码:
public class LoadedApkClassLoaderHookHelper {/*** 这个方法的最终目的是调用* 我们的终极目标:android.content.pm.PackageParser#generateActivityInfo(android.content.pm.PackageParser.Activity, int, android.content.pm.PackageUserState, int)*/public static ApplicationInfo generateApplicationInfo(File apkFile)throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {// 找出需要反射的核心类: android.content.pm.PackageParserClass<?> packageParserClass = Class.forName("android.content.pm.PackageParser");// 首先拿到我们得终极目标: generateApplicationInfo方法// API 23 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!// public static ApplicationInfo generateApplicationInfo(Package p, int flags,//    PackageUserState state) {// 其他Android版本不保证也是如此.Class<?> packageParser$PackageClass = Class.forName("android.content.pm.PackageParser$Package");Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod("generateApplicationInfo",packageParser$PackageClass,int.class,packageUserStateClass);}}    
  • 需要三个参数实例

    • 第一个参数是PackageParser.Package
      这个类代表从PackageParser中解析得到的某个apk包的信息,是磁盘上apk文件在内存中的数据结构表示;因此,要获取这个类,肯定需要解析整个apk文件。PackageParser中解析apk的核心方法是parsePackage,这个方法返回的就是一个Package类型的实例,因此我们调用这个方法即可;
    • 第二个参数是解析包使用的flag
      我们直接选择解析全部信息,也就是0;
    • 第三个参数是PackageUserState
      代表不同用户中包的信息。由于Android是一个多任务多用户系统,因此不同的用户同一个包可能有不同的状态;这里我们只需要获取包的信息,因此直接使用默认的即可;
public class LoadedApkClassLoaderHookHelper {/*** 这个方法的最终目的是调用* 我们的终极目标:android.content.pm.PackageParser#generateActivityInfo(android.content.pm.PackageParser.Activity, int, android.content.pm.PackageUserState, int)*/public static ApplicationInfo generateApplicationInfo(File apkFile)throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {//第一个参数PackageParser.Package// 首先, 我们得创建出一个Package对象出来供这个方法调用// 而这个需要得对象可以通过 android.content.pm.PackageParser#parsePackage 这个方法返回得 Package对象得字段获取得到// 创建出一个PackageParser对象供使用Object packageParser = packageParserClass.newInstance();// 调用 PackageParser.parsePackage 解析apk的信息Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);// 实际上是一个 android.content.pm.PackageParser.Package 对象Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0);// 第三个参数 mDefaultPackageUserState 我们直接使用默认构造函数构造一个出来即可Object defaultPackageUserState = packageUserStateClass.newInstance();}
}
  • 构建插件ApplicationInfo信息
public class LoadedApkClassLoaderHookHelper {/*** 这个方法的最终目的是调用* 我们的终极目标:android.content.pm.PackageParser#generateActivityInfo(android.content.pm.PackageParser.Activity, int, android.content.pm.PackageUserState, int)*/public static ApplicationInfo generateApplicationInfo(File apkFile)throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {// 万事具备!!!!!!!!!!!!!!ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser,packageObj, 0, defaultPackageUserState);String apkPath = apkFile.getPath();applicationInfo.sourceDir = apkPath;applicationInfo.publicSourceDir = apkPath;}
}

4.3.2.2 构建CompatibilityInfo

参数CompatibilityInfo代表设备兼容性信息,直接使用默认的值即可

4.3.3 保守方案:委托系统,让系统帮忙加载

再次搬出ActivityThread中加载Activity类的代码

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);

我们知道r.packageInfo是通过getPackageInfoNoCheck获取到的;

  • getPackageInfo方法中

    • 在『激进方案』中我们自定义一个LoadedApk(持有自定义的ClassLoader),加入缓存,而被系统命中;
    • 若果不使用缓存,则会触发:
if (packageInfo == null || (packageInfo.mResources != null&& !packageInfo.mResources.getAssets().isUpToDate())) {packageInfo =new LoadedApk(this, aInfo, compatInfo, baseLoader,securityViolation, includeCode &&(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);// 略
}

可以看到,没有命中缓存的情况下,系统直接new了一个LoadedApk;

注意这个构造函数的第二个参数aInfo,这是一个ApplicationInfo类型的对象,这是一个ApplicationInfo类型的对象。在『激进方案』中我们为了获取独立插件的ApplicationInfo花了不少心思;那么如果不做任何处理这里传入的这个aInfo参数是什么?

追本溯源不难发现,这个aInfo是从我们的替身HasRegActivity中获取的!而HasRegActivity存在于宿主程序中,所以,这个aInfo对象代表的实际上就是宿主程序的Application信息!

我们知道,接下来会使用new出来的这个LoadedApk的getClassLoader()方法获取到ClassLoader来对插件的类进行加载;而获取到的这个ClassLoader是宿主程序使用的ClassLoader,因此现在还无法加载插件的类;

那么,我们能不能让宿主的ClasLoader获得加载插件类的能力呢?如果我们告诉宿主使用的ClassLoader插件使用的类在哪里,就能帮助他完成加载!

  1. 默认情况下performLacunchActivity会使用替身HasRegActivity的ApplicationInfo也就是宿主程序的CLassLoader加载所有的类;我们的思路是告诉宿主ClassLoader我们的插件在哪,让其帮助完成类加载的过程。
  2. 宿主程序的ClassLoader最终继承自BaseDexClassLoader,BaseDexClassLoader通过DexPathList进行类的查找过程;而这个查找通过遍历一个dexElements的数组完成;
    • 我们通过把插件dex添加进这个数组就让宿主ClasLoader获取了加载插件类的能力。

4.3.3.1 宿主的ClassLoader在哪里,是唯一的吗?

上面说到,我们可以通过告诉宿主程序的ClassLoader插件使用的类,让宿主的ClasLoader完成对于插件类的加载;那么问题来了,我们如何获取到宿主的ClassLoader?宿主程序使用的ClasLoader默认情况下是全局唯一的吗?

答案是肯定的。

因为在FrameWork中宿主程序也是使用LoadedApk表示的,如同Activity启动是加载Activity类一样,宿主中的类也都是通过LoadedApk的getClassLoader()方法得到的ClassLoader加载的;由类加载机制的『双亲委派』特性,只要有一个应用程序类由某一个ClassLoader加载,那么它引用到的别的类除非父加载器能加载,否则都是由这同一个加载器加载的(不遵循双亲委派模型的除外)。

表示宿主的LoadedApk在Application类中有一个成员变量mLoadedApk,而这个变量是从ContextImpl中获取的;

ContextImpl重写了getClassLoader方法,因此我们在Context环境中直接getClassLoader()获取到的就是宿主程序唯一的ClassLoader。

不论是宿主程序还是插件程序都是通过LoadedApk的getClassLoader()方法返回的ClassLoader进行类加载的,返回的这个ClassLoader到底是个什么东西??这个方法源码如下:

public ClassLoader getClassLoader() {synchronized (this) {if (mClassLoader != null) {return mClassLoader;}if (mIncludeCode && !mPackageName.equals("android")) {// 略...mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,mBaseClassLoader);StrictMode.setThreadPolicy(oldPolicy);} else {if (mBaseClassLoader == null) {mClassLoader = ClassLoader.getSystemClassLoader();} else {mClassLoader = mBaseClassLoader;}}return mClassLoader;}
}//可以看到,非android开头的包和android开头的包分别使用了两种不同的ClassLoader
//我们只关心第一种;因此继续跟踪ApplicationLoaders类:
public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
{ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();synchronized (mLoaders) {if (parent == null) {parent = baseParent;}if (parent == baseParent) {ClassLoader loader = mLoaders.get(zip);if (loader != null) {return loader;}Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);PathClassLoader pathClassloader =new PathClassLoader(zip, libPath, parent);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);mLoaders.put(zip, pathClassloader);return pathClassloader;}Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);PathClassLoader pathClassloader = new PathClassLoader(zip, parent);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);return pathClassloader;}
}

可以看到,应用程序使用的ClassLoader都是PathClassLoader类的实例, 它是通过内部的 pathList去实现查找class的过程

这个DexPathList内部有一个叫做dexElements的数组,然后findClass的时候会遍历这个数组来查找Class;

如果我们把插件的信息塞进这个数组里面,那么不就能够完成类的加载过程吗?!!

4.3.3.2.给默认PathClassLoader打补丁

通过上述分析,我们知道,可以把插件的相关信息放入BaseDexClassLoader的表示dex文件的数组里面,这样宿主程序的ClassLoader在进行类加载,遍历这个数组的时候,会自动遍历到我们添加进去的插件信息,从而完成插件类的加载!

public final class BaseDexClassLoaderHookHelper {public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {// 获取 BaseDexClassLoader : pathListField pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");pathListField.setAccessible(true);Object pathListObj = pathListField.get(cl);// 获取 PathList: Element[] dexElementsField dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");dexElementArray.setAccessible(true);Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);// Element 类型Class<?> elementClass = dexElements.getClass().getComponentType();// 创建一个数组, 用来替换原始的数组Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);// 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));Object[] toAddElementArray = new Object[] { o };// 把原始的elements复制进去System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);// 插件的那个element复制进去System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);// 替换dexElementArray.set(pathListObj, newElements);}
}

短短的二十几行代码,我们就完成了『委托宿主ClassLoader加载插件类』的任务;

4.3.3.3 示例

  1. attachBaseContext中hook底层源码,实现初始化
  2. 正常启动一个Activity
public class MainActivity extends Activity {private static final String TAG = "MainActivity";private static final int PATCH_BASE_CLASS_LOADER = 1;private static final int CUSTOM_CLASS_LOADER = 2;private static final int HOOK_METHOD = CUSTOM_CLASS_LOADER;@Overrideprotected void attachBaseContext(Context newBase) {super.attachBaseContext(newBase);try {Utils.extractAssets(newBase, "dynamic-proxy-hook.apk");Utils.extractAssets(newBase, "ams-pms-hook.apk");Utils.extractAssets(newBase, "test.apk");if (HOOK_METHOD == PATCH_BASE_CLASS_LOADER) {File dexFile = getFileStreamPath("test.apk");File optDexFile = getFileStreamPath("test.dex");BaseDexClassLoaderHookHelper.patchClassLoader(getClassLoader(), dexFile, optDexFile);} else {LoadedApkClassLoaderHookHelper.hookLoadedApkInActivityThread(getFileStreamPath("ams-pms-hook.apk"));}AMSHookHelper.hookActivityManagerNative();AMSHookHelper.hookActivityThreadHandler();} catch (Throwable e) {e.printStackTrace();}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button t = new Button(this);t.setText("test button");setContentView(t);Log.d(TAG, "context classloader: " + getApplicationContext().getClassLoader());t.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {try {Intent t = new Intent();if (HOOK_METHOD == PATCH_BASE_CLASS_LOADER) {t.setComponent(new ComponentName("com.weishu.upf.dynamic_proxy_hook.app2","com.weishu.upf.dynamic_proxy_hook.app2.MainActivity"));} else {t.setComponent(new ComponentName("com.weishu.upf.ams_pms_hook.app","com.weishu.upf.ams_pms_hook.app.MainActivity"));}startActivity(t);} catch (Throwable e) {e.printStackTrace();}}});}}

4.4 伪注册启动时:绕过 PMS校验

understand-plugin-framework/classloader-hook/

在完成上述步骤后,运行一下会出现一个异常,如下:

  • 错误提示说是无法实例化 Application,而Application的创建也是在performLaunchActivity中进行的
04-05 02:49:53.742  11759-11759/com.weishu.upf.hook_classloader E/AndroidRuntime﹕ FATAL EXCEPTION: mainProcess: com.weishu.upf.hook_classloader, PID: 11759java.lang.RuntimeException: Unable to start activity ComponentInfo{com.weishu.upf.ams_pms_hook.app/com.weishu.upf.ams_pms_hook.app.MainActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.weishu.upf.ams_pms_hook.app; is package not installed?

因为PMS里面会验证启动类的包名是否是安装过的,如果没安装会报错,而我们插件里的Activity这些类都是没安装过的,所以要hook掉它

在handleLaunchActivity–performLaunchActivity中

 try {java.lang.ClassLoader cl = getClassLoader();//如果包名不是android开头,那么调用了一个叫做initializeJavaContextClassLoader的方法;if (!mPackageName.equals("android")) {initializeJavaContextClassLoader();}ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);appContext.setOuterContext(app);} catch (Exception e) {if (!mActivityThread.mInstrumentation.onException(app, e)) {throw new RuntimeException("Unable to instantiate application " + appClass+ ": " + e.toString(), e);}}private void initializeJavaContextClassLoader() {IPackageManager pm = ActivityThread.getPackageManager();android.content.pm.PackageInfo pi;try {pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());} catch (RemoteException e) {throw new IllegalStateException("Unable to get package info for "+ mPackageName + "; is system dying?", e);}//这里调用了getPackageInfo方法获取包的信息;而我们的插件并没有安装在系统上,因此系统肯定认为插件没有安装,这个方法肯定返回null。if (pi == null) {throw new IllegalStateException("Unable to get package info for "+ mPackageName + "; is package not installed?");}boolean sharedUserIdSet = (pi.sharedUserId != null);boolean processNameNotDefault =(pi.applicationInfo != null &&!mPackageName.equals(pi.applicationInfo.processName));boolean sharable = (sharedUserIdSet || processNameNotDefault);ClassLoader contextClassLoader =(sharable)? new WarningContextClassLoader(): mClassLoader;Thread.currentThread().setContextClassLoader(contextClassLoader);}

所以,我们还要欺骗一下PMS,让系统觉得插件已经安装在系统上了;至于如何欺骗 PMS,Hook机制之AMS&PMS 有详细解释,这里直接给出代码,不赘述了:

public class AMSHookHelper {private static void hookPackageManager() throws Exception {// 这一步是因为 initializeJavaContextClassLoader 这个方法内部无意中检查了这个包是否在系统安装// 如果没有安装, 直接抛出异常, 这里需要临时Hook掉 PMS, 绕过这个检查.Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");currentActivityThreadMethod.setAccessible(true);Object currentActivityThread = currentActivityThreadMethod.invoke(null);// 获取ActivityThread里面原始的 sPackageManagerField sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");sPackageManagerField.setAccessible(true);Object sPackageManager = sPackageManagerField.get(currentActivityThread);// 准备好代理对象, 用来替换原始的对象Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),new Class<?>[] { iPackageManagerInterface },new IPackageManagerHookHandler(sPackageManager));// 1. 替换掉ActivityThread里面的 sPackageManager 字段sPackageManagerField.set(currentActivityThread, proxy);}
}public class IPackageManagerHookHandler implements InvocationHandler{  private Object mBase;  public IPackageManagerHookHandler(Object base) {  mBase = base;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  if (method.getName().equals("getPackageInfo")) {  return new PackageInfo();  }  return method.invoke(mBase, args);  }
}

4.5 伪注册启动的实现: 基于Hook Instrumentation的execStartActivity和newAcitivity方案实现

4.5.1 原理

  1. 在Activity通过AMS校验前,会调用Activity的startActivityForResult方法:

    • 在使用IAcivityManager之前
  2. ActivityThread启动Activity的过程中会调用ActivityThread的performLaunchActivity方法
    • 在使用handleMessage之后
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);...} else {...}}private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {       ...//创建要启动Activity的上下文环境ContextImpl appContext = createBaseContextForActivity(r);Activity activity = null;try {java.lang.ClassLoader cl = appContext.getClassLoader();//用类加载器来创建Activity的实例activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);//1...} catch (Exception e) {...}...return activity;}

看到这里我们可以得到方案,就是在Instrumentation的execStartActivity方法中用占坑SubActivity来通过AMS的验证,在Instrumentation的newActivity方法中还原TargetActivity,这两部操作都和Instrumentation有关,因此我们可以用自定义的Instrumentation来替换掉mInstrumentation

4.5.2 实现

  1. 声明一个(或者有限个)替身Activity
  2. 首先我们自定义一个Instrumentation,在execStartActivity方法中将启动的TargetActivity替换为SubActivity
    • 首先查找要启动的Activity是否已经在AndroidManifest.xml中注册了,如果没有就在注释1处将要启动的Activity(TargetActivity)的ClassName保存起来用于后面还原TargetActivity,接着在注释2处替换要启动的Activity为StubActivity,最后通过反射调用execStartActivity方法,这样就可以用StubActivity通过AMS的验证
  3. 在InstrumentationProxy 的newActivity方法还原TargetActivity
    • newActivity方法中创建了此前保存的TargetActivity,完成了还原TargetActivity。
  4. 编写hookInstrumentation方法,用InstrumentationProxy替换mInstrumentation:
    • 注释1处获取ContextImpl类的ActivityThread类型的mMainThread字段,注释2出获取当前上下文环境的ActivityThread对象。
    • 注释3出获取ActivityThread类中的mInstrumentation字段,最后用InstrumentationProxy来替换mInstrumentation。
  5. 在MyApplication的attachBaseContext方法中调用HookHelper的hookInstrumentation方法,运行程序,当我们点击启动插件按钮,发现启动的是插件TargetActivity。
public class InstrumentationProxy extends Instrumentation {private Instrumentation mInstrumentation;private PackageManager mPackageManager;public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {mInstrumentation = instrumentation;mPackageManager = packageManager;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);if (infos == null || infos.size() == 0) {intent.putExtra(HookHelper.TARGET_INTENsT_NAME, intent.getComponent().getClassName());//1intent.setClassName(who, "com.example.liuwangshu.pluginactivity.StubActivity");//2}try {Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,target, intent, requestCode, options);} catch (Exception e) {e.printStackTrace();}return null;}public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,IllegalAccessException, ClassNotFoundException {String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT_NAME);if (!TextUtils.isEmpty(intentName)) {return super.newActivity(cl, intentName, intent);}return super.newActivity(cl, className, intent);}
}public static void hookInstrumentation(Context context) throws Exception {Class<?> contextImplClass = Class.forName("android.app.ContextImpl");Field mMainThreadField  =FieldUtil.getField(contextImplClass,"mMainThread");//1Object activityThread = mMainThreadField.get(context);//2Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Field mInstrumentationField=FieldUtil.getField(activityThreadClass,"mInstrumentation");//3FieldUtil.setField(activityThreadClass,activityThread,"mInstrumentation",new InstrumentationProxy((Instrumentation) mInstrumentationField.get(activityThread),context.getPackageManager()));}

4.6 总结

本文中我们采用两种方案成功完成了『启动没有在AndroidManifest.xml中显示声明,并且存在于外部插件中的Activity』的任务

  • 『激进方案』中我们自定义了插件的ClassLoader,并且绕开了Framework的检测;利用ActivityThread对于LoadedApk的缓存机制,我们把携带这个自定义的ClassLoader的插件信息添加进mPackages中,进而完成了类的加载过程

  • 『保守方案』中我们深入探究了系统使用ClassLoader findClass的过程,发现应用程序使用的非系统类都是通过同一个PathClassLoader加载的;而这个类的最终父类BaseDexClassLoader通过DexPathList完成类的查找过程;我们hack了这个查找过程,从而完成了插件类的加载

这两种方案孰优孰劣呢?

  • 代码量和分析过程

    • 『激进方案』比较麻烦,这种机制异常复杂;而且在解析apk的时候我们使用的PackageParser的兼容性非常差,我们不得不手动处理每一个版本的apk解析api;另外,它Hook的地方也有点多:不仅需要Hook AMS和H,还需要Hook ActivityThread的mPackages和PackageManager!
    • 『保守方案』则简单得多(虽然原理也不简单),不仅代码很少,而且Hook的地方也不多;有一点正本清源的意思,从最最上层Hook住了整个类的加载过程。
  • ClassLoader构架

    • 『激进方案』是多ClassLoader构架,每一个插件都有一个自己的ClassLoader,因此类的隔离性非常好——如果不同的插件使用了同一个库的不同版本,它们相安无事
    • 『保守方案』是单ClassLoader方案,插件和宿主程序的类全部都通过宿主的ClasLoader加载,虽然代码简单,但是鲁棒性很差;一旦插件之间甚至插件与宿主之间使用的类库有冲突,那么直接GG。

多ClassLoader还有一个优点:可以真正完成代码的热加载!如果插件需要升级,直接重新创建一个自定的ClassLoader加载新的插件,然后替换掉原来的版本即可(Java中,不同ClassLoader加载的同一个类被认为是不同的类);单ClassLoader的话实现非常麻烦,有可能需要重启进程

在J2EE领域中广泛使用ClasLoader的地方均采用多ClassLoader架构,比如Tomcat服务器,Java模块化事实标准的OSGi技术;所以,我们有足够的理由认为选择多ClassLoader架构在大多数情况下是明智之举。

目前开源的插件方案中,**DroidPlugin采用的『激进方案』,Small采用的『保守方案』**那么,有没有两种优点兼顾的方案呢??

DroidPlugin和Small的共同点是两者都是非侵入式的插件框架;什么是『非侵入式』呢?打个比方,你启动一个插件Activity,直接使用startActivity即可,就跟开发普通的apk一样,开发插件和普通的程序对于开发者来说没有什么区别。

一定程度上放弃这种『侵入性』,那么我们就能实现一个两者优点兼而有之的插件框架

五、广播的处理方式

  • understand-plugin-framework/receiver-management/

相比Activity,BroadcastReceiver要简单很多,主要涉及:注册, 发送和接收

5.1 源码分析

5.1.1 注册过程

不论是静态广播还是动态广播,在使用之前都是需要注册的;动态广播的注册需要借助Context类的registerReceiver方法,而静态广播的注册直接在AndroidManifest.xml中声明即可

我们首先分析一下动态广播的注册过程。Context类的registerReceiver的真正实现在ContextImpl里面,而这个方法间接调用了registerReceiverInternal,源码如下:

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,IntentFilter filter, String broadcastPermission,Handler scheduler, Context context) {IIntentReceiver rd = null; // Important !!!!!if (receiver != null) {if (mPackageInfo != null && context != null) {if (scheduler == null) {scheduler = mMainThread.getHandler();}rd = mPackageInfo.getReceiverDispatcher(receiver, context, scheduler,mMainThread.getInstrumentation(), true);} else {if (scheduler == null) {scheduler = mMainThread.getHandler();}rd = new LoadedApk.ReceiverDispatcher(receiver, context, scheduler, null, true).getIIntentReceiver();}}try {return ActivityManagerNative.getDefault().registerReceiver(mMainThread.getApplicationThread(), mBasePackageName,rd, filter, broadcastPermission, userId);} catch (RemoteException e) {return null;}
}
  • 可以看到,BroadcastReceiver的注册也是通过AMS完成的

  • 在进入AMS跟踪它的registerReceiver方法之前,我们先弄清楚这个IIntentReceiver类型的变量rd是什么

    • 这个类是通过AIDL工具生成的,它是一个Binder对象,因此可以用来跨进程传输;
    • 它是用来进行广播分发的
    • 由于广播的分发过程是在AMS中进行的,而AMS所在的进程和BroadcastReceiver所在的进程不一样,因此要把广播分发到BroadcastReceiver具体的进程需要进行跨进程通信,这个通信的载体就是IIntentReceiver类。
    • 其实这个类的作用跟 Activity生命周期管理 中提到的 IApplicationThread相同,都是App进程给AMS进程用来进行通信的对象
    • IIntentReceiver是一个接口,从上述代码中可以看出,它的实现类为LoadedApk.ReceiverDispatcher

我们继续跟踪源码,AMS类的registerReceiver方法代码有点多,这个方法主要做了以下两件事:

  1. 对发送者的身份和权限做出一定的校检
  2. 把这个BroadcastReceiver以BroadcastFilter的形式存储在AMS的mReceiverResolver变量中,供后续使用。

就这样,被传递过来的BroadcastReceiver已经成功地注册在系统之中,能够接收特定类型的广播了;

那么注册在AndroidManifest.xml中的静态广播是如何被系统感知的呢?

在 插件加载机制 中我们知道系统会通过PackageParser解析Apk中的AndroidManifest.xml文件,因此我们有理由认为,系统会在解析AndroidMafest.xml的标签(也即静态注册的广播)的时候保存相应的信息;

而Apk的解析过程是在PMS中进行的,因此静态注册广播的信息存储在PMS中。接下来的分析会证实这一结论。

5.1.2 发送过程context#sendBroadcast

发送广播很简单,就是一句context.sendBroadcast(),我们顺藤摸瓜,跟踪这个方法。

前文也提到过,Context中方法的调用都会委托到ContextImpl这个类,我们直接看ContextImpl对这个方法的实现:

public void sendBroadcast(Intent intent) {warnIfCallingFromSystemProcess();String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());try {intent.prepareToLeaveProcess();ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), intent, resolvedType, null,Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,getUserId());} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}
}
  • 发送广播也是通过AMS进行的
  • AMS#broadcastIntentLocked这个方法相当长,处理了诸如粘性广播,顺序广播,各种Flag以及动态广播静态广播的接收过程,这些我们暂时不关心
    • 值得注意的是,在这个方法中我们发现:其实广播的发送和接收是融为一体的。某个广播被发送之后,AMS会找出所有注册过的BroadcastReceiver中与这个广播匹配的接收者,然后将这个广播分发给相应的接收者处理。

5.1.2.1 广播的匹配AMS#broadcastIntentLocked

某一条广播被发出之后,并不是阿猫阿狗都能接收它并处理的;BroadcastReceiver可能只对某些类型的广播感兴趣,因此它也只能接收和处理这种特定类型的广播

AMS#broadcastIntentLocked

  1. receivers是对这个广播感兴趣的静态BroadcastReceiver列表;

collectReceiverComponents 通过PackageManager获取了与这个广播匹配的静态BroadcastReceiver信息;
这里也证实了我们在分析BroadcasrReceiver注册过程中的推论——静态BroadcastReceiver的注册过程的确实在PMS中进行的。

  1. mReceiverResolver存储了动态注册的BroadcastReceiver的信息;

还记得这个mReceiverResolver吗?我们在分析动态广播的注册过程中发现,动态注册的BroadcastReceiver的相关信息最终存储在此对象之中;

在这里,通过mReceiverResolver对象匹配出了对应的BroadcastReceiver供进一步使用。

public final class ActivityManagerService extends ActivityManagerNativeimplements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver= new IntentResolver<BroadcastFilter, BroadcastFilter>() ;private final int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle map, String requiredPermission, int appOp,boolean ordered, boolean sticky, int callingPid, int callingUid,int userId) {...// Figure out who all will receive this broadcast.List receivers = null;List<BroadcastFilter> registeredReceivers = null;// Need to resolve the intent to interested receivers...if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)== 0) {//【注意此处】receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);}if (intent.getComponent() == null) {if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {// Query one target user at a time, excluding shell-restricted users// 略} else {//【注意此处】registeredReceivers = mReceiverResolver.queryIntent(intent,resolvedType, false, userId);}}}
}

现在系统通过PMS拿到了所有符合要求的静态BroadcastReceiver,然后从AMS中获取了符合要求的动态BroadcastReceiver;

因此接下来的工作非常简单:唤醒这些广播接受者。简单来说就是回调它们的onReceive方法

5.1.3 接收过程AMS#broadcastIntentLocked

通过上文的分析过程我们知道,在AMS的broadcastIntentLocked方法中找出了符合要求的所有BroadcastReceiver;接下来就需要把这个广播分发到这些接收者之中。

  • AMS#broadcastIntentLocked
public final class ActivityManagerService extends ActivityManagerNativeimplements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver= new IntentResolver<BroadcastFilter, BroadcastFilter>() ;BroadcastQueue mFgBroadcastQueue;BroadcastQueue mBgBroadcastQueue;// Convenient for easy iteration over the queues. Foreground is first// so that dispatch of foreground broadcasts gets precedence.final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[2];BroadcastQueue broadcastQueueForIntent(Intent intent) {final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;if (DEBUG_BACKGROUND_BROADCAST) {Slog.i(TAG, "Broadcast intent " + intent + " on "+ (isFg ? "foreground" : "background")+ " queue");}return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;}  private final int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle map, String requiredPermission, int appOp,boolean ordered, boolean sticky, int callingPid, int callingUid,int userId) {...BroadcastQueue queue = broadcastQueueForIntent(intent);BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, resolvedType,requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,resultData, resultExtras, ordered, sticky, false, userId);boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);if (!replaced) {queue.enqueueOrderedBroadcastLocked(r);queue.scheduleBroadcastsLocked();}}
}
  1. 首先创建了一个BroadcastRecord代表此次发送的这条广播,然后把它丢进一个队列,最后通过scheduleBroadcastsLocked通知队列对广播进行处理
  2. 在BroadcastQueue中通过Handle调度了对于广播处理的消息,调度过程由processNextBroadcast方法完成,而这个方法通过performReceiveLocked最终调用了IIntentReceiver的performReceive方法
    • 这个IIntentReceiver正是在广播注册过程中由App进程提供给AMS进程的Binder对象,现在AMS通过这个Binder对象进行IPC调用通知广播接受者所在进程完成余下操作
    • 在上文我们分析广播的注册过程中提到过,这个IntentReceiver的实现是LoadedApk.ReceiverDispatcher

我们查看这个对象的performReceive方法,源码如下:

public void performReceive(Intent intent, int resultCode, String data,Bundle extras, boolean ordered, boolean sticky, int sendingUser) {Args args = new Args(intent, resultCode, data, extras, ordered,sticky, sendingUser);if (!mActivityThread.post(args)) {if (mRegistered && ordered) {IActivityManager mgr = ActivityManagerNative.getDefault();args.sendFinished(mgr);}}
}//这个方法创建了一个Args对象,然后把它post到了mActivityThread这个Handler中;我们查看Args类的run方法public void run() {final BroadcastReceiver receiver = mReceiver;final boolean ordered = mOrdered;  final IActivityManager mgr = ActivityManagerNative.getDefault();final Intent intent = mCurIntent;mCurIntent = null;if (receiver == null || mForgotten) {if (mRegistered && ordered) {sendFinished(mgr);}return;}try {ClassLoader cl =  mReceiver.getClass().getClassLoader(); // Important!! load classintent.setExtrasClassLoader(cl);setExtrasClassLoader(cl);receiver.setPendingResult(this);receiver.onReceive(mContext, intent); //【这里,我们看到了相应BroadcastReceiver的onReceive回调】 callback} catch (Exception e) {if (mRegistered && ordered) {sendFinished(mgr);}if (mInstrumentation == null ||!mInstrumentation.onException(mReceiver, e)) {throw new RuntimeException("Error receiving broadcast " + intent+ " in " + mReceiver, e);}}if (receiver.getPendingResult() != null) {finish();}
}

5.2 hook广播的思路

从分析过程中我们发现,Framework对于静态广播和动态广播的处理是不同的;不过,这个不同之处仅仅体现在注册过程

  • 静态广播需要在AndroidManifest.xml中注册,并且注册的信息存储在PMS中;
  • 动态广播不需要预注册,注册的信息存储在AMS中

从实现Activity的插件化过程中我们知道,需要在AndroidManifest.xml中预先注册是一个相当麻烦的事情——我们需要使用『替身』并在合适的时候进行『偷梁换柱』;因此看起来动态广播的处理要容易那么一点,我们先讨论一下如何实现动态注册BroadcastReceiver的插件化

  • 首先,广播并没有复杂的生命周期,它的整个存活过程其实就是一个onReceive回调;而动态广播又不需要在AndroidManifest.xml中预先注册,所以动态注册的BroadcastReceiver其实可以当作一个普通的Java对象;我们完全可以用纯ClassLoader技术实现它——不就是把插件中的Receiver加载进来,然后想办法让它能接受onReceive回调

静态BroadcastReceiver看起来要复杂一些,但是我们连Activity都搞定了,还有什么难得到我们呢?对于实现静态BroadcastReceiver插件化的问题,有的童鞋或许会想,我们可以借鉴Activity的工作方式——用替身和Hook解决。但是很遗憾,这样是行不通的。为什么呢?因为静态广播主要却别就是AndroidManifest.xml中配置Filter不同,但是如果使用替身,也就意味着没办法响应不同IntentfIlter

  • BroadcastReceiver有一个IntentFilter的概念,也就是说,每一个BroadcastReceiver只对特定的Broadcast感兴趣;而且,AMS在进行广播分发的时候,也会对这些BroadcastReceiver与发出的广播进行匹配,只有Intent匹配的Receiver才能收到广播;在分析源码的时候也提到了这个匹配过程。

  • 如果我们尝试用替身Receiver解决静态注册的问题,那么它的IntentFilter该写什么?我们无法预料插件中静态注册的Receiver会使用什么类型的IntentFilter,就算我们在AndroidManifest.xml中声明替身也没有用——我们压根儿收不到与我们的IntentFilter不匹配的广播。

其实,我们对于Activity的处理方式也有这个问题;如果你尝试用IntentFilter的方式启动Activity,这并不能成功;这算得上是DroidPlugin的缺陷之一。

  • 可以把静态广播当作动态广播处理

既然都是广播,它们的功能都是订阅一个特定的消息然后执行某个特定的操作,我们完全可以把插件中的静态广播全部注册为动态广播,这样就解决了静态广播的问题。当然,这样也是有缺陷的,静态BroadcastReceiver与动态BroadcastReceiver一个非常大的不同之处在于:动态BroadcastReceiver在进程死亡之后是无法接收广播的,而静态BroadcastReceiver则可以——系统会唤醒Receiver所在进程;

5.3 插件中静态广播的非静态实现

上文我们提到,可以把静态BroadcastReceiver当作动态BroadcastReceiver处理;我们接下来实现这个过程。

5.3.1 解析插件Apk中的PulginReceiver

要把插件中的静态BroadcastReceiver当作动态BroadcastReceiver处理,我们首先得知道插件中到底注册了哪些广播;这个过程归根结底就是获取AndroidManifest.xml中的标签下面的内容,我们可以选择手动解析xml文件;这里我们选择使用系统的 PackageParser 帮助解析

这种方式在之前的 [插件Activity加载过程] 中也用到过,如果忘记了可以温习一下。

PackageParser中有一系列方法用来提取Apk中的信息,可是翻遍了这个类也没有找到与「Receiver」名字相关的方法;最终我们发现BroadcastReceiver信息是用与Activity相同的类存储的!这一点可以在PackageParser的内部类Package中发现端倪——成员变量receivers和activities的范型类型相同

所以,我们要解析apk的<receiver>的信息,可以使用PackageParsergenerateActivityInfo方法。

public final class ReceiverHelper {public static Map<ActivityInfo, List<? extends IntentFilter>> sCache =new HashMap<ActivityInfo, List<? extends IntentFilter>>();private static void parserReceivers(File apkFile) throws Exception {Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);Object packageParser = packageParserClass.newInstance();// 首先调用parsePackage获取到apk对象对应的Package对象Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_RECEIVERS);// 读取Package对象里面的receivers字段,注意这是一个 List<Activity> (没错,底层把<receiver>当作<activity>处理)// 接下来要做的就是根据这个List<Activity> 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了)Field receiversField = packageObj.getClass().getDeclaredField("receivers");List receivers = (List) receiversField.get(packageObj);// 调用generateActivityInfo 方法, 把PackageParser.Activity 转换成Class<?> packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity");Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");Class<?> userHandler = Class.forName("android.os.UserHandle");Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");int userId = (Integer) getCallingUserIdMethod.invoke(null);Object defaultUserState = packageUserStateClass.newInstance();Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");Field intentsField = componentClass.getDeclaredField("intents");// 需要调用 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int)Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo",packageParser$ActivityClass, int.class, packageUserStateClass, int.class);// 解析出 receiver以及对应的 intentFilterfor (Object receiver : receivers) {ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, receiver, 0, defaultUserState, userId);List<? extends IntentFilter> filters = (List<? extends IntentFilter>) intentsField.get(receiver);sCache.put(info, filters);}}}

5.3.2 注册插件PulginReceiver

我们已经解析得到了插件中静态注册的BroadcastReceiver的信息,现在我们只需要把这些静态广播动态注册一遍就可以了;但是,由于BroadcastReceiver的实现类存在于插件之后,我们需要手动用ClassLoader来加载它;

这一点在 插件Activity加载机制 已有讲述,不啰嗦了。

public final class ReceiverHelper {public static void preLoadReceiver(Context context, File apk) throws Exception {parserReceivers(apk);ClassLoader cl = null;for (ActivityInfo activityInfo : ReceiverHelper.sCache.keySet()) {Log.i(TAG, "preload receiver:" + activityInfo.name);List<? extends IntentFilter> intentFilters = ReceiverHelper.sCache.get(activityInfo);if (cl == null) {//用自己的ClassLoader去加载插件PulginReceivercl = CustomClassLoader.getPluginClassLoader(apk, activityInfo.packageName);}// 把解析出来的每一个静态Receiver都注册为动态的for (IntentFilter intentFilter : intentFilters) {BroadcastReceiver receiver = (BroadcastReceiver) cl.loadClass(activityInfo.name).newInstance();context.registerReceiver(receiver, intentFilter);}}}
public final class ReceiverHelper {

5.4 插件动态广播的注册实现

动态注册的BroadcastReceiver其实可以当作一个普通的Java对象,把插件中的Receiver加载进来,然后注册

5.5 示例

  • test.jar中存在广播接收器,接收到后,会再发送一个广播
  • MainActivity中加载插件,并初始化hook相关底层framework
public class MainActivity extends Activity {// 发送广播到插件之后, 插件如果受到, 那么会回传一个ACTION 为这个值的广播;static final String ACTION = "com.weishu.upf.demo.app2.PLUGIN_ACTION";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button t = new Button(this);setContentView(t);t.setText("send broadcast to plugin: demo");t.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(getApplicationContext(), "插件插件!收到请回答!!", Toast.LENGTH_SHORT).show();sendBroadcast(new Intent("com.weishu.upf.demo.app2.Receiver1"));}});Utils.extractAssets(this, "test.jar");File testPlugin = getFileStreamPath("test.jar");try {ReceiverHelper.preLoadReceiver(this, testPlugin);Log.i(getClass().getSimpleName(), "hook success");} catch (Exception e) {throw new RuntimeException("receiver load failed", e);}// 注册插件收到我们发送的广播之后, 回传的广播registerReceiver(mReceiver, new IntentFilter(ACTION));}BroadcastReceiver mReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context, "插件插件,我是主程序,握手完成!", Toast.LENGTH_SHORT).show();}};@Overrideprotected void onDestroy() {super.onDestroy();unregisterReceiver(mReceiver);}
}

六、Service的插件化

understand-plugin-framework/service-management/

6.1 源码解析

Service分为两种形式:以startService启动的服务和用bindService绑定的服务;由于这两个过程大体相似,这里以稍复杂的bindService为例分析Service组件的工作原理。

  • 绑定Service的过程是通过Context类的bindService完成的,这个方法需要三个参数

    • 第一个参数代表想要绑定的Service的Intent
    • 第二个参数是一个ServiceConnetion,我们可以通过这个对象接收到Service绑定成功或者失败的回调
    • 第三个参数则是绑定时候的一些FLAG

Context的具体实现在ContextImpl类,ContextImpl中的bindService方法直接调用了bindServiceCommon方法,此方法源码如下:

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,UserHandle user) {IServiceConnection sd;if (conn == null) {throw new IllegalArgumentException("connection is null");}if (mPackageInfo != null) {// importantsd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),mMainThread.getHandler(), flags);} else {throw new RuntimeException("Not supported in system context");}validateServiceIntent(service);try {IBinder token = getActivityToken();if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null&& mPackageInfo.getApplicationInfo().targetSdkVersion< android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {flags |= BIND_WAIVE_PRIORITY;}service.prepareToLeaveProcess();int res = ActivityManagerNative.getDefault().bindService(mMainThread.getApplicationThread(), getActivityToken(), service,service.resolveTypeIfNeeded(getContentResolver()),sd, flags, getOpPackageName(), user.getIdentifier());if (res < 0) {throw new SecurityException("Not allowed to bind to service " + service);}return res != 0;} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}
}
  • 这个方法最终通过ActivityManagerNative实现跨进程通信,借助AMS进而完成Service的绑定过程
  • 在跟踪AMS的bindService源码之前,我们关注一下这个方法开始处创建的IServiceConnection sd变量
    • 这个IServiceConnection与IApplicationThread以及IIntentReceiver相同,都是ActivityThread给AMS提供的用来与之进行通信的Binder对象;
    • 这个接口的实现类为LoadedApk.ServiceDispatcher

bindServiceCommon方法最终调用了ActivityManagerNative的bindService,而这个方法的真正实现在AMS里面,源码如下:

ActiveServices mServices = new ActiveServices(this);
public int bindService(IApplicationThread caller, IBinder token, Intent service,String resolvedType, IServiceConnection connection, int flags, String callingPackage,int userId) throws TransactionTooLargeException {enforceNotIsolatedCaller("bindService");// 略去参数校检synchronized(this) {return mServices.bindServiceLocked(caller, token, service,resolvedType, connection, flags, callingPackage, userId);}
}int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,String resolvedType, IServiceConnection connection, int flags,String callingPackage, int userId) throws TransactionTooLargeException {final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);// 参数校检,略//【1】ServiceLookupResult res =retrieveServiceLocked(service, resolvedType, callingPackage,Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);// 结果校检, 略ServiceRecord s = res.record;final long origId = Binder.clearCallingIdentity();try {// ... 不关心, 略mAm.startAssociationLocked(callerApp.uid, callerApp.processName,s.appInfo.uid, s.name, s.processName);//【2】AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);ConnectionRecord c = new ConnectionRecord(b, activity,connection, flags, clientLabel, clientIntent);IBinder binder = connection.asBinder();ArrayList<ConnectionRecord> clist = s.connections.get(binder);clist.add(c);// 对connection进行处理, 方便存取,略//【3】if ((flags&Context.BIND_AUTO_CREATE) != 0) {s.lastActivity = SystemClock.uptimeMillis();if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {return 0;}}// 与BIND_AUTO_CREATE不同的启动FLAG,原理与后续相同,略} finally {Binder.restoreCallingIdentity(origId);}return 1;
}
  • bindServiceLocked方法

    • 首先它通过retrieveServiceLocked方法获取到了intent匹配到的需要bind到的Service组件res;
    • 然后把ActivityThread传递过来的IServiceConnection使用ConnectionRecord进行了包装,方便接下来使用;
    • 最后如果启动的FLAG为BIND_AUTO_CREATE,那么调用bringUpServiceLocked开始创建Service

我们来看bringUpServiceLocked方法:
java```
private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting) throws TransactionTooLargeException {

// 略。。final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
final String procName = r.processName;
ProcessRecord app;if (!isolated) {app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);if (app != null && app.thread != null) {try {app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);// 【1. important !!!】realStartServiceLocked(r, app, execInFg);return null;} catch (TransactionTooLargeException e) {throw e;} catch (RemoteException e) {Slog.w(TAG, "Exception when starting service " + r.shortName, e);}}
} else {app = r.isolatedProc;
}// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null) {// 【2. important !!!】if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,"service", r.name, false, isolated, false)) == null) {bringDownServiceLocked(r);return msg;}if (isolated) {r.isolatedProc = app;}
}
// 略。。
return null;

}


- `bringUpServiceLocked`方法。- 如果Service所在的进程已经启动,那么直接调用`realStartServiceLocked`方法来**真正启动Service组件**- 如果Service所在的进程还没有启动,那么先在AMS中记下这个要启动的Service组件,然后通过`startProcessLocked`先启动新的进程![在这里插入图片描述](https://img-blog.csdnimg.cn/20181029194514239.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZlaTIwMTIxMTA2,size_16,color_FFFFFF,t_70)### 6.1.1 AMS#realStartServiceLocked直接启动Service  我们来看`realStartServiceLocked`方法:

private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {

// 略。。boolean created = false;
try {synchronized (r.stats.getBatteryStats()) {r.stats.startLaunchedLocked();}mAm.ensurePackageDexOpt(r.serviceInfo.packageName);app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);//【关注1】app.thread.scheduleCreateService(r, r.serviceInfo,mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),app.repProcState);r.postNotification();created = true;
} catch (DeadObjectException e) {mAm.appDiedLocked(app);throw e;
} finally {// 略。。
}//【关注2】
requestServiceBindingsLocked(r, execInFg);// 不关心,略。。

}


#### 6.1.1.1 app.thread#scheduleCreateService本地创建Service在realStartServiceLocked方法中:首先调用了app.thread的scheduleCreateService方法,我们知道,这是一个IApplicationThread对象
它是App所在进程提供给AMS的用来与App进程进行通信的Binder对象。这个Binder的Server端在ActivityThread类```java
//ActivityThread.class
public final void scheduleCreateService(IBinder token,ServiceInfo info, CompatibilityInfo compatInfo, int processState) {updateProcessState(processState, false);CreateServiceData s = new CreateServiceData();s.token = token;s.info = info;s.compatInfo = compatInfo;//它不过是转发了一个消息给ActivityThread的H这个Handler//H类收到这个消息之后,直接调用了ActivityThread类的handleCreateService方法sendMessage(H.CREATE_SERVICE, s);
}//这里与Activity组件的创建过程如出一辙
//需要注意的是,这里Service类的创建过程与Activity是略微有点不同的,虽然都是通过ClassLoader通过反射创建
//但是Activity却把创建过程委托给了Instrumentation类而Service则是直接进行
private void handleCreateService(CreateServiceData data) {unscheduleGcIdler();LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);Service service = null;try {java.lang.ClassLoader cl = packageInfo.getClassLoader();service = (Service) cl.loadClass(data.info.name).newInstance();} catch (Exception e) {}try {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();mServices.put(data.token, service);try {ActivityManagerNative.getDefault().serviceDoneExecuting(data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);} catch (RemoteException e) {// nothing to do.}} catch (Exception e) {}
}

6.1.1.2 AMS#requestServiceBindingLocked实现Bind和connect

截止当前,现在ActivityThread里面的handleCreateService方法成功创建出了Service对象,并且调用了它的onCreate方法;到这里我们的Service已经启动成功

//进入【关注2】
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,boolean execInFg, boolean rebind) throws TransactionTooLargeException {if (r.app == null || r.app.thread == null) {return false;}if ((!i.requested || rebind) && i.apps.size() > 0) {try {bumpServiceExecutingLocked(r, execInFg, "bind");r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,r.app.repProcState);// 不关心,略。。}return true;
}

这里又通过IApplicationThread这个Binder进行了一次IPC调用,我们跟踪ActivityThread类里面的ApplicationThread的scheduleBindService方法

发现这个方法不过通过Handler转发了一次消息,真正的处理代码在handleBindService里面

//ActivityThread.class
private void handleBindService(BindServiceData data) {Service s = mServices.get(data.token);if (s != null) {try {data.intent.setExtrasClassLoader(s.getClassLoader());data.intent.prepareToEnterProcess();try {if (!data.rebind) {//【回调】IBinder binder = s.onBind(data.intent);//【IPC调用】ActivityManagerNative.getDefault().publishService(data.token, data.intent, binder);} else {s.onRebind(data.intent);ActivityManagerNative.getDefault().serviceDoneExecuting(data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);}ensureJitEnabled();} catch (RemoteException ex) {}} catch (Exception e) {}}
}
  1. 我们要Bind的Service终于在这里完成了绑定;
  2. 绑定之后又通过ActivityManagerNative这个Binder进行一次IPC调用,我们查看AMS的publishService方法,这个方法简单第调用了publishServiceLocked方法,源码如下:
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {final long origId = Binder.clearCallingIdentity();try {if (r != null) {Intent.FilterComparison filter= new Intent.FilterComparison(intent);IntentBindRecord b = r.bindings.get(filter);if (b != null && !b.received) {b.binder = service;b.requested = true;b.received = true;for (int conni=r.connections.size()-1; conni>=0; conni--) {ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);for (int i=0; i<clist.size(); i++) {ConnectionRecord c = clist.get(i);if (!filter.equals(c.binding.intent.intent)) {continue;}try {c.conn.connected(r.name, service);} catch (Exception e) {}}}}serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false);}} finally {Binder.restoreCallingIdentity(origId);}
}

还记得我们之前提到的那个IServiceConnection吗?在bindServiceLocked方法里面,我们把这个IServiceConnection放到了一个ConnectionRecord的List中存放在ServiceRecord里面

这里所做的就是取出已经被Bind的这个Service对应的IServiceConnection对象,然后调用它的connected方法;

我们说过,这个IServiceConnection也是一个Binder对象,它的Server端在LoadedApk.ServiceDispatcher里面

6.1.3 进程不存在时service启动方式

最后提一点,以上我们分析了Service所在进程已经存在的情况,如果Service所在进程不存在,那么会调用startProcessLocked方法创建一个新的进程,并把需要启动的Service放在一个队列里面;

创建进程的过程通过Zygote fork出来,进程创建成功之后会调用ActivityThread的main方法,在这个main方法里面间接调用到了AMS的attachApplication方法,在AMS的attachApplication里面会检查刚刚那个待启动Service队列里面的内容,并执行Service的启动操作;

之后的启动过程与进程已经存在的情况下相同;

6.2 Service的插件化思路

从上文的源码分析来看,Service组件与Activity有着非常多的相似之处:它们都是通过Context类完成启动,接着通过ActivityMnagaerNative进入AMS,最后又通过IApplicationThread这个Binder IPC到App进程的Binder线程池,然后通过H转发消息到App进程的主线程,最终完成组件生命周期的回调;

6.2.1 Service与Activity的异同

Service组件和Activity组件有什么不同?这些不同使得我们对于插件化方案的选择又有什么影响?

6.2.1.1 用户交互对于生命周期的影响

首先,Activity与Service组件最大的不同点在于,Activity组件可以与用户进行交互;这一点意味着用户的行为会对Activity组件产生影响,对我们来说最重要的影响就是Activity组件的生命周期;用户点击按钮从界面A跳转到界面B,会引起A和B这两个Activity一系列生命周期的变化。而Service组件则代表后台任务,除了内存不足系统回收之外,它的生命周期完全由我们的代码控制,与用户的交互无关。

这意味着什么?

Activity组件的生命周期受用户交互影响,而这种变化只有Android系统才能感知,因此我们必须把插件的Activity交给系统管理,才能拥有完整的生命周期;

但Service组件的生命周期不受外界因素影响,那么自然而然,我们可以手动控制它的生命周期,就像我们对于BroadcastReceiver的插件化方式一样!

Activity组件的插件化无疑是比较复杂的,为了把插件Activity交给系统管理进而拥有完整生命周期,我们设计了一个天衣无缝的方案骗过了AMS;既然Service的生命周期可以由我们自己控制,那么我们可以有更简单的方案实现它的插件化。

6.2.1.2 Activity的任务栈导致的差异

上文指出了Activity和Service组件在处理用户交互方面的不同,这使得我们对于Service组建的插件化可以选择一种较为简单的方式;也许你会问,那采用Activity插件化的那一套技术能够实现Service组件的插件化吗?

很遗憾,答案是不行的。虽然Activity的插件化技术更复杂,但是这种方案并不能完成Service组件的插件化——复杂的方案并不意味了它能处理更多的问题。

原因在于Activity拥有任务栈的概念。或许你觉得任务栈并不是什么了不起的东西,但是,这确实是Service组件与Activity组件插件化方式分道扬镳的根本原因。

任务栈的概念使得Activtiy的创建就代表着入栈,销毁则代表出栈;又由于Activity代表着与用户交互的界面,所以这个栈的深度不可能太深——Activity栈太深意味着用户需要狂点back键才能回到初始界面,这种体验显然有问题;因此,插件框架要处理的Activity数量其实是有限的,所以我们在AndroidManifest.xml中声明有限个StubActivity就能满足插件启动近乎无限个插件Activity的需求

但是Service组件不一样,理论情况下,可以启动的Service组件是无限的——除了硬件以及内存资源,没有什么限制它的数目;如果采用Activity的插件化方式,就算我们在AndroidMafenist.xml中声明再多的StubService,总有不能满足插件中要启动的Service数目的情况出现。

也许有童鞋会说,可以用一个StubService对应多个插件Service,这确实能解决部分问题;但是,Service无法拥有多实例

Service组件与Activity组件另外一个不同点在于,对同一个Service调用多次startService并不会启动多个Service实例,而非特定Flag的Activity是可以允许这种情况存在的

因此如果用StubService的方式,为了实现Service的这种特性,必须建立一个StubService到插件Service的一个Map,Map的这种一一对应关系使得我们使用一个StubService对应多个插件Service的计划成为天方夜谭。

6.2.2 如何实现Service的插件化?

上文指出,我们不能套用Activity的方案实现Service组件的插件化,可以通过手动控制Service组件的生命周期实现


【Service生命周期】

从图中可以看出,Service的生命周期相当简单:整个生命周期从调用 onCreate() 开始起,到 onDestroy() 返回时结束

  1. 对于非绑定服务,就是从startService调用到stopService或者stopSelf调用
  2. 对于绑定服务,就是bindService调用到unbindService调用;

如果要手动控制Service组件的生命周期,我们只需要模拟出这个过程即可;而实现这一点并不复杂:

  1. 如果以startService方式启动插件Service,直接回调要启动的Service对象的onStartCommand方法即可;如果用stopService或者stopSelf的方式停止Service,只需要回调对应的Service组件的onDestroy方法。
  2. 如果用bindService方式绑定插件Service,可以调用对应Service对应的onBind方法,获取onBind方法返回的Binder对象,然后通过ServiceConnection对象进行回调统计;unBindService的实现同理

6.2.2.1 完全手动控制

现在我们已经有了实现思路,那么具体如何实现呢?

我们必须在startService,stopService等方法被调用的时候拿到控制权,才能手动去控制Service的生命周期;要达到这一目的非常简单——Hook ActivityManagerNative即可。在Activity的插件化方案中我们就通过这种方式接管了startActivity调用,相信读者并不陌生。

我们Hook掉ActivityManagerNative之后,可以拦截对于startService以及stopService等方法的调用;拦截之后,我们可以直接对插件Service进行操作:

  1. 拦截到startService之后,如果Service还没有创建就直接创建Service对象(可能需要加载插件),然后调用这个Service的onCreate,onStartCommond方法;如果Service已经创建,获取到原来创建的Service对象并执行其onStartCommand方法。
  2. 拦截到stopService之后,获取到对应的Service对象,直接调用这个Service的onDestroy方法。

这种方案简直简单得让人不敢相信!很可惜,这么干是不行的

  • 首先,Service存在的意义在于它作为一个后台任务,拥有相对较高运行时优先级;除非在内存及其不足威胁到前台Activity的时候,这个组件才会被系统杀死。

    • 上述这种实现完全把Service当作一个普通的Java对象使用了,因此并没有完全实现Service所具备的能力。
  • 其次,Activity以及Service等组件是可以指定进程的,而让Service运行在某个特定进程的情况非常常见——所谓的远程Service
    • 用上述这种办法压根儿没有办法让某个Service对象运行在一个别的进程

6.2.2.2 代理分发技术

既然我们希望插件的Service具有一定的运行时优先级,那么一个货真价实的Service组件是必不可少的——只有这种被系统认可的真正的Service组件才具有所谓的运行时优先级

因此,我们可以注册一个真正的Service组件ProxyService,让这个Service承载一个真正的Service组件所具备的能力(进程优先级等);当启动插件的服务比如PluginService的时候,我们统一启动这个ProxyService,当这个ProxyService运行起来之后,再在它的onStartCommand等方法里面进行分发,执行PluginService的onStartCommond等对应的方法;

我们把这种方案形象地称为「代理分发技术」

代理分发技术也可以完美解决插件Service可以运行在不同的进程的问题——我们可以在AndroidManifest.xml中注册多个ProxyService,指定它们的process属性,让它们运行在不同的进程;当启动的插件Service希望运行在一个新的进程时,我们可以选择某一个合适的ProxyService进行分发。也许有童鞋会说,那得注册多少个ProxyService才能满足需求啊?理论上确实存在这问题,但事实上,一个App使用超过10个进程的几乎没有;因此这种方案是可行的。

6.3 Service插件化的实现

现在我们已经设计出了Service组件的插件化方案,接下来我们以startService以及stopService为例实现这个过程。

6.3.1 注册代理Service

我们需要一个货真价实的Service组件来承载进程优先级等功能,因此需要在AndroidManifest.xml中声明一个或者多个(用以支持多进程)这样的Sevice:

6.3.2 拦截AMS#startService等调用过程

手动控制Service组件的生命周期,需要拦截startService,stopService等调用,并且把启动插件Service全部重定向为启动ProxyService(保留原始插件Service信息);这个拦截过程需要Hook ActvityManagerNative,我们对这种技术应该是轻车熟路了;不了解的童鞋可以参考之前的文章 Hook机制之AMS&PMS 。

public class AMSHookHelper {/*** Hook AMS* 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity"* 进而骗过AMS*/public static void hookActivityManagerNative() throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException,IllegalAccessException, NoSuchFieldException {Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");gDefaultField.setAccessible(true);Object gDefault = gDefaultField.get(null);// gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段Class<?> singleton = Class.forName("android.util.Singleton");Field mInstanceField = singleton.getDeclaredField("mInstance");mInstanceField.setAccessible(true);// ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象Object rawIActivityManager = mInstanceField.get(gDefault);// 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));mInstanceField.set(gDefault, proxy);}
}//IActivityManagerHandler,在收到startService,stopService之后可以进行具体的操作,对于startService来说,就是直接替换启动的插件Service为ProxyService等待后续处理,代码如下:class IActivityManagerHandler implements InvocationHandler {public IActivityManagerHandler(Object base) {mBase = base;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("startService".equals(method.getName())) {// API 23:// public ComponentName startService(IApplicationThread caller, Intent service,//        String resolvedType, int userId) throws RemoteException// 找到参数里面的第一个Intent 对象Pair<Integer, Intent> integerIntentPair = foundFirstIntentOfArgs(args);Intent newIntent = new Intent();// 代理Service的包名, 也就是我们自己的包名String stubPackage = UPFApplication.getContext().getPackageName();// 这里我们把启动的Service替换为ProxyService, 让ProxyService接收生命周期回调ComponentName componentName = new ComponentName(stubPackage, ProxyService.class.getName());newIntent.setComponent(componentName);// 把我们原始要启动的TargetService先存起来newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, integerIntentPair.second);// 替换掉Intent, 达到欺骗AMS的目的args[integerIntentPair.first] = newIntent;Log.v(TAG, "hook method startService success");return method.invoke(mBase, args);}//     public int stopService(IApplicationThread caller, Intent service,// String resolvedType, int userId) throws RemoteExceptionif ("stopService".equals(method.getName())) {Intent raw = foundFirstIntentOfArgs(args).second;if (!TextUtils.equals(UPFApplication.getContext().getPackageName(), raw.getComponent().getPackageName())) {// 插件的intent才做hookLog.v(TAG, "hook method stopService success");return ServiceManager.getInstance().stopService(raw);}}return method.invoke(mBase, args);}private Pair<Integer, Intent> foundFirstIntentOfArgs(Object... args) {int index = 0;for (int i = 0; i < args.length; i++) {if (args[i] instanceof Intent) {index = i;break;}}return Pair.create(index, (Intent) args[index]);}
}

6.3.3 编写并注册代理ProxyService实现Service匹配和创建分发

Hook ActivityManagerNative之后,所有的插件Service的启动都被重定向了到了我们注册的ProxyService,这样可以保证我们的插件Service有一个真正的Service组件作为宿主;

也就是说,上文中我们把启动插件Service重定向为启动ProxyService,现在ProxyService已经启动,因此必须把控制权交回原始的PluginService

要执行特定插件Service的任务,我们必须把这个任务分发到真正要启动的PluginService上去

以onStart为例,在启动ProxyService之后,会收到ProxyService的onStart回调,我们可以在这个方法里面把具体的任务交给原始要启动的插件Service组件:

public class ProxyService extends Service {public void onStart(Intent intent, int startId) {Log.d(TAG, "onStart() called with " + "intent = [" + intent + "], startId = [" + startId + "]");// 分发ServiceServiceManager.getInstance().onStart(intent, startId);super.onStart(intent, startId);}@Overridepublic IBinder onBind(Intent intent) {// TODO: 16/5/11 bindService实现类似,可自行完成return null;}
}public final class ServiceManager {/*** 启动某个插件Service; 如果Service还没有启动, 那么会创建新的插件Service*/public 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();}}
}

6.3.4 匹配过程

6.3.4.1 预处理:读取插件中的Service组件信息并存储

我们需要 预先的读取插件中所有的Service组件的信息,并存储,需要在加载插件或者初始化Applications时,先触发下

public final class ServiceManager {// 存储插件的Service信息private Map<ComponentName, ServiceInfo> mServiceInfoMap = new HashMap<ComponentName, ServiceInfo>();// 存储插件的Service实例private Map<String, Service> mServiceMap = new HashMap<String, Service>();/*** 解析Apk文件中的 <service>, 并存储起来* 主要是调用PackageParser类的generateServiceInfo方法* @param apkFile 插件对应的apk文件* @throws Exception 解析出错或者反射调用出错, 均会抛出异常*/public void preLoadServices(File apkFile) throws Exception {Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);Object packageParser = packageParserClass.newInstance();// 首先调用parsePackage获取到apk对象对应的Package对象Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_SERVICES);// 读取Package对象里面的services字段// 接下来要做的就是根据这个List<Service> 获取到Service对应的ServiceInfoField servicesField = packageObj.getClass().getDeclaredField("services");List services = (List) servicesField.get(packageObj);// 调用generateServiceInfo 方法, 把PackageParser.Service转换成ServiceInfoClass<?> packageParser$ServiceClass = Class.forName("android.content.pm.PackageParser$Service");Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");Class<?> userHandler = Class.forName("android.os.UserHandle");Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");int userId = (Integer) getCallingUserIdMethod.invoke(null);Object defaultUserState = packageUserStateClass.newInstance();// 需要调用 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int)Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateServiceInfo",packageParser$ServiceClass, int.class, packageUserStateClass, int.class);// 解析出intent对应的Service组件for (Object service : services) {ServiceInfo info = (ServiceInfo) generateReceiverInfo.invoke(packageParser, service, 0, defaultUserState, userId);mServiceInfoMap.put(new ComponentName(info.packageName, info.name), info);}}
}

6.3.4.2 匹配本地的mServiceMap缓存确认目标PluginService

只需要根据Intent里面的Component信息,借助mServiceMap缓存,就可以取出对应的PluginService。

private ServiceInfo selectPluginService(Intent pluginIntent) {for (ComponentName componentName : mServiceInfoMap.keySet()) {if (componentName.equals(pluginIntent.getComponent())) {return mServiceInfoMap.get(componentName);}}return null;
}

6.3.5 创建以及分发

6.3.5.1 预处理:系统BaseDexClassLoader支持自动加载插件中的Service

我们可以在ProxyService里面把任务转发给真正要启动的插件Service组件,要完成这个过程肯定需要创建一个对应的插件Service对象,比如PluginService;

但是通常情况下插件存在与单独的文件之中,正常的方式是无法创建这个PluginService对象的,宿主程序默认的ClassLoader无法加载插件中对应的这个类;

所以,要创建这个对应的PluginService对象,必须先完成插件的加载过程,让这个插件中的所有类都可以被正常访问;

这种技术我们在之前专门讨论过,并给出了「激进方案」和「保守方案」,不了解的童鞋可以参考文章 插件加载机制;这里我选择代码较少的「保守方案」为例(Droid Plugin中采用的激进方案):

public final class BaseDexClassLoaderHookHelper {public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {// 获取 BaseDexClassLoader : pathListField pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");pathListField.setAccessible(true);Object pathListObj = pathListField.get(cl);// 获取 PathList: Element[] dexElementsField dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");dexElementArray.setAccessible(true);Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);// Element 类型Class<?> elementClass = dexElements.getClass().getComponentType();// 创建一个数组, 用来替换原始的数组Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);// 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));Object[] toAddElementArray = new Object[] { o };// 把原始的elements复制进去System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);// 插件的那个element复制进去System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);// 替换dexElementArray.set(pathListObj, newElements);}}

6.3.5.2 创建

插件被加载之后,我们就需要创建插件Service对应的Java对象了;由于这些类是在运行时动态加载进来的,肯定不能直接使用new关键字——我们需要使用反射机制

但是下面的代码创建出插件Service对象能满足要求吗?

ClassLoader cl = getClassLoader();
Service service = cl.loadClass("com.plugin.xxx.PluginService1");

Service作为Android系统的组件,最重要的特点是它具有Context;所以,直接通过反射创建出来的这个PluginService就是一个壳子——没有Context的Service能干什么?

因此我们需要给将要创建的Service类创建出Conetxt;但是Context应该如何创建呢?我们平时压根儿没有这么干过,Context都是系统给我们创建好的。既然这样,我们可以参照一下系统是如何创建Service对象的;

在上文的Service源码分析中,在ActivityThread类的handleCreateService完成了这个步骤,摘要如下:

try {java.lang.ClassLoader cl = packageInfo.getClassLoader();service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
}try {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,我们反射取出来就行;所以,创建Service对象的代码如下:

public final class ServiceManager {// 存储插件的Service实例private Map<String, Service> mServiceMap = new HashMap<String, Service>();/*** 通过ActivityThread的handleCreateService方法创建出Service对象,并缓存到本地hasmap中* @param serviceInfo 插件的ServiceInfo* @throws Exception*/private void proxyCreateService(ServiceInfo serviceInfo) throws Exception {IBinder token = new Binder();// 创建CreateServiceData对象, 用来传递给ActivityThread的handleCreateService 当作参数Class<?> createServiceDataClass = Class.forName("android.app.ActivityThread$CreateServiceData");Constructor<?> constructor  = createServiceDataClass.getDeclaredConstructor();constructor.setAccessible(true);Object createServiceData = constructor.newInstance();// 写入我们创建的createServiceData的token字段, ActivityThread的handleCreateService用这个作为key存储ServiceField 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);// private void handleCreateService(CreateServiceData data) {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);}}

6.3.5.3 分发

现在我们已经创建出了对应的PluginService,并且拥有至关重要的Context对象;接下来就可以把消息分发给原始的PluginService组件了,这个分发的过程很简单,直接执行消息对应的回调(onStart, onDestroy等)即可;因此,完整的startService分发过程如下:

public void onStart(Intent proxyIntent, int startId) {Service service = mServiceMap.get(serviceInfo.name);service.onStart(targetIntent, startId);}

6.4 示例

6.4.1 Application中 先初始化hook一些系统函数

public class UPFApplication extends Application {private static Context sContext;@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);sContext = base;try {// 拦截startService, stopService等操作AMSHookHelper.hookActivityManagerNative();Utils.extractAssets(base, "test.jar");File apkFile = getFileStreamPath("test.jar");File odexFile = getFileStreamPath("test.odex");// Hook ClassLoader, 让插件中的类能够被成功加载BaseDexClassLoaderHookHelper.patchClassLoader(getClassLoader(), apkFile, odexFile);// Service预处理:解析插件中的Service组件ServiceManager.getInstance().preLoadServices(apkFile);} catch (Exception e) {throw new RuntimeException("hook failed");}}public static Context getContext() {return sContext;}
}

6.4.2 Activity中常规方式启动Service

public class MainActivity extends Activity implements View.OnClickListener {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);findViewById(R.id.startService1).setOnClickListener(this);findViewById(R.id.startService2).setOnClickListener(this);findViewById(R.id.stopService1).setOnClickListener(this);findViewById(R.id.stopService2).setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.startService1:startService(new Intent().setComponent(new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService1")));break;case R.id.startService2:startService(new Intent().setComponent(new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService2")));break;case R.id.stopService1:stopService(new Intent().setComponent(new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService1")));break;case R.id.stopService2:stopService(new Intent().setComponent(new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService2")));break;}}
}

七、ContentProvider的插件化

understand-plugin-framework/contentprovider-management/

目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案;那么对于ContentProvider,它又有什么特点?应该如何实现它的插件化?

在Android系统中,每一个应用程序都有自己的用户ID,而每一个应用程序所创建的文件的读写权限都是只赋予给自己所属的用户,这就限制了应用程序之间相互读写数据的操作。应用程序之间如果希望能够进行交互,只能采取跨进程通信的方式;Binder机制能够满足一般的IPC需求,但是如果应用程序之间需要共享大量数据,单纯使用Binder是很难办到的——我相信大家对于Binder 1M缓冲区以及TransactionTooLargeException一定不陌生;

ContentProvider这个组件对于Android系统有着特别重要的作用——作为一种极其方便的数据共享的手段,ContentProvider使得广大第三方App能够在壁垒森严的系统中自由呼吸。

ContentProvider使用了匿名共享内存(Ashmem)机制完成数据共享,因此它可以很方便地完成大量数据的传输。Android系统的短信,联系人,相册,媒体库等等一系列的基础功能都依赖与ContentProvider,它的重要性可见一斑

7.1 共享性

既然ContentProvider的核心特性是数据共享,那么要实现它的插件化,必须能让插件能够把它的ContentProvider共享给系统——如果不能「provide content」那还叫什么ContentProvider?

但是,如果回想一下Activity等组件的插件化方式,在涉及到「共享」这个问题上,一直没有较好的解决方案:

  1. 系统中的第三方App无法启动插件中带有特定IntentFilter的Activity,因为系统压根儿感受不到插件中这个真正的Activity的存在。
  2. 插件中的静态注册的广播并不真正是静态的,而是使用动态注册广播模拟实现的;这就导致如果宿主程序进程死亡,这个静态广播不会起作用;这个问题的根本原因在由于BroadcastReceiver的IntentFilter的不可预知性,使得我们没有办法把静态广播真正“共享”给系统。
  3. 我们没有办法在第三方App中启动或者绑定插件中的Service组件;因为插件的Service并不是真正的Service组件,系统能感知到的只是那个代理Service;因此如果插件如果带有远程Service组件,它根本不能给第三方App提供远程服务

虽然在插件系统中一派生机勃勃的景象,Activity,Service等插件组件百花齐放,插件与宿主、插件与插件争奇斗艳;但是一旦脱离了插件系统的温室,这一片和谐景象不复存在:插件组件不过是傀儡而已;活着的,只有宿主——整个插件系统就是一座死寂的鬼城,各个插件组件借尸还魂般地依附在宿主身上,了无生机。

既然希望把插件的ContentProvider共享给整个系统,让第三方的App都能获取到我们插件共享的数据,我们必须解决这个问题;下文将会围绕这个目标展开,完成ContentProvider的插件化,并且顺带给出上述问题的解决方案。

7.2 ContentProvider工作原理

AMS充当一个中间管理员的角色

  1. 每个进程在启动之后需要把自己应该install的provider, install之后封装PCR进Map,并将PCR的holder告诉AMS,这样后面有其他进程请求这个provider的话,AMS可以告诉你所请求的对端的信息。
  2. AMS持有Holders, Holder持有Binder
  3. 客户端请求时,先找自己map中的pcr,有的话则返回PCR里的Binder驱动;没有,找AMS要Holder,并将Holder中的对象封装PCR进Map,返回holder中的驱动
    也就是说,客户端总会找自己的map的pcr,但是这个pcr里的驱动可能是来自自己搞出来的,也有可能是AMS给的Holder的
  • handleBindApplication

    1. data.info.makeApplication

      1. 反射构造Applicaiton
      2. 调用attach
    2. installContentProviders(app, data.providers);
      1. 遍历cpi:ProviderInfos
      2. installProvider(context, null, cpi, false /noisy/, true /noReleaseNeeded/, true /stable/); 并用返回值填充List<IActivityManager.ContentProviderHolder> results
        1. // holder为null表示还没有install过 :if (holder == null || holder.provider == null) {

          1. localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();反射构建实例对象
          2. provider = localProvider.getIContentProvider();获取Binder驱动
          3. localProvider.attachInfo(c, info); 上下文和onCreate生命周期
        2. if (localProvider != null) {
          1. if (pr != null) { // 不为空代表install过provider = pr.mProvider;
          2. else ProviderClientRecord client = installProviderAuthoritiesLocked(provider, localProvider, holder);
            1. ProviderClientRecord pcr = new ProviderClientRecord(auths, provider, localProvider, holder); binder驱动 + 实例对象 封装进入PCR
            2. PCR对象进入 ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
        3. localProvider == null
          1. 还是封装PCR
        4. 返回 client.holder
      3. ActivityManagerNative.getDefault().publishContentProviders(getApplicationThread(), results); install完成之后,要告诉AMS,传递过去Holders,AMS也有一个MAP
    3. mInstrumentation.callApplicationOnCreate(app);
  • ContentResolver.query
    • ApplicationContentResolver.query

      • 获取驱动:IContentProvider binder = ApplicationContentResolver#acquireUnstableProvider(uri) 或者 acquireProvider(uri)

        • 最终:IContentProvider stableProvider = ActivityThread#acquireProvider(Context c, String auth, int userId, boolean stable)

          1. IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);

            1. ProviderClientRecord pr = mProviderMap.get(key); 从map中获取PCR
            2. IContentProvider provider = pr.mProvider; 从PCR中获取binderq驱动
            3. IBinder jBinder = provider.asBinder();
          2. IActivityManager.ContentProviderHolder holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);
            1. 如果进程B不存在则先启动进程B并installprovider,告诉AMS之后,由AMS返回给进程A对方的provider信息(此过程中由进程A发起的请求provider的线程会一直等待)
            2. 如果进程B存在则AMS直接返回给进程A对方的provider信息
          3. holder = installProvider(c, holder, holder.info,true /noisy/, holder.noReleaseNeeded, stable);相对于上文的过程
            1. 由于已经有holder对象,不会创建实例localProvider
            2. localProvider == null 因此仅installProviderAuthoritiesLocked:记录到map中相关binder对象
          4. 返货Binder驱动 holder.provider
      • 驱动操作IContentProvider.query

如同我们通过startActivity来启动Activity一样,与ContentProvider打交道的过程也是从Context类的一个方法开始的,这个方法叫做getContentResolver,使用ContentProvider的典型代码如下:

ContentResolver resolver = content.getContentResolver();
resolver.query(Uri.parse("content://authority/test"), null, null, null, null);
  • 直接去ContextImpl类里面查找的getContentResolver实现,发现这个方法返回的类型是android.app.ContextImpl.ApplicationContentResolver
  • 这个类是抽象类android.content.ContentResolver的子类,resolver.query实际上是调用父类ContentResolver的query实现:
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,@Nullable String selection, @Nullable String[] selectionArgs,@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {Preconditions.checkNotNull(uri, "uri");IContentProvider unstableProvider = acquireUnstableProvider(uri);if (unstableProvider == null) {return null;}IContentProvider stableProvider = null;Cursor qCursor = null;try {long startTime = SystemClock.uptimeMillis();ICancellationSignal remoteCancellationSignal = null;if (cancellationSignal != null) {cancellationSignal.throwIfCanceled();remoteCancellationSignal = unstableProvider.createCancellationSignal();cancellationSignal.setRemote(remoteCancellationSignal);}try {qCursor = unstableProvider.query(mPackageName, uri, projection,selection, selectionArgs, sortOrder, remoteCancellationSignal);} catch (DeadObjectException e) {// The remote process has died...  but we only hold an unstable// reference though, so we might recover!!!  Let's try!!!!// This is exciting!!1!!1!!!!1unstableProviderDied(unstableProvider);stableProvider = acquireProvider(uri);if (stableProvider == null) {return null;}qCursor = stableProvider.query(mPackageName, uri, projection,selection, selectionArgs, sortOrder, remoteCancellationSignal);}// 略...
}

  • query方法

    • 首先尝试调用抽象方法acquireUnstableProvider拿到一个IContentProvider对象,并尝试调用这个”unstable”对象的query方法
    • 万一调用失败(抛出DeadObjectExceptopn,熟悉Binder的应该了解这个异常)说明ContentProvider所在的进程已经死亡,这时候会尝试调用acquireProvider这个抽象方法来获取一个可用的IContentProvide

由于这两个acquire*都是抽象方法,我们可以直接看子类ApplicationContentResolver的实现:

@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {return mMainThread.acquireProvider(c,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), false);
}
@Override
protected IContentProvider acquireProvider(Context context, String auth) {return mMainThread.acquireProvider(context,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), true);
}

可以看到这两个抽象方法最终都通过调用ActivityThread类的acquireProvider获取到IContentProvider

7.2.1 当前进程中获取ActivityThread#ContentProvider的过程

ActivityThread类的acquireProvider方法如下,我们需要知道的是,方法的最后一个参数stable代表着ContentProvider所在的进程是否存活,如果进程已死,可能需要在必要的时候唤起这个进程;

public final IContentProvider acquireProvider(Context c, String auth, int userId, boolean stable) {final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);if (provider != null) {return provider;}IActivityManager.ContentProviderHolder holder = null;try {holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);} catch (RemoteException ex) {}if (holder == null) {Slog.e(TAG, "Failed to find provider info for " + auth);return null;}holder = installProvider(c, holder, holder.info,true /*noisy*/, holder.noReleaseNeeded, stable);return holder.provider;
}
  • acquireProvider方法核心是 获取ContentProvider并安装

    • 【1】首先通过acquireExistingProvider尝试从本进程中获取ContentProvider
    • 【2】如果获取不到,那么再请求AMS获取对应ContentProvider;
    • 不论是从哪里获取到的ContentProvider,获取完毕之后会调用installProvider来安装ContentProvider。

OK打住,我们思考一下,如果要实现ContentProvider的插件化,我们需要完成一些什么工作?

开篇的时候我提到了数据共享,那么具体来说,实现插件的数据共享,需要完成什么?ContentProvider是一个数据共享组件,也就是说它不过是一个携带数据的载体而已。为了支持跨进程共享,这个载体是Binder调用,为了共享大量数据,使用了匿名共享内存;

这么说还是有点抽象,那么想一下,给出一个ContentProvider,你能对它做一些什么操作?如果能让插件支持这些操作,不就支持了插件化么?这就是典型的duck type思想——如果一个东西看起来像ContentProvider,用起来也像ContentProvider,那么它就是ContentProvider。

ContentProvider主要支持query, insert, update, delete操作,由于这个组件一般工作在别的进程,因此这些调用都是Binder调用。从上面的代码可以看到,这些调用最终都是委托给一个IContentProvider的Binder对象完成的

如果我们Hook掉这个IContentProvider对象,那么对于ContentProvider的所有操作都会被我们拦截掉,这时候我们可以做进一步的操作来完成对于插件ContentProvider组件的支持。

  • 要拦截这个过程

    • 【方式1】:我们可以假装插件的ContentProvider是自己App的ContentProvider,也就是说,让acquireExistingProvider方法可以直接获取到插件的ContentProvider,这样我们就不需要欺骗AMS就能完成插件化了。
    • 【方式2】:当然,你也可以选择Hook掉AMS,让AMS的getContentProvider方法返回被我们处理过的对象,这也是可行的;但是,为什么要舍近求远呢?

7.2.2 进程内部ContentProvider获取和安装过程

从上文的分析暂时得出结论:

我们可以把插件的ContentProvider信息预先放在App进程内部,使得对于ContentProvider执行CURD操作的时候,可以获取到插件的组件,这样或许就可以实现插件化了。

具体来说,我们要做的事情就是让ActivityThread的acquireExistingProvider方法能够返回插件的ContentProvider信息

我们先分析这个方法的实现:

public final IContentProvider acquireExistingProvider(Context c, String auth, int userId, boolean stable) {synchronized (mProviderMap) {final ProviderKey key = new ProviderKey(auth, userId);final ProviderClientRecord pr = mProviderMap.get(key);if (pr == null) {return null;}// 略。。}
}
  • App内部自己的ContentProvider信息保存在ActivityThread类的mProviderMap

    • 这个map的类型是ArrayMap

我们当然可以通过反射修改这个成员变量,直接把插件的ContentProvider信息填进去,但是这个ProviderClientRecord对象如何构造?我们姑且看看系统自己是如果填充这个字段的。

在ActivityThread类中搜索一遍,发现调用mProviderMap对象的put方法的之后installProviderAuthoritiesLocked,而这个方法最终被installProvider方法调用。

在分析ContentProvider的获取过程中我们已经知道,不论是通过本进程的acquireExistingProvider还是借助AMS的getContentProvider得到ContentProvider,最终都会对这个对象执行installProvider操作,也就是**「安装」在本进程内部**

首先,如果之前没有“安装”过,那么holder为null

下面的代码会被执行

final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
if (provider == null) {Slog.e(TAG, "Failed to instantiate class " +info.name + " from sourceDir " +info.applicationInfo.sourceDir);return null;
}
if (DEBUG_PROVIDER) Slog.v(TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
  • 比较直观,直接load这个ContentProvider所在的类,然后用反射创建出这个ContentProvider对象;localProvider

    • 但是由于查询是需要进行跨进程通信的,在本进程创建出这个对象意义不大,所以我们需要取出ContentProvider承载跨进程通信的Binder对象IContentProvider;

创建出对象之后,接下来就是构建合适的信息,保存在ActivityThread内部,也就是mProviderMap:

if (localProvider != null) {ComponentName cname = new ComponentName(info.packageName, info.name);ProviderClientRecord pr = mLocalProvidersByName.get(cname);if (pr != null) {if (DEBUG_PROVIDER) {Slog.v(TAG, "installProvider: lost the race, "+ "using existing local provider");}provider = pr.mProvider;} else {holder = new IActivityManager.ContentProviderHolder(info);holder.provider = provider;holder.noReleaseNeeded = true;pr = installProviderAuthoritiesLocked(provider, localProvider, holder);mLocalProviders.put(jBinder, pr);mLocalProvidersByName.put(cname, pr);}retHolder = pr.mHolder;
} else {

7.2.2.1 思路尝试——本地安装

那么,了解了「安装」过程再结合上文的分析,我们似乎可以完成ContentProvider的插件化了——直接把插件的ContentProvider安装在进程内部就行了。

如果插件系统有多个进程,那么必须在每个进程都「安装」一遍,如果你熟悉Android进程的启动流程那么就会知道,这个安装ContentProvider的过程适合放在Application类中,因为每个Android进程启动的时候,App的Application类是会被启动的。

看起来实现ContentProvider的思路有了,但是这里实际上有一个严重的缺陷!:我们依然没有解决「共享」的问题。

我们只是在插件系统启动的进程里面的ActivityThread的mProviderMap给修改了,这使得只有通过插件系统启动的进程,才能感知到插件中的ContentProvider(因为我们手动把插件中的信息install到这个进程中去了);如果第三方的App想要使用插件的ContentProvider,那系统只会告诉它查无此人

那么,我们应该如何解决共享这个问题呢?

看来还是逃不过AMS的魔掌,我们继续跟踪源码,看看如果在本进程查询不到ContentProvider,AMS是如何完成这个过程的

7.2.3 AMS获取对应ContentProvider

首先我们查阅ActivityManagerService的getContentProvider方法,这个方法间接调用了getContentProviderImpl方法;getContentProviderImpl方法体相当的长,但是实际上只做了两件事件事(我这就不贴代码了,读者可以对着源码看一遍):

  1. 使用PackageManagerService的resolveContentProvider根据Uri中提供的auth信息查阅对应的ContentProivoder的信息ProviderInfo。
  2. 根据查询到的ContentProvider信息,尝试将这个ContentProvider组件安装到系统上。

7.2.3.1 查询ContentProvider组件的ProviderInfo信息过程

查询ContentProvider组件的过程看起来很简单,直接调用PackageManager的resolveContentProvider就能从URI中获取到对应的ProviderInfo信息:

@Override
public ProviderInfo resolveContentProvider(String name, int flags, int userId) {if (!sUserManager.exists(userId)) return null;// readersynchronized (mPackages) {final PackageParser.Provider provider = mProvidersByAuthority.get(name);PackageSetting ps = provider != null? mSettings.mPackages.get(provider.owner.packageName): null;return ps != null&& mSettings.isEnabledLPr(provider.info, flags, userId)&& (!mSafeMode || (provider.info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0)? PackageParser.generateProviderInfo(provider, flags,ps.readUserState(userId), userId): null;}
}

但是实际上我们关心的是,这个mProvidersByAuthority里面的信息是如何添加进PackageManagerService的,会在什么时候更新?

  • 在PackageManagerService这个类中搜索mProvidersByAuthority.put这个调用,会发现在scanPackageDirtyLI会更新mProvidersByAuthority这个map的信息

    • 接着往前追踪会发现:这些信息是在Android系统启动的时候收集的

也就是说,Android系统在启动的时候会扫描一些App的安装目录,典型的比如/data/app/*,获取这个目录里面的apk文件,读取其AndroidManifest.xml中的信息,然后把这些信息保存在PackageManagerService中

合理猜测:额外的,在系统启动之后,安装新的App也会触发对新App中AndroidManifest.xml的操作,感兴趣的读者可以自行翻阅源码。

现在我们知道,查询ContentProvider的信息来源在Android系统启动的时候已经初始化好了,这个过程对于我们第三方app来说是鞭长莫及,想要使用类似在进程内部Hack ContentProvider的查找过程是不可能的。

7.2.3.2 安装ContentProvider组件的过程

获取到URI对应的ContentProvider的信息之后,接下来就是把它安装到系统上了,这样以后有别的查询操作就可以直接拿来使用;

但是这个安装过程AMS是没有办法以一己之力完成的,想象一下:

App DemoA 查询App DemoB 的某个ContentProviderAppB,那么这个ContentProviderAppB必然存在于DemoB这个App中,AMS所在的进程(system_server)连这个ContentProviderAppB的类都没有,因此,AMS必须委托DemoB完成它的ContentProviderAppB的安装;这里就分两种情况:

  1. DemoB这个App已经在运行了,那么AMS直接通知DemoB安装ContentProviderAppB(如果B已经安装了那就更好了)
  2. 其二,DemoB这个app没在运行,那么必须把B进程唤醒,让它干活;

这个过程也就是ActivityManagerService的getContentProviderImpl方法所做的,如下代码:

if (proc != null && proc.thread != null) {if (!proc.pubProviders.containsKey(cpi.name)) {proc.pubProviders.put(cpi.name, cpr);try {proc.thread.scheduleInstallProvider(cpi);} catch (RemoteException e) {}}
} else {proc = startProcessLocked(cpi.processName,cpr.appInfo, false, 0, "content provider",new ComponentName(cpi.applicationInfo.packageName,cpi.name), false, false, false);if (proc == null) {return null;}
}
  1. 如果查询的ContentProvider所在进程处于运行状态,那么AMS会通过这个进程给AMS的ApplicationThread这个Binder对象完成scheduleInstallProvider调用,这个过程比较简单,最终会调用到目标进程的installProvider方法,而这个方法我们在上文已经分析过了。
  2. 如果ContentProvider所在的进程已经死亡,那么会调用startProcessLocked来启动新的进程,startProcessLocked有一系列重载函数,我们一路跟踪,发现最终启动进程的操作交给了Process类的start方法完成,这个方法通过socket与Zygote进程进行通信,通知Zygote进程fork出一个子进程,然后通过反射调用了之前传递过来的一个入口类的main函数,一般来说这个入口类就是ActivityThread,因此子进程fork出来之后会执行ActivityThread类的main函数
  • 在我们继续观察子进程ActivityThread的main函数执行之前,我们看看AMS进程这时候会干什么——startProcessLocked之后AMS进程和fork出来的DemoB进程分道扬镳,AMS会继续往下面执行。

我们暂时回到AMS的getContentProviderImpl方法:

  // Wait for the provider to be published...
synchronized (cpr) {while (cpr.provider == null) {if (cpr.launchingApp == null) {return null;}try {if (conn != null) {conn.waiting = true;}cpr.wait();} catch (InterruptedException ex) {} finally {if (conn != null) {conn.waiting = false;}}}
}

你没看错,一个死循环就是糊在上面:AMS进程会通过一个死循环等到进程B完成ContentProvider的安装,等待完成之后会把ContentProvider的信息返回给进程A。

那么,我们现在的疑惑是,进程B在启动之后,在哪个时间点会完成ContentProvider的安装呢?


【图App启动简要流程】

这个时间点就是:DemoB进程启动之后会执行ActivityThread类的handleBindApplication方法

这个方法相当之长,基本完成了App进程启动之后所有必要的操作;这里我们只关心ContentProvider相关的初始化操作,代码如下:

// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {List<ProviderInfo> providers = data.providers;if (providers != null) {//【核心】installContentProviders(app, providers);// For process that contains content providers, we want to// ensure that the JIT is enabled "at some point".mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);}
}// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
}try {mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
}

仔细观察以上代码,你会发现:**ContentProvider的安装比Application的onCreate回调还要早!!**因此,分析到这里我们已经明白了前面提出的那个问题,进程启动之后会在Applition类的onCreate 回调之前,在Application对象创建之后完成ContentProvider的安装。

然后不要忘了,我们的AMS进程还在那傻傻等待DemoB进程完成ContentProviderAppB的安装呢!在DemoB的Application的onCreate回调之前,DemoB的ContentProviderAppB已经安装好了,因此AMS停止等待,把DemoB安装的结果返回给请求这个ContentProvider的DemoA

我们必须对这个时序保持敏感,有时候就是失之毫厘,差之千里!!

到这里,有关ContentProvider的调用过程以及简要的工作原理我们已经分析完毕,关于它如何共享数据,如何使用匿名共享内存这部分不是插件化的重点,感兴趣的可以参考 Android应用程序组件Content Provider在应用程序之间共享数据的原理分析。

7.2.4 ContentProvider工作原理总结

在实现ContentProvider的插件化之前,通过分析这个组件的工作原理,我们可以得出它的一些与众不同的特性:

  1. ContentProvider本身是用来共享数据的,因此它提供一般的CURD服务;它类似HTTP这种无状态的服务,没有Activity,Service所谓的生命周期的概念,服务要么可用,要么不可用;对应着ContentProvider要么启动,要么随着进程死亡;而通常情况下,死亡之后还会被系统启动。所以,ContentProvider,只要有人需要这个服务,系统可以保证是永生的;这是与其他组件的最大不同;完全不用考虑生命周期的概念。
  2. ContentProvider被设计为共享数据,这种数据量一般来说是相当大的;熟悉Binder的人应该知道,Binder进行数据传输有1M限制,因此如果要使用Binder传输大数据,必须使用类似socket的方式一段一段的读,也就是说需要自己在上层架设一层协议;ContentProvider并没有采取这种方式,而是采用了Android系统的匿名共享内存机制,利用Binder来传输这个文件描述符,进而实现文件的共享;这是第二个不同,因为其他的三个组建通信都是基于Binder的,只有ContentProvider使用了Ashmem。
  3. 一个App启动过程中,ContentProvider组件的启动是非常早的,甚至比Application的onCreate还要早;我们可以利用这个特性结合它不死的特点,完成一些有意义的事情。
  4. ContentProvider存在优先查询本进程的特点,使得它的插件化甚至不需要Hook AMS就能完成。

7.3 思路分析

在分析ContentProvider的工作原理的过程中我们提出了一种插件化方案:在进程启动之初,手动把ContentProvider安装到本进程,使得后续对于插件ContentProvider的请求能够顺利完成。我们也指出它的一个严重缺陷,那就是它只能在插件系统内部掩耳盗铃,在插件系统之外,第三方App依然无法感知到插件中的ContentProvider的存在。

如果插件的ContentProvider组件仅仅是为了共享给其他插件或者宿主程序使用,那么这种方案可以解决问题;不需要Hook AMS,非常简单。

但是,如果希望把插件ContenProvider共享给整个系统呢?在分析AMS中获取ContentProvider的过程中我们了解到,ContentProvider信息的注册是在Android系统启动或者新安装App的时候完成的,而AMS把ContentProvider返回给第三方App也是在system_server进程完成;我们无法对其暗箱操作。

在完成Activity,Service组件的插件化之后,这种限制对我们来说已经是小case了:我们在宿主程序里面注册一个货真价实、被系统认可的StubContentProvider组件,把这个组件共享给第三方App;然后通过代理分发技术把第三方App对于插件ContentProvider的请求通过这个StubContentProvider分发给对应的插件

但是这还存在一个问题,由于第三方App查阅的其实是StubContentProvider,因此他们查阅的URI也必然是StubContentProvider的authority,要查询到插件的ContentProvider,必须把要查询的真正的插件ContentProvider信息传递进来。这个问题的解决方案也很容易,我们可以制定一个「插件查询协议」来实现。

举个例子,假设插件系统的宿主程序在AndroidManifest.xml中注册了一个StubContentProvider,它的Authority为com.test.host_authority;由于这个组件被注册在AndroidManifest.xml中,是系统认可的ContentProvider组件,整个系统都是可以使用这个共享组件的,使用它的URI一般为content://com.test.host_authority;那么,如果插件系统中存在一个插件,这个插件提供了一个PluginContentProvider,它的Authority为com.test.plugin_authorith,因为这个插件的PluginContentProvider没有在宿主程序的AndroidMainifest.xml中注册(预先注册就失去插件的意义了),整个系统是无法感知到它的存在的;

前面提到代理分发技术,也就是,我们让第三方App请求宿主程序的StubContentProvider,这个StubContentProvider把请求转发给合适的插件的ContentProvider就能完成了(插件内部通过预先installProvider可以查询所有的ContentProvider组件);这个协议可以有很多,比如说:如果第三方App需要请求插件的StubContentProvider,可以以content://com.test.host_authority/com.test.plugin_authorith去查询系统;也就是说,我们假装请求StubContentProvider,把真正的需要请求的PluginContentProvider的Authority放在路径参数里面,StubContentProvider收到这个请求之后,拿到这个真正的Authority去请求插件的PluginContentProvider,拿到结果之后再返回给第三方App

这样,我们通过「代理分发技术」以及「插件查询协议」可以完美解决「共享」的问题,开篇提到了我们之前对于Activity,Service组件插件化方案中对于「共享」功能的缺失,按照这个思路,基本可以解决这一系列问题。比如,对于第三方App无法绑定插件服务的问题,我们可以注册一个StubService,把真正需要bind的插件服务信息放在intent的某个字段中,然后在StubService的onBind中解析出这个插件服务信息,然后去拿到插件Service组件的Binder对象返回给第三方。

7.4 实现

7.4.1 预先installProvider

必须试用反射反射实现注册和实例化,保证其中的context上下文对象是有效的。 也就是说,要真实的注册插件Provider(保证内部的上下文),但是同时显示分发调用(保证能被系统调用)

实现预先installProvider,我们首先需要知道,所谓的「预先」到底是在什么时候?

前文我们提到过App进程安装ContentProvider的时机非常之早,在Application类的onCreate回调执行之前已经完成了;这意味着什么?

现在我们对于ContentProvider插件化的实现方式是通过「代理分发技术」,也就是说在请求插件ContentProvider的时候会先请求宿主程序的StubContentProvider;如果一个第三方App查询插件的ContentProvider,而宿主程序没有启动的话,AMS会启动宿主程序并等待宿主程序的StubContentProvider完成安装,一旦安装完成就会把得到的IContentProvider返回给这个第三方App;第三方App拿到IContentProvider这个Binder对象之后就可能发起CURD操作,如果这个时候插件ContentProvider还没有启动,那么肯定就会出异常;要记住,“这个时候”可能宿主程序的onCreate还没有执行完毕呢!!

所以,我们基本可以得出结论,预先安装这个所谓的「预先」必须早于Application的onCreate方法,在Android SDK给我们的回调里面,attachBaseContent这个方法是可以满足要求的,它在Application这个对象被创建之后就会立即调用。

解决了时机问题,那么我们接下来就可以安装ContentProvider了。

安装ContentProvider也就是要调用ActivityThread类的installProvider方法,这个方法需要的参数有点多,而且它的第二个参数IActivityManager.ContentProviderHolder是一个隐藏类,我们不知道如何构造,就算通过反射构造由于SDK没有暴露稳定性不易保证,我们看看有什么方法调用了这个installProvider。

installContentProviders这个方法直接调用installProvder看起来可以使用,但是它是一个private的方法,还有public的方法吗?继续往上寻找调用链,发现了installSystemProviders这个方法:

public final void installSystemProviders(List<ProviderInfo> providers) {if (providers != null) {installContentProviders(mInitialApplication, providers);}
}

但是,我们说过ContentProvider的安装必须相当早,必须在Application类的attachBaseContent方法内,而这个mInitialApplication字段是在onCreate方法调用之后初始化的

所以,如果直接使用这个installSystemProviders势必抛出空指针异常;因此,我们只有退而求其次,选择通过installContentProviders这个方法完成ContentProvider的安装

要调用这个方法必须拿到ContentProvider对应的ProviderInfo,这个我们在之前也介绍过,可以通过PackageParser类完成,当然这个类有一些兼容性问题,我们需要手动处理:

public class ProviderHelper {/*** 解析Apk文件中的 <provider>, 并存储起来* 主要是调用PackageParser类的generateProviderInfo方法** @param apkFile 插件对应的apk文件* @throws Exception 解析出错或者反射调用出错, 均会抛出异常*/public static List<ProviderInfo> parseProviders(File apkFile) throws Exception {Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);Object packageParser = packageParserClass.newInstance();// 首先调用parsePackage获取到apk对象对应的Package对象Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_PROVIDERS);// 读取Package对象里面的services字段// 接下来要做的就是根据这个List<Provider> 获取到Provider对应的ProviderInfoField providersField = packageObj.getClass().getDeclaredField("providers");List providers = (List) providersField.get(packageObj);// 调用generateProviderInfo 方法, 把PackageParser.Provider转换成ProviderInfoClass<?> packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider");Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");Class<?> userHandler = Class.forName("android.os.UserHandle");Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");int userId = (Integer) getCallingUserIdMethod.invoke(null);Object defaultUserState = packageUserStateClass.newInstance();// 需要调用 android.content.pm.PackageParser#generateProviderInfoMethod generateProviderInfo = packageParserClass.getDeclaredMethod("generateProviderInfo",packageParser$ProviderClass, int.class, packageUserStateClass, int.class);List<ProviderInfo> ret = new ArrayList<>();// 解析出intent对应的Provider组件for (Object service : providers) {ProviderInfo info = (ProviderInfo) generateProviderInfo.invoke(packageParser, service, 0, defaultUserState, userId);ret.add(info);}return ret;}
}

解析出ProviderInfo之后,就可以直接调用installContentProvider了:

public class ProviderHelper {/*** 在进程内部安装provider, 也就是调用 ActivityThread.installContentProviders方法** @param context you know* @param apkFile* @throws Exception*/public static void installProviders(Context context, File apkFile) throws Exception {List<ProviderInfo> providerInfos = parseProviders(apkFile);for (ProviderInfo providerInfo : providerInfos) {providerInfo.applicationInfo.packageName = context.getPackageName();}Log.d("test", providerInfos.toString());Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");Object currentActivityThread = currentActivityThreadMethod.invoke(null);Method installProvidersMethod = activityThreadClass.getDeclaredMethod("installContentProviders", Context.class, List.class);installProvidersMethod.setAccessible(true);installProvidersMethod.invoke(currentActivityThread, context, providerInfos);}
}

整个安装过程必须在Application类的attachBaseContent里面完成

/*** 一定需要Application,并且在attachBaseContext里面Hook* 因为provider的初始化非常早,比Application的onCreate还要早* 在别的地方hook都晚了。** @author weishu* @date 16/3/29*/
public class UPFApplication extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);try {File apkFile = getFileStreamPath("testcontentprovider-debug.apk");if (!apkFile.exists()) {Utils.extractAssets(base, "testcontentprovider-debug.apk");}File odexFile = getFileStreamPath("test.odex");// Hook ClassLoader, 让插件中的类能够被成功加载BaseDexClassLoaderHookHelper.patchClassLoader(getClassLoader(), apkFile, odexFile);//在进程内部安装provider, 也就是调用 ActivityThread.installContentProviders方法ProviderHelper.installProviders(base, getFileStreamPath("testcontentprovider-debug.apk"));} catch (Exception e) {throw new RuntimeException("hook failed", e);}}}

7.4.2 代理分发以及协议解析

把插件中的ContentProvider安装到插件系统中之后,在插件内部就可以自由使用这些ContentProvider了;要把这些插件共享给整个系统,我们还需要一个货真价实的ContentProvider组件来执行分发:

<providerandroid:name="com.example.weishu.contentprovider_management.StubContentProvider"android:authorities="com.example.weishu.contentprovider_management.StubContentProvider"android:process=":p"android:exported="true" />

第三方App如果要查询到插件的ContentProvider,必须遵循一个「插件查询协议」,这样StubContentProvider才能把对于插件的请求分发到正确的插件组件:

public class StubContentProvider extends ContentProvider {private static final String TAG = "StubContentProvider";public static final String AUTHORITY = "com.example.weishu.contentprovider_management.StubContentProvider";@Overridepublic boolean onCreate() {return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {//noinspection ConstantConditionsreturn getContext().getContentResolver().query(getRealUri(uri), projection, selection, selectionArgs, sortOrder);}@Overridepublic Uri insert(Uri uri, ContentValues values) {//noinspection ConstantConditionsreturn getContext().getContentResolver().insert(getRealUri(uri), values);}/*** 为了使得插件的ContentProvder提供给外部使用,我们需要一个StubProvider做中转;* 如果外部程序需要使用插件系统中插件的ContentProvider,不能直接查询原来的那个uri* 我们对uri做一些手脚,使得插件系统能识别这个uri;** 这里的处理方式如下:** 原始查询插件的URI应该为:* content://plugin_auth/path/query** 如果需要查询插件,需要修改为:** content://stub_auth/plugin_auth/path/query** 也就是,我们把插件ContentProvider的信息放在URI的path中保存起来;* 然后在StubProvider中做分发。** 当然,也可以使用QueryParamerter,比如:* content://plugin_auth/path/query/ ->  content://stub_auth/path/query?plugin=plugin_auth* @param raw 外部查询我们使用的URI* @return 插件真正的URI*/private Uri getRealUri(Uri raw) {String rawAuth = raw.getAuthority();if (!AUTHORITY.equals(rawAuth)) {Log.w(TAG, "rawAuth:" + rawAuth);}String uriString = raw.toString();uriString = uriString.replaceAll(rawAuth + '/', "");Uri newUri = Uri.parse(uriString);Log.i(TAG, "realUri:" + newUri);return newUri;}}

通过以上过程我们就实现了ContentProvider的插件化。需要说明的是,DroidPlugind的插件化与上述介绍的方案有一些不同之处:

  1. 首先DroidPlugin并没有选择预先安装的方案,而是选择Hook ActivityManagerNative,拦截它的getContentProvider以及publishContentProvider方法实现对于插件组件的控制;

    • 从这里可以看出它对ContentProvider与Service的插件化几乎是相同的,Hook才是DroidPlugin Style _.
  2. 然后,关于携带插件信息,或者说「插件查询协议」方面;DroidPlugin把插件信息放在查询参数里面,本文呢则是路径参数;这一点完全看个人喜好。

7.4.3 示例使用

public class MainActivity extends Activity {private static final String TAG = "MainActivity";// demo ContentProvider 的URIprivate static Uri URI = Uri.parse("content://com.example.weishu.testcontentprovider.TestContentProvider");static int count = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button query = new Button(this);query.setText("query");query.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Cursor cursor = getContentResolver().query(URI,null, null, null, null);assert cursor != null;while (cursor.moveToNext()) {int count = cursor.getColumnCount();StringBuilder sb = new StringBuilder("column: ");for (int i = 0; i < count; i++) {sb.append(cursor.getString(i) + ", ");}Log.d(TAG, sb.toString());}}});Button insert = new Button(this);insert.setText("insert");insert.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {ContentValues values = new ContentValues();values.put("name", "name" + count++);getContentResolver().insert(URI, values);}});LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);layout.addView(query);layout.addView(insert);setContentView(layout);}
}

7.5 小结

本文我们通过「代理分发技术」以及「插件查询协议」完成了ContentProvider组件的插件化,并且给出了对「插件共享组件」的问题的一般解决方案。

值得一提的是,系统的ContentProvider其实是lazy load的,也就是说只有在需要使用的时候才会启动对应的ContentProvider,而我们对于插件的实现则是预先加载,这里还有改进的空间,读者可以思考一下解决方案。

由于ContentProvider的使用频度非常低,而很多它使用的场景(比如系统)并不太需要「插件化」,因此在实际的插件方案中,提供ContentProvider插件化的方案非常之少;就算需要实现ContentProvider的插件化,也只是解决插件内部之间共享组件的问题,并没有把插件组件暴露给整个系统。我个人觉得,如果只是希望插件化,那么是否支持ContentProvider无伤大雅,但是,如果希望实现虚拟化或者说容器技术,所有组件是必须支持插件化的。

参考文献

  • csdn scholarSu:Android插件化

    • Android插件化之Activity生命周期处理
  • Android插件化原理解析
    • Android插件化原理解析——Hook机制之动态代理
    • Android插件化原理解析——Hook机制之Binder Hook
    • Android 插件化原理解析——Hook机制之AMS&PMS
    • Android 插件化原理解析——Activity生命周期管理
    • Android 插件化原理解析——插件加载机制
    • Android插件化原理解析——广播的管理
    • Android 插件化原理解析——Service的插件化
    • Android插件化原理解析——ContentProvider的插件化

(4.6.29.3)插件化之代码加载:启动Activity等四大组件之hook方式相关推荐

  1. 携程Android App插件化和动态加载实践

    转载自:http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading?email=947091870@qq.com 编者按:本文为携程无 ...

  2. java类加载器 架构 设计_类加载器(DexClassLoader)与插件化(动态加载)

    类加载器与插件化解析 2.1 类装载器 DexClassLoader 首先,我们需要了解关于java代码本地import的一些知识: import中所引用的类有两个特点: 1.必须存在于本地,当程序运 ...

  3. Android插件化原理—ClassLoader加载机制

    前面<Android 插件化原理学习 -- Hook 机制之动态代理>一文中我们探索了一下动态代理 hook 实现了 启动没有在 AndroidManifest.xml 中显式声明的 Ac ...

  4. 唯一插件化Replugin源码及原理深度剖析--插件的安装、加载原理

    上一篇 唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理 在Replugin的初始化过程中,我将他们分成了比较重要3个模块,整体框架的初始化.hook系统ClassLoader.插件的 ...

  5. HTML基础和JSP了解及JSP中代码加载顺序

    HTML入门基础教程 html是什么,什么是html通俗解答: html是hypertext markup language的缩写,即超文本标记语言.html是用于创建可从一个平台移植到另一平台的超文 ...

  6. Qt5笔记之Qt5插件的生成与加载及json文件的读取

    一.前言 1. Qt Plugin按照应用场景分两种类型: (1)The High-Level API:用于扩展Qt本身的功能,需放在Qt安装目录下的指定目录里: (2)The Lower-Level ...

  7. 【转载】一行代码加载网络图片到ImageView——Android Picasso

    原文链接:一句代码加载网络图片到ImageView--Android Picasso  注意:此处使用下面代码需要先配置一下gradle,下载所需包. 具体操作如下图: compile 'com.sq ...

  8. swift 代码加载xib storyboard

    初学swift,代码加载xib storyboard -.加载xib override init(nibName nibNameOrNil: String?, bundle nibBundleOrNi ...

  9. XIB总结(代码加载xib或xib拖xib)

    view.xib的说明 View的custom cass是关联自身的 File's owner是关联任意类的 方式一.xib拖xib 用File's owner 方式二.代码加载,不向某个控制器关联控 ...

最新文章

  1. php 空格zhuanyi,php写的将逗号、空格、回车分...-php字符转义的相关注意事项-IIS环境中防止本地用户用fsockopen进行DDOS攻击的方法_169IT.COM...
  2. linux user32.lib,USER32!__ClientLoadLibrary定位
  3. win10玩cf不能全屏_游戏莫名卡顿四招搞定!Win10游戏优化教程
  4. Java学习笔记10-2——MyBatis
  5. 短程调度 中程调度 长程调度
  6. Unity Shader 记录
  7. window.parent.document jquery
  8. 关于学习js的Promise的心得体会
  9. MAC在命令行运行不带窗口的 Emacs -- 比窗口模式占用的资源更少一些
  10. The overload Pattern
  11. 尚硅谷JDBC学习笔记
  12. Mac如何读写NTFS硬盘,NTFSTool让Mac也可以轻松读写NTFS硬盘
  13. hpe服务器中ilo的作用,产品技术-HPE iLO-新华三集团-H3C
  14. 设计模式——Spring注解编程模型
  15. js仿照 蚂蚁森林 效果
  16. 快手火山抖音视频(包含其他视频)跨平台操作搬运,下载,消重,全自动操作解放双手...
  17. ADV7441驱动EDID配置及声音问题
  18. Vue实现vr看房效果
  19. 建议收藏5款办公必备电脑软件
  20. 八、.net core(.NET 6)配置读取appsettings文件内容的通用功能

热门文章

  1. D1s RDC2022纪念版开发板开箱评测及点屏教程
  2. 数字旋转方阵问题-分治法
  3. FZU 2230 翻翻棋
  4. opencv-python基础用法详细代码-图片加载-ROI-边缘滤波-二值化-轮廓提取-膨胀腐蚀等
  5. Java小项目-Domineering demo
  6. 米家扫地机器人沒有系统重置键_米家扫地机器人如何恢复出厂设置
  7. linux错误码5 io error,IO出错常用错误代码
  8. 诊所要使用微信预约系统,需准备哪些资料?
  9. 中望3D 2021 合并面
  10. 调侃计算机专业的句子,调侃作业多的幽默句子 用于关于感动作业的幽默语句...