write in front
This article is original, please indicate the address in the form of a link for reprinting: http://kymjs.com/code/2016/05/15/01
After the first two articles were written, someone told me how they thought your article style had suddenly changed. I've talked so much lately that it's become long-winded, and it doesn't have your efficient and streamlined style. It's not that I don't want to, it's really a plug-in thing. If you don't know the theoretical knowledge, you can't understand it at all. In the next few articles, I will try my best to focus on practice, so that everyone can understand.
In the preface [the past, present and future of Android plug-in] , I briefly told you about the basic implementation principles of all plug-ins in the open source community.
From the beginning of this article, I will take you to implement a plug-in library in the simplest way.
Activity loading process
First, let's talk about the most important function, the dynamic loading of Activity. Looking at the source code we know
- Each
Activity
startup process isstartActivityForResult()
eventually called byInstrument.execStartActivity()
- Then through the process of
ActivityManagerNative.startActivity()
IPC ,AMS
ActivityManagerService.startActivity()
- Finally
ActivityStackSupervisor.startActivityLocked()
, permissions and security checksmService.checkPermission
. If weActivity
do not register, we will return an unregistered error during this check, and finally throw this unregistered exception when we return to the application process. - After the security check is completed, it will call
ApplicationThread.scheduleLaunchActivity()
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
r.compat, r.task.voiceInteractor, app.repProcState, r.icicle, r.persistentState,
results, newIntents, andResume, mService.isNextTransitionForward(),
profilerInfo);
//ApplicationThread.scheduleLaunchActivity中发送消息的部分(只有这部分是有用的)
privatevoidsendMessage(intwhat,Objectobj,intarg1,intarg2,booleanasync){
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
顺带一说:上面app.thread.scheduleLaunchActivity()
的第7个参数,task
字段包含了一个ActivityStack,就是我们即将创建的Activity
所在的ActivityStack
,而如果是通过直接调用Context
类的startActivity()
方法;这种方式启动的Activity
没有 Activity栈,因此不能以 standard 方式启动,必须加上FLAG_ACTIVITY_NEW_TASK
这个 Flag 。而通常我们都是调用被Activity
类重载过的startActivity()
方法,这个是有 Stack 的。
这一步让ApplicationThread
做好跳转 activity 的准备(一些数据的封装),紧接着通过handle
发送消息通知app.thread
要进行Activity
启动调度了,然后 app.thread
接收到消息的时候才开始进行调度。
- 这个message的接收是在ActivityThread中的
handleMessage(Message msg)
处理的。
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);
- 这句中
handleLaunchActivity()
又调用了performLaunchActivity(r, customIntent);
而最终又调用了这句:
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
兜了一圈又回到Instrumentation
了。结果终于找到了可以hook的点了,就是这个mInstrumentation.newActivity()
。
这一部分详细讲解可以查看:Android应用程序启动过程源代码分析、
Activity生命周期管理
替换Activity加载过程
知道了上面Activity
启动过程,我们要做的就是通过替换掉Instrumentation
类,达到定制插件运行环境的目的。
// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 拿到原始的 mInstrumentation字段
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
//如果没有注入过,就执行替换
if (!(mInstrumentation instanceof PluginInstrumentation)) {
PluginInstrumentation pluginInstrumentation = new PluginInstrumentation(mInstrumentation);
mInstrumentationField.set(currentActivityThread, pluginInstrumentation);
}
这样子就替换掉了系统的Instrumentation
而在Instrumentation
中,有一个方法叫newActivity()
这个方法就是实际创建Activity
的方法,它的返回值就是我们应用中实际使用的 activity。
我们就可以在这里,判断到如果即将加载的 className 是一个插件中的Activity,那么就通过ClassLoader.load(className).newInstance();
创建插件 Activity
并返回来替换掉原本系统要创建的Activity
了。
@Override
publicActivitynewActivity(ClassLoadercl,StringclassName,Intentintent)throwsInstantiationException,IllegalAccessException,ClassNotFoundException{
if (intent != null) {
isPlugin = intent.getBooleanExtra(HJPlugin.FLAG_ACTIVITY_FROM_PLUGIN, false);
}
if (isPlugin && intent != null) {
className = intent.getStringExtra(HJPlugin.FLAG_ACTIVITY_CLASS_NAME);
} else {
isPlugin = HJPlugin.getInstance().getPluginAtySet().contains(className);
}
return super.newActivity(cl, className, intent);
}
插件的跳转支持
如果仅仅是启动一个未安装的Activity
,上面所做的事情已经足够了。但是如果我们需要从插件中启动另一个插件Activity
,就需要多做一些事了。
在Activity
启动时,会调用Instrumentation. execStartActivity()
方法,我们所要做的就是重写这个方法,并且重新定义一个intent,来替换掉原本代码中的intent,这个替换的目的就是为了防止上文提到的ActivityStackSupervisor.startActivityLocked()
安全校验,我们要把 intent 原本的setClass()
方法传入的 class 给替换成一个合法的已经注册过的Activity
(可以是任何一个,只要是注册过就行),接着将原本要启动的插件 Activity 类名作为一个字符串保存在Bundle
里面,这样到我们的Instrumentation.newActivity()
执行时判断如果是一个插件Activity,就不去创建 intent 传递的 Activity.class,而是创建Intent.Bundle
里面保留的插件 Activity。
/**
* 覆盖掉原始Instrumentation类的对应方法,用于插件内部跳转Activity时适配
*
* @Override
*/
publicActivityResultexecStartActivity(Contextwho,IBindercontextThread,IBindertoken,Activitytarget,Intentintent,intrequestCode,Bundleoptions){
replaceIntentTargetIfNeed(who, intent);
try {
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity", Context.class, IBinder.class, IBinder.class,
Activity.class, Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("do not support!!!" + e.getMessage());
}
}
结尾
至此,通过替换掉系统的 Instrumentation,我们已经可以将 Activity 动态加载到应用中了。但是如果完整实现出来,还会有个问题,就是类可以完美执行,但是资源还不能加载进来,下章就讲资源的加载以及 so文件和 Service 的加载了。【8个类搞定插件化——Service实现方案】