Android插件化框架使用心得(原理篇)
2016-09-11 14:19
579 查看
工作原理
android 系统运行的基础是基于四大组件,那么插件化框架工作过程也离不开四大组件相关的流程(生命周期等),而 DroidPlugin 是一种非侵入式的设计方案,即插件程序,可以完全按照正常的app开发方式,和宿主间几乎无耦合,同时,宿主启动apk的过程,完全按照android原生api的调用方式,进行启动。以Activity为例,简要描述下在安装插件app的情况下,启动一个插件中Activityin的原理。
首先看下Activity的启动原理,这个是源码层面的问题,有很多分析源码层面的文章,会详细说明,这里借用一张图来说明,
![](http://odbsc6ngk.bkt.clouddn.com/image/activity_flow.jpeg)
如果看过源码分析,会发现其实这张图,只是简略的描述了下启动过程,不过还是可以看出启动一个Activity,最主要的两个方法,startActivity(),handleLaunchActivity(),前者是启动时,最早调用的方法,后者是android内部,实现activity跳转时,一个重要的方法。
由于插件的Activity,在插件的manifest中定义,而通过宿主启动插件的时候,如果是系统API的正常流程,在startActivity和handleLaunchActivity之间,就会有流程进行判断,检测出启动的activity包名,和宿主不对应,直接就会启动失败了。
于是DroidPlugin框架想了个办法,加载DroidPlugin后,预先注册了一些Activity,startActivity时,通过Hook的方式,将需要跳转的插件,Activity信息先替换为 DroidPlugin 框架中占坑预埋的 Activity (同时备份一份),是后续流程的检查和验证流程能顺利通过,然后在handleLaunchActivity 流程中,将备份的信息再替换回去。后续的流程,不会再有检查类的相关信息。
简单看下DroidPlugin中的这两个方法是如何Hook的,翻看源码,
以下是Activity相关Hook的类,继承了抽象类ProxyHook
public class IActivityManagerHook extends ProxyHook { private static final String TAG = IActivityManagerHook.class.getSimpleName(); public IActivityManagerHook(Context hostContext) { super(hostContext); } …… }
ProxyHook最终实现了InvocationHandler
public abstract class ProxyHook extends Hook implements InvocationHandler { protected Object mOldObj; public ProxyHook(Context hostContext) { super(hostContext); } …… }
这是ProxyHook中的关键代码,可以大致看出,invoke执行的hook方法,可能和HookedMethodHandler有关。
try { if (!isEnable()) { return method.invoke(mOldObj, args); } HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method); if (hookedMethodHandler != null) { return hookedMethodHandler.doHookInner(mOldObj, method, args); } return method.invoke(mOldObj, args); }
找到 Activity 相关的 HookedMethodHandler 为 IActivityManagerHookHandle
从来找到了这个类中 hook 的 startActivity 方法
private static class startActivity extends ReplaceCallingPackageHookedMethodHandler { public startActivity(Context hostContext) { super(hostContext); } protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException { int intentOfArgIndex = findFirstIntentIndexInArgs(args); if (args != null && args.length > 1 && intentOfArgIndex >= 0) { Intent intent = (Intent) args[intentOfArgIndex]; //XXX String callingPackage = (String) args[1]; if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) { PluginPatchManager.getInstance().startPluginActivity(intent); return false; } ActivityInfo activityInfo = resolveActivity(intent); if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) { ComponentName component = selectProxyActivity(intent); if (component != null) { Intent newIntent = new Intent(); try { ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName()); setIntentClassLoader(newIntent, pluginClassLoader); } catch (Exception e) { Log.w(TAG, "Set Class Loader to new Intent fail", e); } newIntent.setComponent(component); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.setFlags(intent.getFlags()); String callingPackage = (String) args[1]; if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) { newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); args[intentOfArgIndex] = newIntent; args[1] = mHostContext.getPackageName(); } else { Log.w(TAG, "startActivity,replace selectProxyActivity fail"); } } } return true; }
上述这个方法中,最关键的就是这个 doReplaceIntentForStartActivityAPIHigh 方法
首先,拿到 Intent 参数,然后根据包名判断通过Intent启动的Activity是否在插件中,
如果存在,执行后续 hook 流程
Intent intent = (Intent) args[intentOfArgIndex] ActivityInfo activityInfo = resolveActivity(intent); if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) ……
然后通过 selectProxyActivity 从插件化框架之前占坑的 Activity 中选择一个出来
ComponentName component = selectProxyActivity(intent); if (component != null) { Intent newIntent = new Intent(); try { ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName()); setIntentClassLoader(newIntent, pluginClassLoader); } catch (Exception e) { Log.w(TAG, "Set Class Loader to new Intent fail", e); } }
之后构造一个新的Intent对象:newIntent,将之前选出的占坑的 Activity 中的信息调入到这个Intent中,同时将 启动插件的 Intent 信息 备份一份在 newIntent 中 newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
newIntent.setComponent(component); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.setFlags(intent.getFlags());
然后把newIntent 重新复制到 原来Intent应该在的参数位置。这样Intent信息就被替换掉,于是就能跳过后续,Activity启动机制的检查了。
args[intentOfArgIndex] = newIntent; args[1] = mHostContext.getPackageName();
简单回顾下Activity启动过程中的一个流程,中间有个重要的环节就是,AMS 通过一系列交互,走到了ActivityThread中的 scheduleLaunchActivity 这个方法中,会发送一个启动Activity的消息出去,给 Handler H 处理(这个 Handler 在源码中的命名就是 H),而 Hook Activity 关键流程的第二步就在这个 Handler 的处理中,翻看 ActivityThread 源码可以发现,关键一步在于 handleLaunchActivity
public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { 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); } break; …… }
再往之后的流程,就走到了,创建Application, 创建Activity的流程,于是,插件化框架考虑在handleLaunchActivity,通过Hook的方式,将先前 startActivity 中改写的信息在替换回来。
主要通过了PluginCallbackHook 这个类进行了hook
protected void onInstall(ClassLoader classLoader) throws Throwable { Object target = ActivityThreadCompat.currentActivityThread(); Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass(); /*替换ActivityThread.mH.mCallback,拦截组件调度消息*/ Field mHField = FieldUtils.getField(ActivityThreadClass, "mH"); Handler handler = (Handler) FieldUtils.readField(mHField, target); Field mCallbackField = FieldUtils.getField(Handler.class, "mCallback"); //*这里读取出旧的callback并处理*/ Object mCallback = FieldUtils.readField(mCallbackField, handler); if (!PluginCallback.class.isInstance(mCallback)) { PluginCallback value = mCallback != null ? new PluginCallback(mHostContext, handler, (Handler.Callback) mCallback) : new PluginCallback(mHostContext, handler, null); value.setEnable(isEnable()); mCallbacks.add(value); FieldUtils.writeField(mCallbackField, handler, value); Log.i(TAG, "PluginCallbackHook has installed"); } else { Log.i(TAG, "PluginCallbackHook has installed,skip"); } }
上述代码,主要是在通过反射拿到 ActivityThread 中的“mH” 对象,就是前文所述 Handler H,之后在拿到 Handler 类中 mCallback 对象,之后使用 PluginCallback 替换掉这个 callback,此 callback 中,就完成了对 Handler 消息处理,所以handleLaunchActivity就在这个流程中。
下面来看下 PluginCallback 中的具体操作。其实和 ActivityThread 源码比较类似。都是通过一个 handleMessage 来处理消息,真正的“替换”流程,还是在 handleLaunchActivity 中。
先拿到之前 在 startActivity 时,备份的 插件中目标Activity 的 Intent 信息
Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent"); stubIntent.setExtrasClassLoader(mHostContext.getClassLoader()); Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
之后将targetIntent 替换掉原来消息对象中的 Intent 信息
FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
然后后续继续处理,msg信息
if (mCallback != null) { return mCallback.handleMessage(msg); } else { return false; }
Acitivity 启动中,第二个重要的 hook 流程就结束了。中间过程还有很多细节处理,这里暂时不作过多说明。
参考资料
DroidPlugin 官方文档田维术 系列文章
CSDN 一个简要的系列文章
相关文章推荐
- Android插件化框架使用心得 (使用篇)
- Android 插件化框架 RePlugin 使用心得
- Android插件化框架使用心得 (细节篇)
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android ORM 框架之 greenDAO 使用心得
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- 视频框架 Vitamio 使用教程+部分心得 (四) 滑动控制声音和亮度+android 4.4.X 以上时全屏播放
- Android自助餐之插件化(使用Small框架)
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android ORM 框架之 greenDAO 使用心得