在前面的几篇文章中已经介绍完了Android插件化的第一和第二个根本问题,就是宿主和插件的代码互相调用问题和插件中资源的读取问题。现剩下的就是Android插件化里最麻烦的第三个根本问题,也就是在插件中使用四大组件的问题。我们知道,目前插件中的四大组件要想正常使用就必须要在宿主中的AndroidManifest.xml中提前声明好,因为四大组件在启动过程中只认宿主中的AndroidManifest.xml,插件中可以不需要声明。那样的话,在开发过程中难免会非常不方便,要想在插件中增加一个Activity或Service那就必须同时修改宿主,这样就违背了插件化免安装和灵活更新的初衷。
我们在前面第一篇文章中也提到了什么情况需要插件化问题上,有一点是在模块功能相对成熟稳定后才比较适合去做成插件化。因为到了模块工程的后期更新幅度不会太大,我们只需要在宿主AndroidManifest.xml中预留声明几个命名不太友好的Activity和Service的空声明就能应付一般情况下的更新,等到下次大版本连宿主一起更新时再来重构回来或又再新增几个坑位以备不时之需。
其实目前大多数插件化框架的最大差异地方就是在于如何在解决插件中使用四大组件上做得更好,针对此情况大致有两大流派:
动态替换方案,以Activity为例,主要是对Android底层代码进行Hook,使在App启动Activity中进行欺骗ActivityManagerService,以达到加载插件中组件的目的。
静态代理方案,以Activity为例,通过宿主中的一个代理Activity统一操纵插件中的所有Activity。
动态替换方案做得最好的应该是360的DroidPlugin插件化框架,它可以做到不需在宿主中去声明插件中的组件信息,意义上已经可以加载任何别人家的apk了。它的思路大概是:不合并插件中的dex,因为LoadedApk是保存apk四大组件信息和其它信息的类型,框架代码中为插件也创建了一个LoadedApk对象,并放于ActivityThread的mPackages数组变量中,再为每个插件都创建一个ClassLoader,然后把LoadedApk对象的ClassLoader改为插件的ClassLoader。这样等一系列的反射中就能达到启动插件Activity的目的,但是缺点也是相当明显,就是要反射的地方非常多,而且还要适配不同的Android版本。我们并不打算去分析这个复杂的过程,知道是大概原理就好了。
有一种相对比较简单一点的方案,就是欺骗ActivityManagerService,目前像ACCD插件化框架就是使用这方案。思路大概是先在宿主中定义一个替身Activity,然后也是在启动Activity过程中通过反射Hook两处地方代码,第一处是在准备跨进程调用ActivityManagerService前,即Instrumentation.java的execStartActivity方法中通过ActivityManagerNative.getDefault()它返回一个IActivityManager对象,我们对它创建一个代理,在IActivityManager对象去调用startActivity前把目标Activity替换成替身Activity,以达到欺骗目的。然后第二处是在ActivityManagerSerfvice跨进程回来后,在ActivityThread.java中接收LAUNCH_ACTIVITY消息前,可以对Handle的callback进行代码,让其消息接收前将目标Activity换回来。这样做就能达到只需要在宿主中只声明一个替身Activity就能满足于插件中Actvitiy(如不清楚Activity的启动流程可以回顾前面文章《Android应用程序启动详解(二)之Application和Activity的启动过程》)。这两处地方所做事情,用代码可以这样:
public class AMSHookHelper {
public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
public static void hookActivity() {
try {
hookAMN();
hookActivityThread();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 第一处Hook地方,Hook ActivityManagerNative中通过getDefault方法获得的对象,使其在调用startActivity时替换成替身Activity,以达到欺骗ActivityManagerSerfvice的目的
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
private static void hookAMN() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
// 获取 ActivityManagerNative 的 gDefault
Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefaultObj = gDefaultField.get(null);
// 获取 gDefault 对应在 android.util.Singleton 的单例对象 mInstance
Class singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstanceObj = mInstanceField.get(gDefaultObj);
// 创建 gDefault 的代理
Class> classInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class>[] { classInterface },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只 Hook startActivity 一个方法
if (!"startActivity".equals(method.getName())) {
return method.invoke(mInstanceObj, args);
}
// 找到参数里面的Intent 对象
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
Intent targetIntent = (Intent) args[index];
boolean isExistActivity = isExistActivity(targetIntent.getComponent().getClassName());
if (isExistActivity) {
return method.invoke(mInstanceObj, args);
}
String stubPackage = targetIntent.getComponent().getPackageName();
Intent newIntent = new Intent();
newIntent.setComponent(new ComponentName(stubPackage, StubActivity.class.getName()));
// 把原来要启动的目标Activity先存起来
newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, targetIntent);
// 替换掉Intent, 即欺骗AMS要启动的是替身Activity
args[index] = newIntent;
return method.invoke(mInstanceObj, args);
}
}
);
//把 gDefault 的 mInstance 字段,替换成 proxy
mInstanceField.set(gDefaultObj, proxy);
}
/**
* 判断Activity是否有在宿主中声明
* @param activity
* @return
*/
private static boolean isExistActivity(String activity) {
try {
PackageManager packageManager = HostApplication.getContext().getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(HostApplication.getContext().getPackageName(), PackageManager.GET_ACTIVITIES);
ActivityInfo[] activities = packageInfo.activities;
for(int i = 0; i < activities.length; i++) {
if (activity.equalsIgnoreCase(activities[i].name)) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 第二处Hook地方,Hook ActivityThread 中的 Handle mCallback 对象,使接收 LAUNCH_ACTIVITY 消息后将替身 Activity 换回目标 Activity
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
private static void hookActivityThread() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
// 获取到当前的 ActivityThread 对象
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object currentActivityThreadObj = sCurrentActivityThreadField.get(null);
// 获取 ActivityThread 对象中的 mH 变量
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mHObj = (Handler) mHField.get(currentActivityThreadObj);
// Hook Handler 的 mCallback 字段
Class handlerClass = Handler.class;
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mHObj, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
int LAUNCH_ACTIVITY = 0;
try {
Class hClass = Class.forName("android.app.ActivityThread$H");
Field launchActivityField = hClass.getDeclaredField("LAUNCH_ACTIVITY");
launchActivityField.setAccessible(true);
LAUNCH_ACTIVITY = (int) launchActivityField.get(null);
if (msg.what == LAUNCH_ACTIVITY) {
// 把替身 Activity 的 Intent 恢复成目标 Activity 的 Intent
Class c = msg.obj.getClass();
Field intentField = c.getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);
Intent targetIntent = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
if (targetIntent != null) {
intent.setComponent(targetIntent.getComponent());
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 走完原来流程
mHObj.handleMessage(msg);
return true;
}
});
}
}
上述方法只要在startActivity之前找个时机调用一下AMSHookHelper.hookActivity();即可,这里还需要在宿主中创建一个替身Activity:
public class StubActivity extends Activity {
}
这个StubActivity啥都不用做,就这样就能解决不在宿主中声明插件的Activity也能正常启动。这个方案虽然也比较简单,但毕竟还是反射了系统底层代码,所以在往后Android版本更新时还要留意是否有兼容问题。还要注意的是,此方案只对LaunchMode是standard才能生效,如果是SingleTop、SingleTask和SingleInstance的话,一般就要加上使用占位思想,就是提前在宿主中各定义N个这3类的Acitivity,然后通过对应关系将插件中的Activtity和这些占位Activity绑定起来,做一个顺序循环使用。这里就不作详细介绍。
静态代理方案相对来说,会比较好理解一点。因为它不需要去Hook任何代码,主要在宿主中创建一个代理的Activity,叫ProxyActivity,让这个ProxyActivity内部有一个对插件Activity的引用,让ProxyActivity的任何生命周期函数都调用插件中的Activity中同名的函数。这也是dynamic-load-apk插件化框架所创,这方案最有趣的一点就是存在一个that关键字。来通过代码看看它的实现。
第一步,也是最关键一步,在宿主中创建一个ProxyActivity,并添加如下代码:
public class ProxyActivity extends Activity {
private Object mRemoteActivity;
private String mClass;
private HashMap mActivityLifecircleMethods = new HashMap();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mClass = getIntent().getStringExtra(Coordinator.EXTRA_CLASS);
launchTargetActivity();
invokeOnCreate();
}
/**
* 反射目标 Activity
*/
void launchTargetActivity() {
try {
// 获取插件的 Activity 对象
Class> localClass = Class.forName(mClass);
Constructor> localConstructor = localClass.getConstructor(new Class[] {});
mRemoteActivity = localConstructor.newInstance(new Object[] {});
// 执行插件 Activity 的 setProxy 方法,传递this过去,使建立双向引用
Method setProxy = localClass.getMethod("setProxy", new Class[] { Activity.class });
setProxy.setAccessible(true);
setProxy.invoke(mRemoteActivity, new Object[] { this });
// 反射插件 Activity 的生命周期函数
launchTargetActivityLifecircleMethods(localClass);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 反射插件 Activity 的生命周期函数
* @param localClass
*/
protected void launchTargetActivityLifecircleMethods(Class> localClass) {
Method onCreate = null;
try {
onCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });
onCreate.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onCreate", onCreate);
String[] methodNames = new String[]{"onRestart", "onStart", "onResume", "onPause", "onStop", "onDestory"};
for (String methodName : methodNames) {
Method method = null;
try {
method = localClass.getDeclaredMethod(methodName, new Class[]{});
method.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put(methodName, method);
}
Method onActivityResult = null;
try {
onActivityResult = localClass.getDeclaredMethod("onActivityResult",
new Class[] { int.class, int.class, Intent.class });
onActivityResult.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onActivityResult", onActivityResult);
}
/**
* 执行插件 Activity 的 onCreate 方法
*/
private void invokeOnCreate() {
Method onCreate = mActivityLifecircleMethods.get("onCreate");
if (onCreate != null) {
try {
Bundle bundle = new Bundle();
onCreate.invoke(mRemoteActivity, new Object[]{bundle});
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
invokeOnActivityResult(requestCode, resultCode, data);
}
private void invokeOnActivityResult(int requestCode, int resultCode, Intent data) {
Method onActivityResult = mActivityLifecircleMethods.get("onActivityResult");
if (onActivityResult != null) {
try {
onActivityResult.invoke(mRemoteActivity, new Object[] { requestCode, resultCode, data });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onStart() {
super.onStart();
invokeOnStart();
}
private void invokeOnStart() {
Method onStart = mActivityLifecircleMethods.get("onStart");
if (onStart != null) {
try {
onStart.invoke(mRemoteActivity, new Object[] {});
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onRestart() {
super.onRestart();
invokeOnRestart();
}
private void invokeOnRestart() {
Method onRestart = mActivityLifecircleMethods.get("onRestart");
if (onRestart != null) {
try {
onRestart.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onResume() {
super.onResume();
invokeOnResume();
}
private void invokeOnResume() {
Method onResume = mActivityLifecircleMethods.get("onResume");
if (onResume != null) {
try {
onResume.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onPause() {
super.onPause();
invokeOnPause();
}
private void invokeOnPause() {
Method onPause = mActivityLifecircleMethods.get("onPause");
if (onPause != null) {
try {
onPause.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onStop() {
super.onStop();
invokeOnStop();
}
private void invokeOnStop() {
Method onStop = mActivityLifecircleMethods.get("onStop");
if (onStop != null) {
try {
onStop.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
invokeOnDestroy();
}
private void invokeOnDestroy() {
Method onDestroy = mActivityLifecircleMethods.get("onDestroy");
if (onDestroy != null) {
try {
onDestroy.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
ProxyActivity在onCreate方法中,接收插件目标Activity的名称,并反射它的实例和生命周期方法,其间将自己this传递给插件目标Activity,形成双向引用,最后调用了插件目标Activity的onCreate方法。
第二步,在插件中创建一个外表很像Activity的类BasePluginActivity:
public class BasePluginActivity {
protected Activity that;
public void setProxy(Activity proxyActivity) {
that = proxyActivity;
}
protected void onCreate(Bundle savedInstanceState) {
}
public void setContentView(int layoutResID) {
that.setContentView(layoutResID);
}
}
此类就是用于插件中所有Activity的基类,也就是它接收了宿主ProxyActivity传递过来的引用,这里用变量that来保存。
第三步,在插件中创建目标Activity,并使其继承BasePluginActivity:
public class PluginActivity extends BasePluginActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin2);
}
}
这是一个看起来正常不过的Activity,但是其实它的onCreate和setContentView方法都是调用了它的父类BasePluginActivity同名方法,然后通过父类中的that指向宿主中ProxyActivity的同名方法。
第四步,就是调用插件中的Activity了:
Intent intent = new Intent();
intent.setClass(MainActivity.this, ProxyActivity.class);
intent.putExtra(Coordinator.EXTRA_CLASS, "com.zyx.plugin.PluginActivity");
startActivity(intent);
所有要启动插件中的Activity时,都必要启动宿主的ProxyActivity,然后将插件Activity的完整类名附加到EXTRA_CLASS参数中。
就这样,静态代理方案也就完成了。其实此方案所做的一系列事情还是比较好理解,不像动态替换方案那样,还需先把Activity的启动过程先理解一遍,而且也不用担心会因为Android的版本更新而导致反射失败。但是同样要注意的是,此方案也只对LaunchMode是standard才能生效。
对于同一个Service调用多次startService并不会启动多个Service实例,所以如果按照Activity的动态替换方案,弄一个替身StubService是应付不了多插件Service的。其实在我们日常发开中,绝大部分情况Service的数量不超过10个,Service也不像Activity那样有不同的LaunchMode。有些插件化框架会预先创建10个StubService占位,然后还是通过Hook的办法,再加上一个配置表来绑定插件中哪个Service和宿主中几号StubService进行关系绑定来处理。这办法也是耗尽心思做了差强人意的事情,以其那样,还不如就直接在宿主中的AndroidManifest.xml中只预先声明10个备用的Service,然后在插件中需要使用时,插件也命名一个同名的Service来得痛快,虽然这样的话插件中的Service命名会很奇怪,但是我们可以用注释来标明Service的用途和对宿主中AndroidManifest.xml中标注哪些Service声明已经被使用,这样开发起来也简单,而且不需要去维护那个绑定关系的配置表。
BroadcastReceiver分动态注册和静态注册两种,对于动态注册广播,插件化已经默认支持了,因为它不需要在AndroidManifest.xml中去注册声明,而是通过代码就能注册。而对于静态注册广播,也可以借助在宿主AndroidManifest.xml中预声明占位来解决,而且一个插件只占一个位就足够了,因为广播都要携带一个或多个Action,我们在插件中可以通过Action来区分做事情。
Contentprovider就是一个数据库引擎,向外界提供增删改查的接口。它在插件化中的解决方案完全可以借鉴BroadcastReceiver方案,借助在宿主AndroidManifest.xml中预声明占位,也是一个插件只占一个位。然后通过URI来区分做事情。
插件化中四大组件的解决从来都是开发者最头痛的事情。上述解决方案中,你是不是觉得Service、BroadcastReceiver和ContentProvider的解决方案很粗糙?是又怎么样,谁叫它们使用的场景没有Activity多。其实Activity除了LaunchMode是standard外的解决方案也并不完美。当然目前热门的那几个插件化框架肯定没有这么粗糙的解决,笔者这样只是做一个简单的入门介绍和思路分析。还是要再次强调那句建议:项目中模块功能相对成熟稳定后才比较适合去做成插件化!