滴滴插件化框架VirtualAPK原理解析之插件Activity管理

Posted 伯努力不努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了滴滴插件化框架VirtualAPK原理解析之插件Activity管理相关的知识,希望对你有一定的参考价值。

上周末,滴滴与360都开源了各自的插件化框架,VirtualAPK与RePlugin,作为一个插件化方面的狂热研究者,在周末就迫不及待的下载了Virtualapk框架来进行研究,本篇博客带来的是VirtualAPK原理解析的第一篇Activity管理,博客只是自己的理解,小弟才疏学浅,可能有很多理解不对的地方,欢迎各位大神指出。(看博客之前,请大家先下载VirtualVirtualAPKapk的项目,https://github.com/didi/VirtualAPK

Hook ActivityManagerService

在解释VirtualAPK是如何对Activity进行管理之前,有必要说一下ActivityManagerService,android的四大组件都需要与它打交道,它主要为四大组件做了这些事情:

1.startActivity最终调用了AMS的startActivity方法,实现了Activity的启动;Activity的生命周期回调,也在AMS中完成;

//ActivityManagerService.java
 @Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) 
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
            resultWho, requestCode, startFlags, profilerInfo, options,
            UserHandle.getCallingUserId());
    

2.startService,bindService最终调用到AMS的startService和bindService方法;

//ActivityManagerService.java
 public int bindService(IApplicationThread caller, IBinder token,
            Intent service, String resolvedType,
            IServiceConnection connection, int flags, int userId) 
        enforceNotIsolatedCaller("bindService");

        // Refuse possible leaked file descriptors
        if (service != null && service.hasFileDescriptors() == true) 
            throw new IllegalArgumentException("File descriptors passed in Intent");
        

        synchronized(this) 
            return mServices.bindServiceLocked(caller, token, service, resolvedType,
                    connection, flags, userId);
        
    

3.动态广播的注册和接收在AMS中完成(静态广播在PMS中完成)

 public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) 
        enforceNotIsolatedCaller("registerReceiver");
        int callingUid;
        int callingPid;
        synchronized(this) 
            ProcessRecord callerApp = null;
            if (caller != null) 
                callerApp = getRecordForAppLocked(caller);
                if (callerApp == null) 
                    throw new SecurityException(
                            "Unable to find app for caller " + caller
                            + " (pid=" + Binder.getCallingPid()
                            + ") when registering receiver " + receiver);
                
                if (callerApp.info.uid != Process.SYSTEM_UID &&
                        !callerApp.pkgList.containsKey(callerPackage) &&
                        !"android".equals(callerPackage)) 
                    throw new SecurityException("Given caller package " + callerPackage
                            + " is not running in process " + callerApp);
                
                callingUid = callerApp.info.uid;
                callingPid = callerApp.pid;
             else 
                callerPackage = null;
                callingUid = Binder.getCallingUid();
                callingPid = Binder.getCallingPid();
            

            userId = this.handleIncomingUser(callingPid, callingUid, userId,
                    true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);

            List allSticky = null;

            // Look for any matching sticky broadcasts...
            Iterator actions = filter.actionsIterator();
            if (actions != null) 
                while (actions.hasNext()) 
                    String action = (String)actions.next();
                    allSticky = getStickiesLocked(action, filter, allSticky,
                            UserHandle.USER_ALL);
                    allSticky = getStickiesLocked(action, filter, allSticky,
                            UserHandle.getUserId(callingUid));
                
             else 
                allSticky = getStickiesLocked(null, filter, allSticky,
                        UserHandle.USER_ALL);
                allSticky = getStickiesLocked(null, filter, allSticky,
                        UserHandle.getUserId(callingUid));
            

            // The first sticky in the list is returned directly back to
            // the client.
            Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null;

            if (DEBUG_BROADCAST) Slog.v(TAG, "Register receiver " + filter
                    + ": " + sticky);

            if (receiver == null) 
                return sticky;
            

            ReceiverList rl
                = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
...........

