插件化技术和热修复技术都属于动态加载技术,从普及率的角度来看,插件化没有热修复的普及率高,主要原因是占大多数的中小型应用很少也没有必要去采用插件化技术。虽然插件化的普及率现在还不算高,但是理解插件化的原理对于应用开发的技术提升有很大帮助,可以使你更好的地理解系统源码,并将系统源码和应用开发相结合。
动态加载技术
在讲到插件化原理之前,需要先了解它的前身:动态加载技术。在传统Android开发中,一旦应用程序的代码被打包成APK并上传到各个应用市场,我们就不能修改App的源码了,只能通过服务器来控制程序中预留的分支代码逻辑。但是大多数情况下我们都无法提前预知新的需求和突发情况,也就无法提前在代码中预留分支,这个时候就需要采用动态加载技术:在App运行时,动态加载一些程序中原本不存在的可执行文件并执行这些文件中 的代码逻辑。在Android中,可执行文件分为两种,一种是动态链接库so,另一种是dex相关文件(dex以及包含dex的jar/apk文件)。在之前的文章Android热修复及原理总结中也提到了上述可执行文件的加载,这是因为热修复技术本身也是由动态加载技术派生出来的。
插件化的定义
插件化的App有宿主和插件两部分组成,宿主是指先被安装在手机上的APK,就是平常我们加载的普通APK。插件一般指的是经过处理的APK、so和dex等文件,插件可以被宿主动态加载并执行,有的插件也可以作为App独立运行。所以,插件化的定义可以理解为:将一个应用按照插件的方式进行改造的过程。
Activity的插件化
四大组件的插件化是插件化技术的核心知识点,而Activity的插件化又是四大组件中最重要的。Activity的插件化实质上指的就是启动插件中的Activity,它和普通的Activity区别在于插件中的Activity,没有在宿主App的AndroidManifest.xml文件中注册。
Activity的插件化主要有三种实现方式,分别是反射实现,接口实现和Hook技术实现。反射实现会对性能有所影响,主流的插件化框架没有采用这种方式,关于接口实现,可以阅读Dynamic-load-apk的源码,这里不多做介绍,目前Hook技术实现是主流,本文将主要介绍Hook技术实现的Activity插件化。
Hook技术实现主要有两种解决方案,一种是通过Hook IActivityManager来实现,另一种是Hook Intrumentation实现。主要也就是Hook点的选择不同,要了解Hook点的选择,我们需要从整体上再来回顾下Activity的启动过程。
普通的Actiivty(A->B)启动整体过程回顾
在应用程序进程中的ActivityA向AMS请求创建ActivityB,AMS会对ActivityB的生命周期和栈进行管理,校验ActivityB等,如果ActivityB满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动ActivityB。
Hook IActivityManager方案的实现
如上图所示,Activity的启动必须经过AMS的校验,而这个校验过程的关键,就是要检查Activity是否已经在AndroidManifest.xml文件中注册。AMS存在于SystemServer进程中,我们无法直接修改,只能在应用程序进程中做文章。可以采用预先占坑的方式来解决没有在AndroidManifest.xml文件中注册的问题,具体做法就是再上图的【步骤1】之前使用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验。接着在【步骤2】之后用插件的Activity替换占坑的Activity。
由于篇幅有限,这里省略了插件Activity的加载逻辑,想了解的同学可以看看这篇关于ClassLoader的文章。首先直接创建一个TargetActivity代表已经加载进来的插件Activity。
public class TargetActivity extends Activity {
private final static String TAG = "TargetActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target);
Log.e(TAG,"onCreate");
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG,"onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG,"onResume");
}
}
代码很简单,没有多余的逻辑,我们这里主要是通过在生命周期函数中打印log,来验证TargetActivity是否启动。接着我们在创建一个用来占坑的SubActivity。代码如下:
public class SubActivity extends AppCompatActivity {
private final static String TAG = "SubActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub);
Log.e(TAG,"onCreate");
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG,"onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG,"onResume");
}
}
然后在AndroidManifest.xml中注册SubActivity。
TargetActivity用来代表插件中的Activity,因此不需要在AndroidManifest.xml中进行注册。如果我们直接在MainActivity中启动TargetActivity,通常情况下肯定是要报错的。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button start_one_btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start_one_btn = findViewById(R.id.start_one_btn);
start_one_btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.start_one_btn:
Intent intentOne = new Intent(this,TargetActivity.class);
startActivity(intentOne);
break;
}
}
}
接下来就是本方案的重点了。
使用占坑的SubActivity通过AMS的校验
为了防止上面提到的报错,需要将启动的TargetActivity替换成SubActivity。用SubActivity来通过AMS的校验。这里需要指出Android 8.0与Android 7.0的AMS的相关类的代码和结构有一些差别,主要是Android8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager,直接采用AIDL进行进程间通信。Android7.0的Activity的启动会调用到ActivityManagerNative的getDefault方法,代码如下:
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
...
static public IActivityManager getDefault() {
return gDefault.get();
}
...
private static final Singleton gDefault = new Singleton() {
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类来实现单例,而且Singelton类型的对象gDefault又是静态对象,因此IActivityManager是一个比较好的Hook点。Android8.0的Activity的启动会调用到ActivityManager的getService方法,代码如下:
public class ActivityManager {
...
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
...
private static final Singleton IActivityManagerSingleton =
new Singleton() {
@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类来实现单例,这样就可以确定了无论是Android 8.0或Android 7.0,IActivityManager都是一个比较好的Hook点。同时我们还需要了解下Singleton类的源码,便于后面的Hook实现:
public abstract class Singleton {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
接下来开始定义替换IActivityManager的代理类IActivityManagerProxy,这里用到的是动态代理机制,代码如下:
public class IActivityManagerProxy implements InvocationHandler {
private Object iActivityManager;
public final static String TARGET_NAME = "target_activity";
public IActivityManagerProxy(Object iActivityManager){
this.iActivityManager = iActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("startActivity")){
Intent intent = null;
int index = 0;
for(int i = 0;i < args.length;i++){
if(args[i] instanceof Intent){
index = i;
intent = (Intent)args[i];
break;
}
}
if(intent.getComponent().getClassName().contains("TargetActivity")){
Intent proxyIntent = new Intent();
proxyIntent.putExtra(TARGET_NAME,intent);
proxyIntent.setClassName("com.zacky.activityplugintest",
"com.zacky.activityplugintest.SubActivity");
args[index] = proxyIntent;
}
}
return method.invoke(iActivityManager, args);
}
}
简述其逻辑:拦截startActivity方法的调用,获取参数args中的第一个Intent对象,判断它要启动的组件是不是TargetActivity,如果是就创建一个新的Intent对象proxyIntent,用来启动SubActivity,并把这个启动TargetActivity的Intent保存到proxyIntent中,用于之后还原TargetActivity。然后将参数args中的Intent参数替换成proxyIntent。这样启动的目标就变成了SubActivity,用来通过AMS的校验。最后用IActivityManagerProxy来替换IActivityManager。代码如下:
private void initHookActivityManagerAndH() throws Exception {
Object defaultSingleton;
if(Build.VERSION.SDK_INT >= 26) {
Class activityManagerClass = Class.forName("android.app.ActivityManager");
Field songletonField =
activityManagerClass.getDeclaredField("IActivityManagerSingleton");
songletonField.setAccessible(true);
defaultSingleton = songletonField.get(activityManagerClass);
}else{
Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
Field songletonField = activityManagerClass.getDeclaredField("gDefault");
songletonField.setAccessible(true);
defaultSingleton = songletonField.get(activityManagerClass);
}
Class singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iActivityManager = mInstanceField.get(defaultSingleton);
Object iActivityManagerProxy = Proxy.newProxyInstance(getClassLoader(),
new Class[]{Class.forName("android.app.IActivityManager")},
new IActivityManagerProxy(iActivityManager));
mInstanceField.set(defaultSingleton,iActivityManagerProxy);
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField =
activityThreadClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object sCurrentActivityThread = sCurrentActivityThreadField.get(activityThreadClass);
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(sCurrentActivityThread);
Class handlerCLass = Class.forName("android.os.Handler");
Field mCallbackField = handlerCLass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH,new PluginHandlerCallback((Handler) mH));
}
主要也就是用到反射,这个Hook的方法其实可以在启动TargetActivity之前的任何时机调用都行。我这里是在自定义Application的onCreate方法中调用的,代码如下:
public class TestApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
try {
initHookActivityManagerAndH();
} catch (Exception e) {
e.printStackTrace();
}
}
}
还原插件Activity
细心的同学肯定发现了initHookActivityManagerAndH方法中,还通过反射getActivityThread类中的mH对象设置了mCallback。这个就和还原TargetActivity有关。后面会讲到。
上面已经演示了用占坑的SubActivity替换TargetActivity,通过了AMS的校验,但我们的真实目的是启动代表插件的TargetActivity,所以还需要用TargetActivity来替换占坑的SubActivity。替换的时机就是上面的Activity启动过程演示图中的【步骤2】之后。简单来说就是,通过AMS的校验后,AMS就会请求应用程序进程中的ActivityThread去创建并启动目标Activity。ActivityThread会通过H类将代码逻辑的处理切换到主线程中,H类是ActivityThread的内部类并继承自Handler。了解Activity启动过程的朋友对他肯定不陌生。但是由于Android 9.0的源码,将ActivityThread类中的相关代码逻辑做了一些改动,这里有必要简单的介绍下:
Android 9.0引入了ClientTransaction、ClientLifecycleManager和ClientLifecycleHandler来辅助管理Activity的生命周期。以Activity的启动过程为例,在ActivityStackSupervisor.realStartActivityLocked方法中为ClientTransaction对象添加LaunchActivityItem的callback,然后设置当前的生命周期状态,再调用ClientLifecycleManager.scheduleTransaction方法执行。这个方法的内部会调用ClientTransaction的transaction方法,transaction方法又会去调用IApplicationThread的scheduleTransaction方法,转而调用ActivityThread的scheduleTransaction方法,这个方法继承自ClientLifecycleHandler。在ClientLifecycleHandler的scheduleTransaction方法中,会将transaction通过Message传递给mH来处理,最终由TransactionExecutor来执行LaunchActivityItem的execute方法,execute方法里面的代码,就是Android9.0之前在mH的handleMessage方法中处理LAUNCH_ACTIVITY的相关代码,创建ActivityClientRecord对象,调用handleLaunchActivity方法,创建和启动Activity。对相关细节感兴趣的同学,可以参考(Android 9.0)Activity启动流程源码分析
ActivityThread通过mH将代码逻辑的处理切换到主线程中执行,一个应用程序进程中,只有一个ActivityThread对象sCurrentActivityThread,代表主线程。每个ActivityThread对象包含一个final修饰的mH对象,也就是说每个应用程序进程中也就只有唯一的mH对象,而Activity的生命周期管理肯定会经过mH的handleMessage方法来转发处理。所以mH可以还原TargetActivity的Hook点。H继承自Handler。了解Handler机制的同学,肯定都知道,Handler的dispatchMessage方法分发Message时,会先判断mCallback是否为null,如果不为null,就会执行mCallback的handleMessage方法,否则执行handler自身的handleMessage方法。代码如下:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
那么Hook mH的思路就出来了,我们可以用自定义的Callback来替换mH的mCallback,Callback定义如下
public class PluginHandlerCallback implements Handler.Callback {
public static int EXECUTE_TRANSACTION = 159;
Handler handler;
public PluginHandlerCallback(Handler handler){
this.handler = handler;
}
@Override
public boolean handleMessage(@NonNull Message msg) {
try {
if(EXECUTE_TRANSACTION == msg.what){
Intent intent;
Class activityClientRecordClass =
Class.forName("android.app.servertransaction.ClientTransaction");
Method getCallbacksMethod = activityClientRecordClass.getDeclaredMethod("getCallbacks");
getCallbacksMethod.setAccessible(true);
List mActivityCallbacks = (List) getCallbacksMethod.invoke(msg.obj,null);
if(mActivityCallbacks != null){
for (Object item : mActivityCallbacks){
Class launchActivityItemClass = Class.forName("android.app.servertransaction.LaunchActivityItem");
if(item.getClass() == launchActivityItemClass){
Field mIntentField = launchActivityItemClass.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
intent = (Intent)mIntentField.get(item);
if(intent.getComponent().getClassName().contains("SubActivity")){
if(intent!=null && intent.hasExtra(TARGET_NAME)
&& intent.getParcelableExtra(TARGET_NAME) instanceof Intent){
Intent targetIntent = intent.getParcelableExtra(TARGET_NAME);
mIntentField.set(item,targetIntent);
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
handler.handleMessage(msg);
return true;
}
}
在PluginHandlerCallback的构造方法中传入一个Handler对象,实际使用时就是mH。在定义handleMessage方法的逻辑之前我们需要了解下ClientTransaction类的结构,因为从上面提到的Android9.0的Activity启动过程中,有mH处理的Message对象的obj属性是一个ClientTransaction对象。ClientTransaction代码如下:
public class ClientTransaction implements Parcelable, ObjectPoolItem {
/** A list of individual callbacks to a client. */
private List mActivityCallbacks;
...
/** Get the list of callbacks. */
@Nullable
List getCallbacks() {
return mActivityCallbacks;
}
}
而最终调用handleLaunchActivity方法的LaunchActivityItem对象,就存放在mActivityCallbacks列表中。
public class LaunchActivityItem extends ClientTransactionItem {
...
private Intent mIntent;
...
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
由此我们就可以理清handleMessage方法中的代码逻辑了。我们首先要根据msg.what来找到和Activity的生命周期管理相关的Message,通过反射找到LaunchActivityItem对象和它的mIntent变量,这个mIntent对象就是最终创建和启动Activity的Intent,我们需要做的就是用最初启动TargetActivity的Intent来替换这个mIntent。在handleMessage方法的最后,调用handler.handleMessage(msg)来处理mH的原有逻辑。
至此,PluginHandlerCallback的定义就对应了initHookActivityManagerAndH方法中,通过反射来Hook mH和mCallback的那一段代码。
代码讲解完毕,运行程序,如点击按钮启动TargetActivity,查看Logcat可以看到程序不但没有报错,TargetActivity也成功创建并启动。
Hook Intrumentation方案的实现
上文中提到了Activity的插件化,除了Hook IActivityManager方案之外,还可以通过Hook Intrumentation来实现。首先还是回顾下Activity启动过程,Intrumentation的作用:
- 在Activity类的startActivityForResult方法中会调用Instrumentation的execStartActivity方法,在这个方法里面就会AMS的startActivity方法,可以说,mInstrumentation的execStartActivity方法是Activity启动流程从应用程序进程转到AMS所在的SystemServer进程的过渡。
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);
...
}
- 在ActivityThread的performLaunchActivity方法中会调用Instrumentation的newActivity方法来创建Activity的实例。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
}
可以看到,刚好一个是在AMS的校验之前,一个是在通过AMS校验之后。可以作为合适的Hook点。Hook Intrumentation方案比Hook IActivityManager方案实现要简单一些,由于Android不同版本的源码对IActivityManager的实现会有变化,相比之下,Intrumentation在Activity的启动过程中的作用和方法的调用时机并没有什么变化。并且现在人气很高的插件化开源框架VirtualAPK,也是用Hook Intrumentation方案来实现Activity的插件化。
Hook Intrumentation方案的实现同样需要用到占坑的Activity,所以这里和Hook IActivityManager方案的演示代码有重复的地方,不多赘述。
首先,我们自定义一个Instrumentation,代码如下:
public class ProxyInstrumentation extends Instrumentation {
public final static String TARGET_NAME = "target_activity";
private Instrumentation instrumentation;
private PackageManager mPackageManager;
public ProxyInstrumentation(Instrumentation instrumentation,PackageManager mPackageManager){
this.instrumentation = instrumentation;
this.mPackageManager = mPackageManager;
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options){
List infos = mPackageManager.queryIntentActivities(intent,PackageManager.MATCH_ALL);
if(infos==null || infos.size()==0){
Intent proxyIntent = new Intent();
proxyIntent.putExtra(TARGET_NAME,intent);
proxyIntent.setClassName("com.zacky.activityplugintest",
"com.zacky.activityplugintest.SubActivity");
try {
Method execStartActivityMethod =
Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);
execStartActivityMethod.setAccessible(true);
ActivityResult activityResult = (ActivityResult)execStartActivityMethod.invoke(instrumentation,
who,contextThread,token,target,proxyIntent,requestCode,options);
return activityResult;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if(intent.hasExtra(TARGET_NAME) && intent.getParcelableExtra(TARGET_NAME) instanceof Intent){
Intent targetIntent = intent.getParcelableExtra(TARGET_NAME);
return super.newActivity(cl, targetIntent.getComponent().getClassName(), targetIntent);
}else{
return super.newActivity(cl, className, intent);
}
}
}
因为Instrumentation类不是接口,不能用动态代理实现,我们用自定义的ProxyInstrumentation继承自Instrumentation。
重写execStartActivity方法,判断待启动的Activity是否已经在AndroidManifest.xml中注册。如果没有,就创建一个启动SubActivity的Intent对象proxyIntent,用来替换原来的intent。同时将intent保存在proxyIntent中,用于之后还原TargetActivity。然后通过反射调用构造方法中传进来的instrumentation对象的execStartActivity方法,这样就实现了用SubActivity来通过AMS的验证。
重写newActivity方法,实现TargetActivity的还原。判断也很简单。如果传进来的参数intent里包含了启动TargetActivity的Intent,调用父类Instrumentation的newActivity方法时,就传入TargetActivity的类名。这样发就完成了TargetActivity的还原。
然后,编写initHookIntrumentation方法,用自定义的ProxyInstrumentation,替换代表当前应用程序主线程的ActivtyThread类型的对象sCurrentActivityThread的mInstrumentation。代码如下:
private void initHookIntrumentation() throws Exception{
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField =
activityThreadClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object sCurrentActivityThread = sCurrentActivityThreadField.get(activityThreadClass);
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(sCurrentActivityThread);
ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(mInstrumentation,getPackageManager());
mInstrumentationField.set(sCurrentActivityThread,proxyInstrumentation);
}
笔者这里同样是在自定义Application的onCreate方法中调用initHookIntrumentation方法。然后运行程序,如点击按钮启动TargetActivity,查看Logcat可以看到程序不但没有报错,TargetActivity也成功创建并启动。
总结
实现Activity的插件化,实际上就是启动插件中未在AndroidManifest文件中注册的Activity。主要的方案就是先用一个已在AndroidManifest.xml中注册的Activity来占坑,用来通过AMS的校验,接着在合适的时机用插件Activity替换占坑的Activity。
本文参考:
《Android进阶解密》
(Android 9.0)Activity启动流程源码分析