神刀安全网

8个类搞定插件化(上) – 张涛

写在前面

本文原创,转载请以链接形式注明地址: http://kymjs.com/code/2016/05/15/01

前两篇文章写完后,有人跟我说怎么觉得你文章风格突然变了,最近讲了这么多内容变啰嗦了,没有你高效率精简的风格了。宝宝心里苦啊,不是我不想,实在是插件化这东西,如果你不知道理论知识的话,根本没办法去理解啊。接下来这几篇我尽可能的以实践为主,让大家都能看得懂。

在 序文 【Android 插件化的过去 现在 未来】 中简单的跟大家讲过现在开源社区中所有插件化的基本实现原理。

从本文开始就带大家用最简单的办法实现一个插件化库。

8个类搞定插件化(上) - 张涛

Activity 加载过程

首先讲讲最主要的功能,Activity 的动态加载。查看源码我们知道

8个类搞定插件化(上) - 张涛

  • 每个 Activity 的启动过程都是通过 startActivityForResult() 最终都会调用 Instrument.execStartActivity()
  • 再由 ActivityManagerNative.startActivity() 通过 IPC AMS 所在进程, ActivityManagerService.startActivity()
  • 最后 ActivityStackSupervisor.startActivityLocked() ,权限以及安全检查 mService.checkPermission 。我们的 Activity 如果不注册就会在这个检查时返回一个没有注册的错误,最后回到应用进程的时候抛出这个没注册的异常。
  • 安全校验完成以后,会调用 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中发送消息的部分(只有这部分是有用的) private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {     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()

替换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);  //如果没有注入过,就执行hook 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 public Activity newActivity(ClassLoader cl, String className, Intent intent)         throws InstantiationException, 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  */ public ActivityResult execStartActivity(         Context who, IBinder contextThread, IBinder token, Activity target,         Intent intent, int requestCode, Bundle options) {     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个类搞定插件化(上) – 张涛

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址