4.getContentResolver最终从AMS的getContentProvider获取到ContentProvider

  @Override
    public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String name, int userId, boolean stable) 
        enforceNotIsolatedCaller("getContentProvider");
        if (caller == null) 
            String msg = "null IApplicationThread when getting content provider "
                    + name;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        
        // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
        // with cross-user grant.
        return getContentProviderImpl(caller, name, null, stable, userId);
    

AMS以Binder方式提供给应用程序使用的系统服务,所以我们要处理插件中的四大组件,必须要Hook掉AMS进行相应的处理。

startActivity与AMS的关系

前面我们说过Activity的启动最终会调用到了AMS的方法进行启动,接下来我们就先分析这一个过程。

在开发中,我们使用startActivity有两种形式:
1.直接使用Context类的startActivity方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个Flag。
2.调用被Activity类重载过的startActivity方法,通常在我们的Activity中直接调用这个方法就是这种形式;

context的startActivity
先来分析第一种情况,context的startActivity是一个抽象方法

public abstract void startActivity(Intent intent);

Activity,Service等并没有直接继承Context,而是继承了ContextWrapper,所以我们需要看ContextWrapper里面的startActivity方法

@Override
    public void startActivity(Intent intent) 
        mBase.startActivity(intent);
    

我们可以得知,startActivity方法最终会通过mBase来完成,它的真正实现是ContextImpl类

 @Override
    public void startActivity(Intent intent) 
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    

在继续往下追寻,会看到如下代码:

  @Override
    public void startActivity(Intent intent, Bundle options) 
        warnIfCallingFromSystemProcess();
        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?");
        
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intent, -1, options);
    

看到这里,你是不是突然恍然大悟,为什么平常在使用非Activity的Context来startActivity的时候会需要添加FLAG_ACTIVITY_NEW_TASK;

我们还可以得出结论,真正的startActivity使用了Instrumentation类的execStartActivity方法;


public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) 
    // ... 省略无关代码
    try 
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess();

        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;

到这里我们发现真正调用的是ActivityManagerNative的startActivity方法,至于ActivityManagerNative是啥,请自行查资料

Activity的startActivity
Activity类的startActivity方法相比Context而言直观了很多;这个startActivity通过若干次调用辗转到达startActivityForResult这个方法,在这个方法内部有如下代码:

  Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) 
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            

可以得知,通过Activity和ContextImpl类启动Activity是一样的,他们都通过Instrumentation这个辅助类调用到了ActivityManagerNative的方法。

Hook AMS
前面说过,startActivity最终通过ActivityManagerNative这个方法远程调用了AMS的startActivity方法,ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。

ActivityManagerNative中有一个getDefault()方法

/**
     * Retrieve the system's default/global activity manager.
     */
    static public IActivityManager getDefault() 
        return gDefault.get();
    

gDefault这个静态变量的定义如下:

 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;
        
    ;

framework使用了一个单例把这个AMS的代理对象保存了起来;这样只要需要与AMS进行IPC调用,获取这个单例即可,所以我们需要Hook掉这个单例,就可以达到Hook AMS的效果,在Virtualapk中我们就可以看到如下一段代码:

//PluginManager.java
/**
     * hookSystemServices, but need to compatible with Android O in future.
     */
    private void hookSystemServices() 
        try 
            Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
            IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());

            // Hook IActivityManager from ActivityManagerNative
            ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);

            if (defaultSingleton.get() == activityManagerProxy) 
                this.mActivityManager = activityManagerProxy;
            
         catch (Exception e) 
            e.printStackTrace();
        
    

可以看到在VirtualAPK中PluginManager类里,就是替换掉了ActivityManagerNative为VirtualAPK中自己的ActivityManagerProxy,而这个ActivityManagerProxy其实是一个动态代理,所有ActivityManagerNative中的方法都会经过这个代理

 public static IActivityManager newInstance(PluginManager pluginManager, IActivityManager activityManager) 
        return (IActivityManager) Proxy.newProxyInstance(activityManager.getClass().getClassLoader(), new Class[]  IActivityManager.class , new ActivityManagerProxy(pluginManager, activityManager));
    

