本文原创,转载请以链接形式注明地址: http://kymjs.com/code/2016/05/15/01
前两篇文章写完后,有人跟我说怎么觉得你文章风格突然变了,最近讲了这么多内容变啰嗦了,没有你高效率精简的风格了。宝宝心里苦啊,不是我不想,实在是插件化这东西,如果你不知道理论知识的话,根本没办法去理解啊。接下来这几篇我尽可能的以实践为主,让大家都能看得懂。
在 序文 【Android 插件化的过去 现在 未来】 中简单的跟大家讲过现在开源社区中所有插件化的基本实现原理。
从本文开始就带大家用最简单的办法实现一个插件化库。
首先讲讲最主要的功能,Activity 的动态加载。查看源码我们知道
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
接收到消息的时候才开始进行调度。
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
启动过程,我们要做的就是通过替换掉 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 的加载了。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!