滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理
2017-07-02 21:15
471 查看
上周末,滴滴与360都开源了各自的插件化框架,VirtualAPK与RePlugin,作为一个插件化方面的狂热研究者,在周末就迫不及待的下载了Virtualapk框架来进行研究,本篇博客带来的是VirtualAPK原理解析的第一篇Activity管理,博客只是自己的理解,小弟才疏学浅,可能有很多理解不对的地方,欢迎各位大神指出。(看博客之前,请大家先下载VirtualVirtualAPKapk的项目,https://github.com/didi/VirtualAPK)
1.startActivity最终调用了AMS的startActivity方法,实现了Activity的启动;Activity的生命周期回调,也在AMS中完成;
2.startService,bindService最终调用到AMS的startService和bindService方法;
3.动态广播的注册和接收在AMS中完成(静态广播在PMS中完成)
4.getContentResolver最终从AMS的getContentProvider获取到ContentProvider
AMS以Binder方式提供给应用程序使用的系统服务,所以我们要处理插件中的四大组件,必须要Hook掉AMS进行相应的处理。
在开发中,我们使用startActivity有两种形式:
1.直接使用Context类的startActivity方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个Flag。
2.调用被Activity类重载过的startActivity方法,通常在我们的Activity中直接调用这个方法就是这种形式;
context的startActivity
先来分析第一种情况,context的startActivity是一个抽象方法
Activity,Service等并没有直接继承Context,而是继承了ContextWrapper,所以我们需要看ContextWrapper里面的startActivity方法
我们可以得知,startActivity方法最终会通过mBase来完成,它的真正实现是ContextImpl类
在继续往下追寻,会看到如下代码:
看到这里,你是不是突然恍然大悟,为什么平常在使用非Activity的Context来startActivity的时候会需要添加FLAG_ACTIVITY_NEW_TASK;
我们还可以得出结论,真正的startActivity使用了Instrumentation类的execStartActivity方法;
到这里我们发现真正调用的是ActivityManagerNative的startActivity方法,至于ActivityManagerNative是啥,请自行查资料
Activity的startActivity
Activity类的startActivity方法相比Context而言直观了很多;这个startActivity通过若干次调用辗转到达startActivityForResult这个方法,在这个方法内部有如下代码:
可以得知,通过Activity和ContextImpl类启动Activity是一样的,他们都通过Instrumentation这个辅助类调用到了ActivityManagerNative的方法。
Hook AMS
前面说过,startActivity最终通过ActivityManagerNative这个方法远程调用了AMS的startActivity方法,ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。
ActivityManagerNative中有一个getDefault()方法
gDefault这个静态变量的定义如下:
framework使用了一个单例把这个AMS的代理对象保存了起来;这样只要需要与AMS进行IPC调用,获取这个单例即可,所以我们需要Hook掉这个单例,就可以达到Hook AMS的效果,在Virtualapk中我们就可以看到如下一段代码:
可以看到在VirtualAPK中PluginManager类里,就是替换掉了ActivityManagerNative为VirtualAPK中自己的ActivityManagerProxy,而这个ActivityManagerProxy其实是一个动态代理,所有ActivityManagerNative中的方法都会经过这个代理
我们可以看到在ActivityManagerProxy的invoke方法,针对一些方法进行了处理
上述关于startService等方法的处理,我们在后面说道service时再详细说明,这里我们只要知道VirtualAPK中是如何Hook AMS的
必须在AndroidManifest.xml中显示声明使用的Activity,这个硬性要求很大程度上限制了插件系统的发挥:因为插件的Activity必定不会在宿主程序中进行声明
如何绕过这个限制呢?既然AndroidManifest文件中必须声明,那么我就声明一个(或者有限个)替身Activity好了,当需要启动插件的某个Activity的时候,先让系统以为启动的是AndroidManifest中声明的那个替身,暂时骗过系统;然后到合适的时候又替换回我们需要启动的真正的Activity;我们打开Virtualapk的Library,查看清单文件中,声明了各种启动模式的Activity
可以发现,Virtualapk采用的正是我上面说的暂坑的模式,那我们到底要如何进行偷梁换柱呢,这就必须从Activity启动过程来进行分析
在前面分析AMS的时候,我们知道Activity的startActivity最终会来到ActivityManagerNative类的startActivity方法,接下来会通过Binder IPC到AMS所在进程调用AMS的startActivity方法;Android系统的组件生命周期管理就是在AMS里面完成的
ActivityManagerService的startActivity方法如下:
直接调用了startActivityAsUser这个方法;接着是ActivityStackSupervisor类的startActivityMayWait方法。
startActivityMayWait这个方法前面对参数进行了一系列处理,在这个方法内部对传进来的Intent进行了解析,并尝试从中取出关于启动Activity的信息。
然后这个方法调用了startActivityLocked方法;在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等;前面所述的,启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的:
这里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后会检查这个值,然后抛出异常:
从这里,我们明白了必须在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中对消息进行处理
直接调用了ActivityThread的handleLaunchActivity方法
performLaunchActivity方法中主要做了两件事情
1.使用ClassLoader加载并通过反射创建Activity对象
2.如果Application还没有创建,那么创建Application对象并回调相应的生命周期方法;
整个系统的Activity堆栈,Activity生命周期回调都是由AMS所在的系统进程system_server帮开发者完成的;Android的Framework层帮忙完成了诸如生命周期管理等繁琐复杂的过程,简化了应用层的开发。
我们可以先启动一个已经在AndroidManifest.xml里面声明过的替身Activity,让这个Activity进入AMS进程接受检验;最后在换成我们真正需要启动的Activity;这样就成功欺骗了AMS进程
使用StubActivity绕过AMS
启动Activity的控制权转移到AMS进程之前,我们需要想办法临时把TargetActivity替换成替身StubActivity,我们来看一下Virtualapk里面是如何做的
1.代理系统Instrumentation
前面分析Activity的启动流程的时候,我们知道无论是通过Context还是Activity的startActivity方法最终都会经过Instrumentation
我们在Virtualapk中看到如下代码:
首先通过反射获取了ActivityThread中的instrumentation,并通过
VAInstrumentation 类对系统的instrumentation进行了封装,我们来看一看VAInstrumentation 类里面到底做了什么,其实VAInstrumentation 就是系统instrumentation的一个代理,然后将这个VAInstrumentation 替换掉原来系统ActivityThread中的instrumentation,VAInstrumentation 里面重写了execStartActivity,realExecStartActivity等方法,为什么要重写这些方法?没关系,我们一个个来进行分析
首先是execStartActivity方法的分析
execStartActivity方法主要做了以下这些事情:
1.将隐试启动插件里activity的意图转化为显示启动,因为在宿主通过隐式启动插件activity,是无法启动的,原因很简单,插件的activity并不在宿主apk中
ComponentsHandler的transformIntentToExplicitAsNeeded做的就是这个事情,里面会通过mPluginManager.resolveActivity方法查找插件里面第一个符合隐式条件的第一个ResolveInfo,然后new component 设置进intent,大家可以追着代码进去看,这里不再贴出
2.临时把TargetActivity替换成替身StubActivity
我们前面说过,要进行偷梁换柱,而这个地方就是在execStartActivity的以下的代码中
可以发现,关键代码在markIntentIfNeeded方法中
我们可以发现,会判断启动的Activity是不是插件里的,如果是,则将目标Activity的包名和TargetActivity的名字存储到intent中,接着通过dispatchStubActivity方法,根据要启动的TargetActivity是什么启动模式的来启动相应的代理StubActivity
dispatchStubActivity方法里面会通过TargetActivity是什么启动模式的来获取相应的代理StubActivity的类名并设置进intent中,这里就完成了偷梁换柱的过程。
3.调用系统Instrumentation的execStartActivity方法,进入正常启动Activity的流程
这里是通过反射调用系统Instrumentation的execStartActivity方法
通过这个替换过程,在ActivityManagerNative的startActivity调用之后,system_server端收到Binder驱动的消息,开始执行ActivityManagerService里面真正的startActivity方法;这时候AMS看到的intent参数里面的组件已经是StubActivity了,因此可以成功绕过检查
在AMS进程里面我们是没有办法换回来的,因此我们要等AMS把控制权交给App所在进程,还记得前面我们说ApplicationThread所在的Binder线程池通过Handler与ActivityThread进行通信么?
于是乎,我们是不是可以有一种思路,在这个Handler的handleMessage时做处理呢?我们简单看一下Handler的代码
Handler类消息分发的过程:
1.如果传递的Message本身就有callback,那么直接使用Message对象的callback方法;
2.如果Handler类的成员变量mCallback存在,那么首先执行这个mCallback回调;
3.如果mCallback的回调返回true,那么表示消息已经成功处理;直接结束。
4.如果mCallback的回调返回false,那么表示消息没有处理完毕,会继续使用Handler类的handleMessage方法处理消息。
ActivityThread中的Handler类H重载了handleMessage方法:
从dispathMessage方法中,我们可以得出思路:我们可以拦截这一过程:把这个H类的mCallback替换为我们的自定义实现,这样dispathMessage就会首先使用这个自定义的mCallback,然后看情况使用H重载的handleMessage。
这个Handler.Callback是一个接口,我们可以使用动态代理或者普通代理完成Hook,在Virtualapk中使用普通的静态代理方式,还记得前面的VAInstrumentation类么?它不仅继承了系统的Instrumentation,同时还实现了 Handler.Callback 接口
再次贴出前面PluginManager初始化的时候hookInstrumentationAndHandler的代码
可以很明显的发现setHandlerCallback方法中对Callback进行了处理
果不其然,替换了ActivityThread中的Handler的mCallback为VAInstrumentation ,所以当ActivityThread的H进行dispathMessage的时候,必定会走到VAInstrumentation 的handleMessage方法
可以看到在VAInstrumentation 的handleMessage方法只是拦截了LAUNCH_ACTIVITY的处理,在里面将intent中的activityInfo.theme替换为插件的theme,并给intent设置了ClassLoader,这里为什么要设置一个ClassLoader?我想是因为在ActivityThread的performLaunchActivity方法会将其取出,然后设置进mInstrumentation.newActivity方法中
经过上面的调用,又会进入到VAInstrumentation 的newActivity
首先是cl.loadClass(className),注意,我使用的demo,这里的className为com.didi.virtualapk.core.A$1,还有可能是其他的,但都会是在清单文件声明的stubActivity的名字
调用cl.loadClass(className)去加载这些类,肯定是会爆出ClassNotFoundException异常的,因为这些类并不存在,他们只是在清单文件中起到占坑的作用,用来欺骗系统的,这里的设计确实非常巧妙,接下来自然走到catch里,catch里自然是去构建真正需要加载的TargetActivity
传入newActivity方法的是LoadedPlugin中的ClassLoader,这个ClassLoader已经是修改过的,可以加载插件和宿主里的类,关羽ClassLoader不懂的,可以看《Android插件化学习之路(二)之ClassLoader完全解析》
然后将intent设置进插件Activity 中,注意,这里的intent里的className还是com.didi.virtualapk.core.A$1
接下来会调用到ActivityThread中的performLaunchActivity 中的如下代码
然后又进入到VAInstrumentation 的callActivityOnCreate方法中
最后这里就是进行一些替换的工作了,替换掉TargetActivity里的mResources,mBase,mApplication为LoadedPlugin中生成的可以用于加载插件资源的相应Resources和Context,为什么要进行这些替换工作?后续文章会进行详细讲解,最后就是调用系统Instrumentation的callActivityOnCreate其启动这个插件TargetActivity了,我们可以发现,到最后intent里的className还是com.didi.virtualapk.core.A$1,这是因为这个intent只是用来欺骗系统的作用
可以看见,接下来就是调用插件Activity的onCreate方法了,就完了加载插件Activity的过程
你可能会问通过上面的方式启动的插件TargetActivity就具有生命周期了吗?答案是肯定的,大家可以通过demo去验证这一点,我们以onDestroy为例分析一下这个过程:
Activity的finish方法最终会通过ActivityManagerNative到AMS然后接着通过ApplicationThread到ActivityThread,然后通过H转发消息到ActivityThread的handleDestroyActivity,接着这个方法把任务交给performDestroyActivity完成。
performDestroyActivity,关键代码如下:
通过mActivities拿到了一个ActivityClientRecord,然后直接把这个record里面的Activity交给Instrument类完成了onDestroy的调用。
这里的r.activity是StubActivity为什么它能正确完成对TargetActivity生命周期的回调呢?
答案是token。AMS与ActivityThread之间对于Activity的生命周期的交互,并没有直接使用Activity对象进行交互,而是使用一个token来标识,这个token是binder对象,因此可以方便地跨进程传递。Activity里面有一个成员变量mToken代表的就是它,token可以唯一地标识一个Activity对象,它在Activity的attach方法里面初始化;
在AMS处理Activity的任务栈的时候,使用这个token标记Activity,因此在demo里面,AMS进程里面的token对应的是StubActivity,但是在App进程里面,token对应的却是TargetActivity!因此,在ActivityThread执行回调的时候,能正确地回调到TargetActivity相应的方法。
为什么App进程里面,token对应的是TargetActivity呢?
ActivityClientRecord是在mActivities里面取出来的,确实是根据token取;那么这个token是什么时候添加进去的呢?我们看performLaunchActivity就完成明白了:它通过classloader加载了TargetActivity,然后完成一切操作之后把这个activity添加进了mActivities!另外,在这个方法里面我们还能看到对Ativity.attach方法的调用,它传递给了新创建的Activity一个token对象,而这个token是在ActivityClientRecord构造函数里面初始化的。
至此,整个Virtualapk框架对于插件Activity的管理就到此结束了,接下来还会继续分析其他组件的原理,最后,如果小弟有分析偏差的地方,欢迎矫正。
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 ComponentInfo{com.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线程池通过Handler与ActivityThread进行通信么?
于是乎,我们是不是可以有一种思路,在这个Handler的handleMessage时做处理呢?我们简单看一下Handler的代码
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
Handler类消息分发的过程:
1.如果传递的Message本身就有callback,那么直接使用Message对象的callback方法;
2.如果Handler类的成员变量mCallback存在,那么首先执行这个mCallback回调;
3.如果mCallback的回调返回true,那么表示消息已经成功处理;直接结束。
4.如果mCallback的回调返回false,那么表示消息没有处理完毕,会继续使用Handler类的handleMessage方法处理消息。
ActivityThread中的Handler类H重载了handleMessage方法:
public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { 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); } break; case RELAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // 省略一万行代码。。。。 } }
从dispathMessage方法中,我们可以得出思路:我们可以拦截这一过程:把这个H类的mCallback替换为我们的自定义实现,这样dispathMessage就会首先使用这个自定义的mCallback,然后看情况使用H重载的handleMessage。
这个Handler.Callback是一个接口,我们可以使用动态代理或者普通代理完成Hook,在Virtualapk中使用普通的静态代理方式,还记得前面的VAInstrumentation类么?它不仅继承了系统的Instrumentation,同时还实现了 Handler.Callback 接口
再次贴出前面PluginManager初始化的时候hookInstrumentationAndHandler的代码
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(); } }
可以很明显的发现setHandlerCallback方法中对Callback进行了处理
public static void setHandlerCallback(Context base, Handler.Callback callback) { try { Object activityThread = getActivityThread(base); Handler mainHandler = (Handler) ReflectUtil.invoke(activityThread.getClass(), activityThread, "getHandler", (Object[])null); ReflectUtil.setField(Handler.class, mainHandler, "mCallback", callback); } catch (Exception e) { e.printStackTrace(); } }
果不其然,替换了ActivityThread中的Handler的mCallback为VAInstrumentation ,所以当ActivityThread的H进行dispathMessage的时候,必定会走到VAInstrumentation 的handleMessage方法
//VAInstrumentation.java @Override public boolean handleMessage(Message msg) { if (msg.what == LAUNCH_ACTIVITY) { // ActivityClientRecord r Object r = msg.obj; try { Intent intent = (Intent) ReflectUtil.getField(r.getClass(), r, "intent"); intent.setExtrasClassLoader(VAInstrumentation.class.getClassLoader()); ActivityInfo activityInfo = (ActivityInfo) ReflectUtil.getField(r.getClass(), r, "activityInfo"); if (PluginUtil.isIntentFromPlugin(intent)) { int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent); if (theme != 0) { Log.i(TAG, "resolve theme, current theme:" + activityInfo.theme + " after :0x" + Integer.toHexString(theme)); activityInfo.theme = theme; } } } catch (Exception e) { e.printStackTrace(); } } return false; }
可以看到在VAInstrumentation 的handleMessage方法只是拦截了LAUNCH_ACTIVITY的处理,在里面将intent中的activityInfo.theme替换为插件的theme,并给intent设置了ClassLoader,这里为什么要设置一个ClassLoader?我想是因为在ActivityThread的performLaunchActivity方法会将其取出,然后设置进mInstrumentation.newActivity方法中
//ActivityThread.java Activity activity = null; 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); } }
经过上面的调用,又会进入到VAInstrumentation 的newActivity
@Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { cl.loadClass(className); } catch (ClassNotFoundException e) { LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); String targetClassName = PluginUtil.getTargetActivity(intent); Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName)); if (targetClassName != null) { Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent); try { // for 4.1+ ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources()); } catch (Exception ignored) { // ignored. } return activity; } } return mBase.newActivity(cl, className, intent); }
首先是cl.loadClass(className),注意,我使用的demo,这里的className为com.didi.virtualapk.core.A$1,还有可能是其他的,但都会是在清单文件声明的stubActivity的名字
调用cl.loadClass(className)去加载这些类,肯定是会爆出ClassNotFoundException异常的,因为这些类并不存在,他们只是在清单文件中起到占坑的作用,用来欺骗系统的,这里的设计确实非常巧妙,接下来自然走到catch里,catch里自然是去构建真正需要加载的TargetActivity
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent);
传入newActivity方法的是LoadedPlugin中的ClassLoader,这个ClassLoader已经是修改过的,可以加载插件和宿主里的类,关羽ClassLoader不懂的,可以看《Android插件化学习之路(二)之ClassLoader完全解析》
然后将intent设置进插件Activity 中,注意,这里的intent里的className还是com.didi.virtualapk.core.A$1
接下来会调用到ActivityThread中的performLaunchActivity 中的如下代码
if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); }
然后又进入到VAInstrumentation 的callActivityOnCreate方法中
public void callActivityOnCreate(Activity activity, Bundle icicle) { final Intent intent = activity.getIntent(); if (PluginUtil.isIntentFromPlugin(intent)) { Context base = activity.getBaseContext(); try { LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources()); ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext()); ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication()); ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext()); // set screenOrientation ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent)); if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { activity.setRequestedOrientation(activityInfo.screenOrientation); } } catch (Exception e) { e.printStackTrace(); } } mBase.callActivityOnCreate(activity, icicle); }
最后这里就是进行一些替换的工作了,替换掉TargetActivity里的mResources,mBase,mApplication为LoadedPlugin中生成的可以用于加载插件资源的相应Resources和Context,为什么要进行这些替换工作?后续文章会进行详细讲解,最后就是调用系统Instrumentation的callActivityOnCreate其启动这个插件TargetActivity了,我们可以发现,到最后intent里的className还是com.didi.virtualapk.core.A$1,这是因为这个intent只是用来欺骗系统的作用
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { prePerformCreate(activity); activity.performCreate(icicle, persistentState); postPerformCreate(activity); }
可以看见,接下来就是调用插件Activity的onCreate方法了,就完了加载插件Activity的过程
你可能会问通过上面的方式启动的插件TargetActivity就具有生命周期了吗?答案是肯定的,大家可以通过demo去验证这一点,我们以onDestroy为例分析一下这个过程:
Activity的finish方法最终会通过ActivityManagerNative到AMS然后接着通过ApplicationThread到ActivityThread,然后通过H转发消息到ActivityThread的handleDestroyActivity,接着这个方法把任务交给performDestroyActivity完成。
performDestroyActivity,关键代码如下:
ActivityClientRecord r = mActivities.get(token); // ...... mInstrumentation.callActivityOnDestroy(r.activity);
通过mActivities拿到了一个ActivityClientRecord,然后直接把这个record里面的Activity交给Instrument类完成了onDestroy的调用。
这里的r.activity是StubActivity为什么它能正确完成对TargetActivity生命周期的回调呢?
答案是token。AMS与ActivityThread之间对于Activity的生命周期的交互,并没有直接使用Activity对象进行交互,而是使用一个token来标识,这个token是binder对象,因此可以方便地跨进程传递。Activity里面有一个成员变量mToken代表的就是它,token可以唯一地标识一个Activity对象,它在Activity的attach方法里面初始化;
在AMS处理Activity的任务栈的时候,使用这个token标记Activity,因此在demo里面,AMS进程里面的token对应的是StubActivity,但是在App进程里面,token对应的却是TargetActivity!因此,在ActivityThread执行回调的时候,能正确地回调到TargetActivity相应的方法。
为什么App进程里面,token对应的是TargetActivity呢?
ActivityClientRecord是在mActivities里面取出来的,确实是根据token取;那么这个token是什么时候添加进去的呢?我们看performLaunchActivity就完成明白了:它通过classloader加载了TargetActivity,然后完成一切操作之后把这个activity添加进了mActivities!另外,在这个方法里面我们还能看到对Ativity.attach方法的调用,它传递给了新创建的Activity一个token对象,而这个token是在ActivityClientRecord构造函数里面初始化的。
至此,整个Virtualapk框架对于插件Activity的管理就到此结束了,接下来还会继续分析其他组件的原理,最后,如果小弟有分析偏差的地方,欢迎矫正。
相关文章推荐
- 滴滴插件化VirtualAPK框架原理解析(二)之Service 管理
- [置顶] 滴滴开源Android插件化框架VirtualAPK原理分析
- 插件化探索,滴滴开源框架VirtualAPK的深入分析
- 《Android 插件化框架VirtualAPK :(二)原理分析》
- 良心博客滴滴开源框架VirtualAPK插件化介绍加教程加DEMO加投入项目
- Android 插件化框架 DynamicLoadApk 源码解析
- VirtualAPK:滴滴 Android 插件化的实践之路
- Android 插件补丁框架 ZeusPlugin 原理解析
- Android插件框架VirtualAPK学习和使用
- Android Small插件化框架--启动插件Activity源码解析(下)
- Android Small插件化框架--启动插件Activity源码解析(上)
- android插件化-获取apkplug框架已安装插件-03
- VirtualAPK:滴滴 Android 插件化的实践之路
- Android 插件化原理解析——插件加载机制
- Android 插件化原理 完胜360插件框架 技术实战
- Android Small插件化框架--启动插件Activity源码解析(下)
- VirtualAPK插件化方案原理探索
- VirtualAPK:滴滴 Android 插件化的实践之路
- Android Small插件化框架--启动插件Activity源码解析(上)
- Android 插件化框架 DynamicLoadApk 源码解析