我们可以看到在ActivityManagerProxy的invoke方法,针对一些方法进行了处理

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        if ("startService".equals(method.getName())) 
            try 
                return startService(proxy, method, args);
             catch (Throwable e) 
                Log.e(TAG, "Start service error", e);
            
         else if ("stopService".equals(method.getName())) 
            try 
                return stopService(proxy, method, args);
             catch (Throwable e) 
                Log.e(TAG, "Stop Service error", e);
            
         else if ("stopServiceToken".equals(method.getName())) 
            try 
                return stopServiceToken(proxy, method, args);
             catch (Throwable e) 
                Log.e(TAG, "Stop service token error", e);
            
         else if ("bindService".equals(method.getName())) 
            try 
                return bindService(proxy, method, args);
             catch (Throwable e) 
                e.printStackTrace();
            
         else if ("unbindService".equals(method.getName())) 
            try 
                return unbindService(proxy, method, args);
             catch (Throwable e) 
                e.printStackTrace();
            
         else if ("getIntentSender".equals(method.getName())) 
            try 
                getIntentSender(method, args);
             catch (Exception e) 
                e.printStackTrace();
            
         else if ("overridePendingTransition".equals(method.getName()))
            try 
                overridePendingTransition(method, args);
             catch (Exception e)
                e.printStackTrace();
            
        

        try 
            // sometimes system binder has problems.
            return method.invoke(this.mActivityManager, args);
         catch (Throwable th) 
            Throwable c = th.getCause();
            if (c != null && c instanceof DeadObjectException) 
                // retry connect to system binder
                IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                if (ams != null) 
                    IActivityManager am = ActivityManagerNative.asInterface(ams);
                    mActivityManager = am;
                
            

            Throwable cause = th;
            do 
                if (cause instanceof RemoteException) 
                    throw cause;
                
             while ((cause = cause.getCause()) != null);

            throw c != null ? c : th;
        

    

上述关于startService等方法的处理,我们在后面说道service时再详细说明,这里我们只要知道VirtualAPK中是如何Hook AMS的

启动插件Activity会遇到什么限制?

在Android中启动Activity有一个限制:必须在AndroidManifest.xml中显示声明使用的Activity;否则会遇到下面这种异常:

 Process: com.example.dldemo, PID: 3600
                                                                  java.lang.RuntimeException: Unable to start activity ComponentInfocom.example.dldemo/com.example.dldemo.MainActivity: android.content.ActivityNotFoundException: Unable to find explicit activity class com.example.dldemo/com.example.dldemo.TestActivity1; have you declared this activity in your AndroidManifest.xml?
                                                                      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
                                                                      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
                                                                      at android.app.ActivityThread.-wrap11(ActivityThread.java)
                                                                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                                                                      at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                      at android.os.Looper.loop(Looper.java:148)
                                                                      at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                                      at java.lang.reflect.Method.invoke(Native Method)
                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

必须在AndroidManifest.xml中显示声明使用的Activity,这个硬性要求很大程度上限制了插件系统的发挥:因为插件的Activity必定不会在宿主程序中进行声明

