之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在Android系统上运行起来?
在Java平台要做到动态运行模块、热插拔可以使用ClassLoader
技术进行动态类加载,比如广泛使用的OSGi
技术。在Android上当然也可以使用动态加载技术,但是仅仅把类加载进来就足够了吗?Activity
,Service
等组件是有生命周期的,它们统一由系统服务AMS
管理;使用ClassLoader
可以从插件中创建Activity对象,但是,一个没有生命周期的Activity对象有什么用?所以在Android系统上,仅仅完成动态类加载是不够的;我们需要想办法把我们加载进来的Activity等组件交给系统管理,让AMS
赋予组件生命周期;这样才算是一个有血有肉的完善的插件化方案。
接下来的系列文章会讲述 DroidPlugin对于Android四大组件的处理方式,我们且看它如何采用Hook技术坑蒙拐骗把系统玩弄于股掌之中,最终赋予Activity,Service等组件生命周期,完成借尸还魂的。
首先,我们来看看DroidPlugin对于Activity
组件的处理方式。
阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的intercept-activity模块。另外,如果对于Hook技术不甚了解,请先查阅我之前的文章:
AndroidManifest.xml的限制
读到这里,或许有部分读者觉得疑惑了,启动Activity不就是一个startActivity
的事吗,有这么神秘兮兮的?
启动Activity确实非常简单,但是Android却有一个限制:必须在AndroidManifest.xml中显示声明使用的Activity;我相信读者肯定会遇到下面这种异常:
1 2 3 |
03-18 15:29:56.074 20709-20709/com.weishu.intercept_activity.app E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.weishu.intercept_activity.app, PID: 20709 android.content.ActivityNotFoundException: Unable to find explicit activity class {com.weishu.intercept_activity.app/com.weishu.intercept_activity.app.TargetActivity}; have you declared this activity in your AndroidManifest.xml? |
必须在AndroidManifest.xml中显示声明使用的Activity』这个硬性要求很大程度上限制了插件系统的发挥:假设我们需要启动一个插件的Activity,插件使用的Activity是无法预知的,这样肯定也不会在Manifest文件中声明;如果插件新添加一个Activity,主程序的AndroidManifest.xml就需要更新;既然双方都需要修改升级,何必要使用插件呢?这已经违背了动态加载的初衷:不修改插件框架而动态扩展功能。
能不能想办法绕过这个限制呢?
束手无策啊,怎么办?借刀杀人偷梁换柱无中生有以逸待劳乘火打劫瞒天过海…等等!偷梁换柱瞒天过海?貌似可以一试。
我们可以耍个障眼法:既然AndroidManifest文件中必须声明,那么我就声明一个(或者有限个)替身Activity好了,当需要启动插件的某个Activity的时候,先让系统以为启动的是AndroidManifest中声明的那个替身,暂时骗过系统;然后到合适的时候又替换回我们需要启动的真正的Activity;所谓瞒天过海,莫过如此!
现在有了方案了,但是该如何做呢?兵书又说,知己知彼百战不殆!如果连Activity的启动过程都不熟悉,怎么完成这个瞒天过海的过程?
Activity启动过程
启动Activity非常简单,一个startActivity
就完事了;那么在这个简单调用的背后发生了什么呢?Look the fucking source code!
关于Activity 的启动过程,也不是三言两语能解释清楚的,如果按照源码一步一步走下来,插件化系列文章就不用写了;所以这里我就给出一个大致流程,只列出关键的调用点(以Android 6.0源码为例);如果读者希望更详细的讲解,可以参考老罗的 Android应用程序的Activity启动过程简要介绍和学习计划
首先是Activity类的startActivity
方法:
1 2 3 |
public void startActivity(Intent intent) { startActivity(intent, null); } |
跟着这个方法一步一步跟踪,会发现它最后在startActivityForResult
里面调用了Instrument对象的execStartActivity
方法;接着在这个函数里面调用了ActivityManagerNative类的startActivity
方法;这个过程在前文已经反复举例讲解了,我们知道接下来会通过Binder IPC到AMS
所在进程调用AMS
的startActivity
方法;Android系统的组件生命周期管理就是在AMS
里面完成的,那么在AMS
里面到底做了什么呢?
ActivityManagerService的startActivity
方法如下:
1 2 3 4 5 6 7 8 |
public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId()); } |
很简单,直接调用了startActivityAsUser
这个方法;接着是ActivityStackSupervisor
类的startActivityMayWait
方法。这个ActivityStackSupervisor类到底是个啥?如果仔细查阅,低版本的Android源码上是没有这个类的;后来AMS的代码进行了部分重构,关于Activity栈管理的部分单独提取出来成为了ActivityStackSupervisor
类;好了,继续看代码。
startActivityMayWait这个方法前面对参数进行了一系列处理,我们需要知道的是,在这个方法内部对传进来的Intent进行了解析,并尝试从中取出关于启动Activity的信息。
然后这个方法调用了startActivityLocked方法;在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等;我们上文所述的,启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的:
1 2 3 4 5 |
if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. err = ActivityManager.START_CLASS_NOT_FOUND; } |
这里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后会检查这个值,然后跑出异常:
1 2 3 4 5 6 |
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?"); |
源码看到这里,我们已经确认了『必须在AndroidManifest.xml中显示声明使用的Activity』的原因;然而这个校检过程发生在AMS
所在的进程system_server
,我们没有办法篡改,只能另寻他路。
OK,我们继续跟踪源码;在startActivityLocked之后处理的都是Activity任务栈相关内容,这一系列ActivityStack和ActivityStackSupervisor纠缠不清的调用看下图就明白了;不明白也没关系: D 目前用处不大。
ǣ析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在Android系统上运行起来?
在Java平台要做到动态运行模块、热插拔可以使用ClassLoader
技术进行动态类加载,比如广泛使用的OSGi
技术。在Android上当然也可以使用动态加载技术,但是仅仅把类加载进来就足够了吗?Activity
,Service
等组件是有生命周期的,它们统一由系统服务AMS
管理;使用ClassLoader
可以从插件中创建Activity对象,但是,一个没有生命周期的Activity对象有什么用?所以在Android系统上,仅仅完成动态类加载是不够的;我们需要想办法把我们加载进来的Activity等组件交给系统管理,让AMS
赋予组件生命周期;这样才算是一个有血有肉的完善的插件化方案。
接下来的系列文章会讲述 DroidPlugin对于Android四大组件的处理方式,我们且看它如何采用Hook技术坑蒙拐骗把系统玩弄于股掌之中,最终赋予Activity,Service等组件生命周期,完成借尸还魂的。
首先,我们来看看DroidPlugin对于Activity
组件的处理方式。
阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的intercept-activity模块。另外,如果对于Hook技术不甚了解,请先查阅我之前的文章:
AndroidManifest.xml的限制
读到这里,或许有部分读者觉得疑惑了,启动Activity不就是一个startActivity
的事吗,有这么神秘兮兮的?
启动Activity确实非常简单,但是Android却有一个限制:必须在AndroidManifest.xml中显示声明使用的Activity;我相信读者肯定会遇到下面这种异常:
1 2 3 |
03-18 15:29:56.074 20709-20709/com.weishu.intercept_activity.app E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.weishu.intercept_activity.app, PID: 20709 android.content.ActivityNotFoundException: Unable to find explicit activity class {com.weishu.intercept_activity.app/com.weishu.intercept_activity.app.TargetActivity}; have you declared this activity in your AndroidManifest.xml? |
必须在AndroidManifest.xml中显示声明使用的Activity』这个硬性要求很大程度上限制了插件系统的发挥:假设我们需要启动一个插件的Activity,插件使用的Activity是无法预知的,这样肯定也不会在Manifest文件中声明;如果插件新添加一个Activity,主程序的AndroidManifest.xml就需要更新;既然双方都需要修改升级,何必要使用插件呢?这已经违背了动态加载的初衷:不修改插件框架而动态扩展功能。
能不能想办法绕过这个限制呢?
束手无策啊,怎么办?借刀杀人偷梁换柱无中生有以逸待劳乘火打劫瞒天过海…等等!偷梁换柱瞒天过海?貌似可以一试。
我们可以耍个障眼法:既然AndroidManifest文件中必须声明,那么我就声明一个(或者有限个)替身Activity好了,当需要启动插件的某个Activity的时候,先让系统以为启动的是AndroidManifest中声明的那个替身,暂时骗过系统;然后到合适的时候又替换回我们需要启动的真正的Activity;所谓瞒天过海,莫过如此!
现在有了方案了,但是该如何做呢?兵书又说,知己知彼百战不殆!如果连Activity的启动过程都不熟悉,怎么完成这个瞒天过海的过程?
Activity启动过程
启动Activity非常简单,一个startActivity
就完事了;那么在这个简单调用的背后发生了什么呢?Look the fucking source code!
关于Activity 的启动过程,也不是三言两语能解释清楚的,如果按照源码一步一步走下来,插件化系列文章就不用写了;所以这里我就给出一个大致流程,只列出关键的调用点(以Android 6.0源码为例);如果读者希望更详细的讲解,可以参考老罗的 Android应用程序的Activity启动过程简要介绍和学习计划
首先是Activity类的startActivity
方法:
1 2 3 |
public void startActivity(Intent intent) { startActivity(intent, null); } |
跟着这个方法一步一步跟踪,会发现它最后在startActivityForResult
里面调用了Instrument对象的execStartActivity
方法;接着在这个函数里面调用了ActivityManagerNative类的startActivity
方法;这个过程在前文已经反复举例讲解了,我们知道接下来会通过Binder IPC到AMS
所在进程调用AMS
的startActivity
方法;Android系统的组件生命周期管理就是在AMS
里面完成的,那么在AMS
里面到底做了什么呢?
ActivityManagerService的startActivity
方法如下:
1 2 3 4 5 6 7 8 |
public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId()); } |
很简单,直接调用了startActivityAsUser
这个方法;接着是ActivityStackSupervisor
类的startActivityMayWait
方法。这个ActivityStackSupervisor类到底是个啥?如果仔细查阅,低版本的Android源码上是没有这个类的;后来AMS的代码进行了部分重构,关于Activity栈管理的部分单独提取出来成为了ActivityStackSupervisor
类;好了,继续看代码。
startActivityMayWait这个方法前面对参数进行了一系列处理,我们需要知道的是,在这个方法内部对传进来的Intent进行了解析,并尝试从中取出关于启动Activity的信息。
然后这个方法调用了startActivityLocked方法;在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等;我们上文所述的,启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的:
1 2 3 4 5 |
if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. err = ActivityManager.START_CLASS_NOT_FOUND; } |
这里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后会检查这个值,然后跑出异常:
1 2 3 4 5 6 |
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?"); |
源码看到这里,我们已经确认了『必须在AndroidManifest.xml中显示声明使用的Activity』的原因;然而这个校检过程发生在AMS
所在的进程system_server
,我们没有办法篡改,只能另寻他路。
OK,我们继续跟踪源码;在startActivityLocked之后处理的都是Activity任务栈相关内容,这一系列ActivityStack和ActivityStackSupervisor纠缠不清的调用看下图就明白了;不明白也没关系: D 目前用处不大。
on-button-icon">