8 classes to get plug-in - Activity implementation scheme

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. 
Android plug-in

Activity loading process

First, let's talk about the most important function, the dynamic loading of Activity. Looking at the source code we know

Android Plugin 2

  • Each Activitystartup process is startActivityForResult() eventually called byInstrument.execStartActivity()
  • Then through the process of ActivityManagerNative.startActivity() IPC  ,AMSActivityManagerService.startActivity()
  • Finally  ActivityStackSupervisor.startActivityLocked(), permissions and security checks mService.checkPermission. If we Activitydo 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 callApplicationThread.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实现方案

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326712110&siteId=291194637