如何绕过这个限制呢?既然AndroidManifest文件中必须声明,那么我就声明一个(或者有限个)替身Activity好了,当需要启动插件的某个Activity的时候,先让系统以为启动的是AndroidManifest中声明的那个替身,暂时骗过系统;然后到合适的时候又替换回我们需要启动的真正的Activity;我们打开Virtualapk的Library,查看清单文件中,声明了各种启动模式的Activity

  <!-- Stub Activities -->
        <activity android:name=".A$1" android:launchMode="standard"/>
        <activity android:name=".A$2" android:launchMode="standard"
            android:theme="@android:style/Theme.Translucent" />

        <!-- Stub Activities -->
        <activity android:name=".B$1" android:launchMode="singleTop"/>
        <activity android:name=".B$2" android:launchMode="singleTop"/>
        <activity android:name=".B$3" android:launchMode="singleTop"/>
        <activity android:name=".B$4" android:launchMode="singleTop"/>
        <activity android:name=".B$5" android:launchMode="singleTop"/>
        <activity android:name=".B$6" android:launchMode="singleTop"/>
        <activity android:name=".B$7" android:launchMode="singleTop"/>
        <activity android:name=".B$8" android:launchMode="singleTop"/>

        <!-- Stub Activities -->
        <activity android:name=".C$1" android:launchMode="singleTask"/>
        <activity android:name=".C$2" android:launchMode="singleTask"/>
        <activity android:name=".C$3" android:launchMode="singleTask"/>
        <activity android:name=".C$4" android:launchMode="singleTask"/>
        <activity android:name=".C$5" android:launchMode="singleTask"/>
        <activity android:name=".C$6" android:launchMode="singleTask"/>
        <activity android:name=".C$7" android:launchMode="singleTask"/>
        <activity android:name=".C$8" android:launchMode="singleTask"/>

        <!-- Stub Activities -->
        <activity android:name=".D$1" android:launchMode="singleInstance"/>
        <activity android:name=".D$2" android:launchMode="singleInstance"/>
        <activity android:name=".D$3" android:launchMode="singleInstance"/>
        <activity android:name=".D$4" android:launchMode="singleInstance"/>
        <activity android:name=".D$5" android:launchMode="singleInstance"/>
        <activity android:name=".D$6" android:launchMode="singleInstance"/>
        <activity android:name=".D$7" android:launchMode="singleInstance"/>
        <activity android:name=".D$8" android:launchMode="singleInstance"/>

可以发现,Virtualapk采用的正是我上面说的暂坑的模式,那我们到底要如何进行偷梁换柱呢,这就必须从Activity启动过程来进行分析

Activity启动过程

这里简单描述一下Activity的启动过程,想了解详细启动过程,后面我会用一篇博客进行讲解

在前面分析AMS的时候,我们知道Activity的startActivity最终会来到ActivityManagerNative类的startActivity方法,接下来会通过Binder IPC到AMS所在进程调用AMS的startActivity方法;Android系统的组件生命周期管理就是在AMS里面完成的

ActivityManagerService的startActivity方法如下:

@Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) 
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
            resultWho, requestCode, startFlags, profilerInfo, options,
            UserHandle.getCallingUserId());
    

直接调用了startActivityAsUser这个方法;接着是ActivityStackSupervisor类的startActivityMayWait方法。

startActivityMayWait这个方法前面对参数进行了一系列处理,在这个方法内部对传进来的Intent进行了解析,并尝试从中取出关于启动Activity的信息。

然后这个方法调用了startActivityLocked方法;在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等;前面所述的,启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的:

if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) 
            // We couldn't find a class that can handle the given Intent.
            // That's the end of that!
            err = ActivityManager.START_INTENT_NOT_RESOLVED;
        

这里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后会检查这个值,然后抛出异常:

 case ActivityManager.START_CLASS_NOT_FOUND:
                if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                    throw new ActivityNotFoundException(
                            "Unable to find explicit activity class "
                            + ((Intent)intent).getComponent().toShortString()
                            + "; have you declared this activity in your AndroidManifest.xml?");
                throw new ActivityNotFoundException(
                        "No Activity found to handle " + intent);

从这里,我们明白了必须在AndroidManifest.xml中显示声明使用的Activity的原因;然而这个校检过程发生在AMS所在的进程system_server,我们是不能更改system_server进程的东西的。
startActivityLocked之后处理的都是Activity任务栈相关内容

声明:图片来源于网络

这一系列调用最终到达了ActivityStackSupervisor的realStartActivityLocked方法;这个方法开始了真正的“启动Activity”:它调用了ApplicationThread的scheduleLaunchActivity方法,开始了真正的Activity对象创建以及启动过程。

ApplicationThread实际上是一个Binder对象,是App所在的进程与AMS所在进程system_server通信的桥梁:

1.App进程会委托AMS进程完成Activity生命周期的管理以及任务栈的管理;这个通信过程AMS是Server端,App进程通过持有AMS的client代理ActivityManagerNative完成通信过程;
2.AMS进程完成生命周期管理以及任务栈管理后,会把控制权交给App进程,让App进程完成Activity类对象的创建,以及生命周期回调;这个通信过程也是通过Binder完成的,App所在server端的Binder对象存在于ActivityThread的内部类ApplicationThread;AMS所在client通过持有IApplicationThread的代理对象完成对于App进程的通信。

