title: " Android插件化原理(一)Activity插件化"
date: 2018-5-28 00:16
photos:
- https://s2.ax1x.com/2019/05/31/VlkKJA.jpg
tag: - Android应用层
- Android插件化原理
categories: - Android应用层
本文首发于微信公众号「后场村刘皇叔」
关联系列
Android深入四大组件系列
Android解析AMS系列
Android解析ClassLoader系列
前言
四大组件的插件化是插件化技术的核心知识点,而Activity插件化更是重中之重,Activity插件化主要有三种实现方式,分别是反射实现、接口实现和Hook技术实现。反射实现会对性能有所影响,主流的插件化框架没有采用此方式,关于接口实现可以阅读dynamic-load-apk的源码,这里不做介绍,目前Hook技术实现是主流,因此本篇文章主要介绍Hook技术实现。
Hook技术实现主要有两种解决方案 ,一种是通过Hook IActivityManager来实现,另一种是Hook Instrumentation实现。在讲到这两个解决方案前,我们需要从整体上了解Activity的启动流程。
1.Activity的启动过程回顾
Activity的启动过程主要分为两种,一种是根Activity的启动过程,一种是普通Activity的启动过程。关于根Activity的启动过程在在介绍过,这里来简单回顾下,如下图所示。
图 1
首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动后,AMS会请求应用程序进程创建并启动根Activity。
普通Activity和根Activity的启动过程大同小异,但是没有这么复杂,因为不涉及应用程序进程的创建,跟Laucher也没关系,如下图所示。
图2
上图抽象的给出了普通Acticity的启动过程。在应用程序进程中的Activity向AMS请求创建普通Activity(步骤1),AMS会对
这个Activty的生命周期管和栈进行管理,校验Activity等等。如果Activity满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动普通Activity(步骤2)。
2.Hook IActivityManager方案实现
AMS是在SystemServer进程中,我们无法直接进行修改,只能在应用程序进程中做文章。可以采用预先占坑的方式来解决没有在AndroidManifest.xml中显示声明的问题,具体做法就是在上图步骤1之前使用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验。
接着在步骤2之后用插件Activity替换占坑的Activity,接下来根据这个解决方案我们来实践一下。
2.1 注册Activity进行占坑
为了更好的讲解启动插件Activity的原理,这里省略了插件Activity的加载逻辑,直接创建一个TargetActivity来代表已经加载进来的插件Activity,接着我们再创建一个SubActivity用来占坑。在AndroidManifest.xml中注册SubActivity,如下所示。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.liuwangshu.pluginactivity">S
<application
...
<activity android:name=".StubActivity"/>
</application>
</manifest>
TargetActivity用来代表已经加载进来的插件Activity,因此不需要在AndroidManifest.xml进行注册。如果我们直接在MainActivity中启动TargetActivity肯定会报错(android.content.ActivityNotFoundException异常)。
2.2 使用占坑Activity通过AMS验证
为了防止报错,需要将启动的TargetActivity替换为SubActivity,用SubActivity来通过AMS的验证。在第六章时讲过Android 8.0与7.0的AMS家族有一些差别,主要是Android 8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager,直接采用AIDL来进行进程间通信。
Android7.0的Activity的启动会调用ActivityManagerNative的getDefault方法,如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java
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;
}
};
getDefault方法返回了IActivityManager类型的对象,IActivityManager借助了Singleton类来实现单例,而且gDefault又是静态的,因此IActivityManager是一个比较好的Hook点。
Android8.0的Activity的启动会调用ActivityManager的getService方法,如下所示。
frameworks/base/core/java/android/app/ActivityManager.java
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
同样的,getService方法返回了了IActivityManager类型的对象,并且IActivityManager借助了Singleton类来实现单例,确定了无论是Android7.0还是Android8.0,IActivityManager都是比较好的Hook点。Singleton类如下所示,后面会用到。
frameworks/base/core/java/android/util/Singleton.java
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
由于Hook需要多次对字段进行反射操作,先写一个字段工具类FieldUtil:
FieldUtil.java
public class FieldUtil {
public static Object getField(Class clazz, Object target, String name) throws Exception {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.get(target);
}
public static Field getField(Class clazz, String name) throws Exception{
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field;
}
public static void setField(Class clazz, Object target, String name, Object value) throws Exception {
Field field = clazz.getDeclaredField(name);
fi