Small的解决的问题
开发时:让你完全透明的像开发普通工程一样完成插件开发
编译时:自动化的帮助你分离各个公共库、业务模块插件(插件仅保留自身的代码跟资源,达到最小化)
运行时:运用最少量的Hook无缝的将各个插件并入宿主,让代码跟资源完全融合,自由调用
Hook Instrumentation对象欺骗系统启动插件activity
首先替换系统instrumentation,替换ActivityThread中的mCallback,mCallback中处理了很多的消息类型,比如LAUNCH_ACTIVITY或者CREATE_SERVICE,通过在这里拦截替换成自己的activity和service来加载插件的activity和service
@Override
public void setUp(Context context) {
super.setUp(context);
if (sHostInstrumentation == null) {
try {
// Inject instrumentation
final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
final Method method = activityThreadClass.getMethod("currentActivityThread");
Object thread = method.invoke(null, (Object[]) null);
Field field = activityThreadClass.getDeclaredField("mInstrumentation");
field.setAccessible(true);
sHostInstrumentation = (Instrumentation) field.get(thread);
Instrumentation wrapper = new InstrumentationWrapper();
field.set(thread, wrapper);
if (context instanceof Activity) {
field = Activity.class.getDeclaredField("mInstrumentation");
field.setAccessible(true);
field.set(context, wrapper);
}
// Inject handler
field = activityThreadClass.getDeclaredField("mH");
field.setAccessible(true);
Handler ah = (Handler) field.get(thread);
field = Handler.class.getDeclaredField("mCallback");
field.setAccessible(true);
field.set(ah, new ActivityThreadHandlerCallback());
} catch (Exception ignored) {
ignored.printStackTrace();
// Usually, cannot reach here
}
}
}
其中InstrumentationWrapper这个类重写了execStartActivity方法
/** @Override V21+
* Wrap activity from REAL to STUB */
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, android.os.Bundle options) {
wrapIntent(intent);
return ReflectAccelerator.execStartActivity(sHostInstrumentation,
who, contextThread, token, target, intent, requestCode, options);
}
/** @Override V20-
* Wrap activity from REAL to STUB */
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode) {
wrapIntent(intent);
return ReflectAccelerator.execStartActivity(sHostInstrumentation,
who, contextThread, token, target, intent, requestCode);
}
通过wrapIntent方法伪装成启动的是menifest中注册的桩activity来骗取系统的声明周期管理和合法性校验
private void wrapIntent(Intent intent) {
ComponentName component = intent.getComponent();
String realClazz;
if (component == null) {
// Implicit way to start an activity
component = intent.resolveActivity(Small.getContext().getPackageManager());
if (component != null) return; // ignore system or host action
realClazz = resolveActivity(intent);
if (realClazz == null) return;
} else {
realClazz = component.getClassName();
}
if (sLoadedActivities == null) return;
ActivityInfo ai = sLoadedActivities.get(realClazz);
if (ai == null) return;
// Carry the real(plugin) class for incoming `newActivity' method.
intent.addCategory(REDIRECT_FLAG + realClazz);
String stubClazz = dequeueStubActivity(ai, realClazz);
intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
}
接着在替换的mCallback中拦截intant替换成自己的activity
/**
* Class for restore activity info from Stub to Real
*/
private static class ActivityThreadHandlerCallback implements Handler.Callback {
private static final int LAUNCH_ACTIVITY = 100;
@Override
public boolean handleMessage(Message msg) {
if (msg.what != LAUNCH_ACTIVITY) return false;
Object/*ActivityClientRecord*/ r = msg.obj;
Intent intent = ReflectAccelerator.getIntent(r);
String targetClass = unwrapIntent(intent);
if (targetClass == null) return false;
// Replace with the REAL activityInfo
ActivityInfo targetInfo = sLoadedActivities.get(targetClass);
ReflectAccelerator.setActivityInfo(r, targetInfo);
return false;
}
}
非常简洁的实现了插件activity的加载,下面看下menifest中的桩
<!-- Stub Activities -->
<!-- 1 standard mode -->
<activity
android:name="net.wequick.small.A"
android:launchMode="standard" />
<activity
android:name="net.wequick.small.A1"
android:theme="@android:style/Theme.Translucent" />
<!-- 4 singleTask mode -->
<activity
android:name="net.wequick.small.A10"
android:launchMode="singleTask" />
<activity
android:name="net.wequick.small.A11"
android:launchMode="singleTask" />
<activity
android:name="net.wequick.small.A12"
android:launchMode="singleTask" />
<activity
android:name="net.wequick.small.A13"
android:launchMode="singleTask" />
<!-- 4 singleTop mode -->
<activity
android:name="net.wequick.small.A20"
android:launchMode="singleTop" />
<activity
android:name="net.wequick.small.A21"
android:launchMode="singleTop" />
<activity
android:name="net.wequick.small.A22"
android:launchMode="singleTop" />
<activity
android:name="net.wequick.small.A23"
android:launchMode="singleTop" />
<!-- 4 singleInstance mode -->
<activity
android:name="net.wequick.small.A30"
android:launchMode="singleInstance" />
<activity
android:name="net.wequick.small.A31"
android:launchMode="singleInstance" />
<activity
android:name="net.wequick.small.A32"
android:launchMode="singleInstance" />
<activity
android:name="net.wequick.small.A33"
android:launchMode="singleInstance" />
<!-- Web Activity -->
<activity
android:name="net.wequick.small.webkit.WebActivity"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden|adjustPan" />
</application>
这种写法的好处是系统托管activity的launchMode,弊端是activity的数量收到限制,因为一个插件activity就对应一个桩,当桩的数量不够时不好处理,更好的方法是只注册一个activity,自己管理launchMode,记录下每一个启动的activity的launchMode,按照launchMode的规则自己对应真实的插件activity
资源分区,解决宿主和插件的资源id冲突
详解:https://github.com/wequick/Small/wiki/Android-dynamic-load-resources
工程结构
Small对工程结构做了较大的调整
app:host工程
app.:app插件工程;
lib.:library插件工程;
web.*:web插件工程;
其他:其他assert 工程;
所谓插件就是指的这些module,每一个module在编译插件的时候都会生成一个.so文件,签名后被自动放在host里面,host实际是一个空壳,调用各个插件,下图解读开发、编译、运行三个阶段
插件的发展历程
Android Dynamic Loader
dynamic-load-apk
CJFrameForAndroid
android-pluginmgr
Direct-Load-apk
DroidPlugin
DynamicAPK
ACDD
Small
Android-Plugin-Framework
OSGIService
归类总结
我把插件框架的应用场景分为三类
1)插件之间完全隔离,目的是把所有别的应用变成自己的插件,典型的是360的DroidPlugin,实现的最彻底,插件之间完全不认识对方,不能调用对方的代码&资源
2)插件之间通过接口隔离,典型的通过osgi构建一套接口,比如
ACDD,OSGIService
3)插件和宿主完全融合,代码资源能够互调,典型的Small
这是两个不同的方向,一个旨在彻底的分离插件,一个旨在分离同一个app。
发展方向
从桌面应用的发展方向来看,最终大家都会像web靠拢,通过简单的配置几个标签就能实现应用,android&ios都只是在朝着这个方向努力,目前的reactnative,阿里的weex,这是发展方向,个人见解多关注下这类的框架