App进程内部的ApplicationThread与App主线程并不在同一个线程内,他们通过Handler完成通信,这个Handler存在于ActivityThread类,它的名字很简单就叫H

ApplicationThread的scheduleLaunchActivity方法,正式包装了参数最终使用Handler发了一个消息。然后在ActivityThread中对消息进行处理

case LAUNCH_ACTIVITY: 
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

直接调用了ActivityThread的handleLaunchActivity方法

  private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) 
        //.....省略代码

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);

performLaunchActivity方法中主要做了两件事情
1.使用ClassLoader加载并通过反射创建Activity对象

 try 
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) 
                r.state.setClassLoader(cl);
            
         catch (Exception e) 
            if (!mInstrumentation.onException(activity, e)) 
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            
        

2.如果Application还没有创建,那么创建Application对象并回调相应的生命周期方法;

Application app = r.packageInfo.makeApplication(false, mInstrumentation);

整个系统的Activity堆栈,Activity生命周期回调都是由AMS所在的系统进程system_server帮开发者完成的;Android的Framework层帮忙完成了诸如生命周期管理等繁琐复杂的过程,简化了应用层的开发。

启动插件中声明的Activity

通过上面的描述,相信你已经大致了解了Activity的启动过程,之前我们说过,启动插件中的Activity会遇到的问题是必须在清单文件中进行声明,我们也说了解决的思路是在AndroidManifest.xml里面声明一个替身Activity,然后在合适的时候把这个假的替换成我们真正需要启动的Activity

我们可以先启动一个已经在AndroidManifest.xml里面声明过的替身Activity,让这个Activity进入AMS进程接受检验;最后在换成我们真正需要启动的Activity;这样就成功欺骗了AMS进程

使用StubActivity绕过AMS
启动Activity的控制权转移到AMS进程之前,我们需要想办法临时把TargetActivity替换成替身StubActivity,我们来看一下Virtualapk里面是如何做的

1.代理系统Instrumentation
前面分析Activity的启动流程的时候,我们知道无论是通过Context还是Activity的startActivity方法最终都会经过Instrumentation

我们在Virtualapk中看到如下代码:

 private void hookInstrumentationAndHandler() 
        try 
            Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
            if (baseInstrumentation.getClass().getName().contains("lbe")) 
                // reject executing in paralell space, for example, lbe.
                System.exit(0);
            

            final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
            Object activityThread = ReflectUtil.getActivityThread(this.mContext);
            ReflectUtil.setInstrumentation(activityThread, instrumentation);
            ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
            this.mInstrumentation = instrumentation;
         catch (Exception e) 
            e.printStackTrace();
        
    

首先通过反射获取了ActivityThread中的instrumentation,并通过
VAInstrumentation 类对系统的instrumentation进行了封装,我们来看一看VAInstrumentation 类里面到底做了什么,其实VAInstrumentation 就是系统instrumentation的一个代理,然后将这个VAInstrumentation 替换掉原来系统ActivityThread中的instrumentation,VAInstrumentation 里面重写了execStartActivity,realExecStartActivity等方法,为什么要重写这些方法?没关系,我们一个个来进行分析

首先是execStartActivity方法的分析

 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) 
        mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
        // null component is an implicitly intent
        if (intent.getComponent() != null) 
            Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
                    intent.getComponent().getClassName()));
            // resolve intent with Stub Activity if needed
            this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
        

        ActivityResult result = realExecStartActivity(who, contextThread, token, target,
                    intent, requestCode, options);

        return result;

    

execStartActivity方法主要做了以下这些事情:
1.将隐试启动插件里activity的意图转化为显示启动,因为在宿主通过隐式启动插件activity,是无法启动的,原因很简单,插件的activity并不在宿主apk中

 mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);

