您的位置:首页 > 移动开发 > Android开发

Android插件化框架使用心得(原理篇)

2016-09-11 14:19 579 查看

工作原理

android 系统运行的基础是基于四大组件,那么插件化框架工作过程也离不开四大组件相关的流程(生命周期等),而 DroidPlugin 是一种非侵入式的设计方案,即插件程序,可以完全按照正常的app开发方式,和宿主间几乎无耦合,同时,宿主启动apk的过程,完全按照android原生api的调用方式,进行启动。

以Activity为例,简要描述下在安装插件app的情况下,启动一个插件中Activityin的原理。

首先看下Activity的启动原理,这个是源码层面的问题,有很多分析源码层面的文章,会详细说明,这里借用一张图来说明,



如果看过源码分析,会发现其实这张图,只是简略的描述了下启动过程,不过还是可以看出启动一个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 插件化