ComponentsHandler的transformIntentToExplicitAsNeeded做的就是这个事情,里面会通过mPluginManager.resolveActivity方法查找插件里面第一个符合隐式条件的第一个ResolveInfo,然后new component 设置进intent,大家可以追着代码进去看,这里不再贴出

 public Intent transformIntentToExplicitAsNeeded(Intent intent) 
        ComponentName component = intent.getComponent();
        if (component == null) 
            ResolveInfo info = mPluginManager.resolveActivity(intent);
            if (info != null && info.activityInfo != null) 
                component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
                intent.setComponent(component);
            
        

        return intent;
    

2.临时把TargetActivity替换成替身StubActivity
我们前面说过,要进行偷梁换柱,而这个地方就是在execStartActivity的以下的代码中

if (intent.getComponent() != null) 
            Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
                    intent.getComponent().getClassName()));
            // resolve intent with Stub Activity if needed
            this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
        

可以发现,关键代码在markIntentIfNeeded方法中

//ComponentsHandler.java
public void markIntentIfNeeded(Intent intent) 
        if (intent.getComponent() == null) 
            return;
        

        String targetPackageName = intent.getComponent().getPackageName();
        String targetClassName = intent.getComponent().getClassName();
        // search map and return specific launchmode stub activity
        if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) 
            intent.putExtra(Constants.KEY_IS_PLUGIN, true);
            intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
            intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
            dispatchStubActivity(intent);
        
    

我们可以发现,会判断启动的Activity是不是插件里的,如果是,则将目标Activity的包名和TargetActivity的名字存储到intent中,接着通过dispatchStubActivity方法,根据要启动的TargetActivity是什么启动模式的来启动相应的代理StubActivity

private void dispatchStubActivity(Intent intent) 
        ComponentName component = intent.getComponent();
        String targetClassName = intent.getComponent().getClassName();
        LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
        ActivityInfo info = loadedPlugin.getActivityInfo(component);
        if (info == null) 
            throw new RuntimeException("can not find " + component);
        
        int launchMode = info.launchMode;
        Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
        themeObj.applyStyle(info.theme, true);
        String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
        Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
        intent.setClassName(mContext, stubActivity);
    

dispatchStubActivity方法里面会通过TargetActivity是什么启动模式的来获取相应的代理StubActivity的类名并设置进intent中,这里就完成了偷梁换柱的过程。

3.调用系统Instrumentation的execStartActivity方法,进入正常启动Activity的流程

   ActivityResult result = realExecStartActivity(who, contextThread, token, target,
                    intent, requestCode, options);
 private ActivityResult realExecStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) 
        ActivityResult result = null;
        try 
            Class[] parameterTypes = Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
            int.class, Bundle.class;
            result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
                    "execStartActivity", parameterTypes,
                    who, contextThread, token, target, intent, requestCode, options);
         catch (Exception e) 
            e.printStackTrace();
        

        return result;
    

这里是通过反射调用系统Instrumentation的execStartActivity方法

通过这个替换过程,在ActivityManagerNative的startActivity调用之后,system_server端收到Binder驱动的消息,开始执行ActivityManagerService里面真正的startActivity方法;这时候AMS看到的intent参数里面的组件已经是StubActivity了,因此可以成功绕过检查

将StubActivity替换回TargetActivity

好了,接下来就是进行跨进程通信,system_server管理Activity的生命周期,之前我们用替身StubActivity临时换了TargetActivity,肯定需要在合适的时候替换回来,Virtualapk替换的回来的地方是哪里呢?

在AMS进程里面我们是没有办法换回来的,因此我们要等AMS把控制权交给App所在进程,还记得前面我们说ApplicationThread所在的Binder线程池通

以上是关于滴滴插件化框架VirtualAPK原理解析之插件Activity管理的主要内容,如果未能解决你的问题,请参考以下文章

滴滴开源Android插件化框架VirtualAPK原理分析

滴滴开源Android插件化框架VirtualAPK原理分析

滴滴开源Android插件化框架VirtualAPK原理分析

Android开发的艺术:插件化框架Virtual APK实现原理解析

android 插件化框架VitualAPK

十大android开发框架