简介
Small框架写得非常简洁,核心类只有几个。大概涉及以下几部分:
- gradle-small插件:Small中的一个gradle自定义插件,用于打包组件
- aapt:用于分离资源文件,重设资源id等等
- 插件类的加载:动态加载.so包
- 插件资源id冲突问题
- Activity启动和生命周期问题
如果没有看过Small的wiki,建议先看一下Small Android
三个核心问题的解决
插件类的加载
这个问题的解决和其它插件化框架的解决方法差不多。
wiki 里面写得很清楚了。Android的类是由DexClassLoader加载的,通过反射可以将插件包动态加载进去。Small的gradle插件生成的是.so包,在初始化的时候会通过.so文件生成.zip文件,再由.zip文件生成一个dex元素,反射添加到宿主类加载器的dexPathList里。
插件资源id冲突问题
small的作者通过修改aapt的生成产物解决了。这一部分涉及到gradle自定义插件的内容,稍后再分析。wiki Android dynamic load resources 里面说得比较清楚。插件里的资源通过AssetManager加载。
解决Activity注册和生命周期问题
这个问题也解决得非常巧妙。具体可以先看这里wiki。大概就是,在宿主工程里预先注册几个Activity占坑,Instrumentation启动Activity时导向已注册的Activity,但是在Instrumentation生成Activity实例时(newActivity方法)通过真实的类生成。Activity的启动是在Instrumentation 里实现的,用InstrumentationWrapper类继承Instrumentation,并且通过反射将ActivityThread 里的mInstrumentation实例替换掉。
- 第一步,在宿主Activity注册特殊命名的Activity占坑
<application>
<!-- Stub Activities -->
<!-- 1 standard mode -->
<activity android:name=".A.0" android:launchMode="standard"/>
<!-- 4 singleTask mode -->
<activity android:name=".A.1$0" android:launchMode="singleTask"/>
<activity android:name=".A.1$1" android:launchMode="singleTask"/>
<activity android:name=".A.1$2" android:launchMode="singleTask"/>
<activity android:name=".A.1$3" android:launchMode="singleTask"/>
<!-- 4 singleTop mode -->
<activity android:name=".A.2$0" android:launchMode="singleTop"/>
<activity android:name=".A.2$1" android:launchMode="singleTop"/>
<activity android:name=".A.2$2" android:launchMode="singleTop"/>
<activity android:name=".A.2$3" android:launchMode="singleTop"/>
<!-- 4 singleInstance mode -->
<activity android:name=".A.3$0" android:launchMode="singleInstance"/>
<activity android:name=".A.3$1" android:launchMode="singleInstance"/>
<activity android:name=".A.3$2" android:launchMode="singleInstance"/>
<activity android:name=".A.3$3" android:launchMode="singleInstance"/>
<!-- Web Activity -->
<activity android:name=".webkit.WebActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden|adjustPan"/>
<!--<service android:name="net.wequick.small.service.UpgradeService"-->
<!--android:exported="false"/>-->
</application>
- 第二步,Small初始化时注入instrumentation
@Override
public void setUp(Context context) {
super.setUp(context);
// Inject instrumentation
if (sHostInstrumentation == null) {
try {
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);
}
} catch (Exception ignored) {
ignored.printStackTrace();
// Usually, cannot reach here
}
}
}
在继承的InstrumentationWrapper类中,wrapIntent方法将启动的Activity导向已注册的占坑Activity,并把真实要启动的Activity类名存在intent的Category中,在newActivity方法执行时unwrapIntent,启动真实的Activity
/** @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.execStartActivityV21(sHostInstrumentation,
who, contextThread, token, target, intent, requestCode, options);
}
private void wrapIntent(Intent intent) {
String realClazz = intent.getComponent().getClassName();
ActivityInfo ai = sLoadedActivities.get(realClazz);
if (ai == null) return;
intent.addCategory(REDIRECT_FLAG + realClazz); //实际要启动的Activity类名储存在intent的Category中,加上一个特殊的REDIRECT_FLAG标记
String stubClazz = dequeueStubActivity(ai, realClazz);
intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
}
@Override
/** Unwrap activity from STUB to REAL */
public Activity newActivity(ClassLoader cl, String className, Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
// Stub -> Real
if (!className.startsWith(STUB_ACTIVITY_PREFIX)) {
return super.newActivity(cl, className, intent);
}
className = unwrapIntent(intent, className);
Activity activity = super.newActivity(cl, className, intent);
return activity;
}
private String unwrapIntent(Intent intent, String className) {
Set<String> categories = intent.getCategories();
if (categories == null) return className;
// Get plugin activity class name from categories
Iterator<String> it = categories.iterator();
String realClazz = null;
while (it.hasNext()) {
String category = it.next();
if (category.charAt(0) == REDIRECT_FLAG) {
realClazz = category.substring(1);
break;
}
}
if (realClazz == null) return className;
return realClazz;
}
Small的初始化
Small主要有下面这几个类
Small类:提供操作Bundle的静态方法
Bundle类: 加载组件,用类加载器把.so文件加载进来,解析bundle.json配置文件
Launcher :用于启动宿主activity或插件activity
初始化流程
下面讲讲Small的初始化过程,从setUp方法开始
public static void setUp(Context context, Bundle.OnLoadListener listener) {
Context appContext = context.getApplicationContext();
sContext = appContext;
saveActivityClasses(appContext);
LocalBroadcastManager.getInstance(appContext).registerReceiver(new OpenUriReceiver(),
new IntentFilter(EVENT_OPENURI));
//判断宿主版本
int backupHostVersion = getHostVersionCode();
int currHostVersion = 0;
try {
PackageInfo pi = appContext.getPackageManager().getPackageInfo(
appContext.getPackageName(), 0);
currHostVersion = pi.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (backupHostVersion != currHostVersion) {
sIsNewHostApp = true;
setHostVersionCode(currHostVersion);
clearAppCache(appContext);
} else {
sIsNewHostApp = false;
}
// Register default bundle launchers
registerLauncher(new ActivityLauncher());
registerLauncher(new ApkBundleLauncher());
registerLauncher(new WebBundleLauncher());
//初始化bundle
Bundle.setupLaunchers(context);
// Load bundles
Bundle.loadLaunchableBundles(listener);
}
主要做了以下这些事:
- 记录host中注册的activity,用hashmap存起来,对应saveActivityClasses方法
- 注册bundle launcher并初始化
- 解析bundle.json文件
bundle launcher的初始化
public static void setupLaunchers(Context context) {
if (sBundleLaunchers == null) return;
for (BundleLauncher launcher : sBundleLaunchers) {
launcher.setUp(context);
}
}
只有ApkBundleLauncher重写了setUp方法,该方法注入instrumentation,为解决Activity的启动和生命周期问题做准备。
@Override
public void setUp(Context context) {
super.setUp(context);
// Inject instrumentation
if (sHostInstrumentation == null) {
try {
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);
}
} catch (Exception ignored) {
ignored.printStackTrace();
// Usually, cannot reach here
}
}
}
解析bundle.json文件,加载bundle
bundle.json用于配置插件的路由,实现了Activity的url化,使插件间可以互相访问
这是bundle.json文件的一个样例
{
"version": "1.0.0",
"bundles": [
{
"uri": "lib.utils",
"pkg": "net.wequick.example.small.lib.utils"
},
{
"uri": "main",
"pkg": "net.wequick.example.small.app.main"
},
{
"uri": "home",
"pkg": "net.wequick.example.small.app.home"
},
{
"uri": "message",
"pkg": "net.wequick.example.small.app.message"
},
{
"uri": "find",
"pkg": "net.wequick.example.small.app.find"
},
{
"uri": "mine",
"pkg": "net.wequick.example.small.app.mine"
},
{
"uri": "detail",
"pkg": "net.wequick.example.small.app.detail",
"rules": {
"page1": ".TestActivity"
}
},
{
"uri": "about",
"pkg": "net.wequick.example.small.web.about"
}
]
}
解析文件的过程比较简单,读取文件,解析为json
/**
* Load bundles from manifest
*/
public static void loadLaunchableBundles(OnLoadListener listener) {
mListener = listener;
Context context = Small.getContext();
// Read manifest file
File manifestFile = new File(context.getFilesDir(), BUNDLE_MANIFEST_NAME);
manifestFile.delete();
String manifestJson;
if (!manifestFile.exists()) {
// Copy asset to files
try {
InputStream is = context.getAssets().open(BUNDLE_MANIFEST_NAME);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
manifestFile.createNewFile();
FileOutputStream os = new FileOutputStream(manifestFile);
os.write(buffer);
os.close();
manifestJson = new String(buffer, 0, size);
} catch (IOException e) {
e.printStackTrace();
return;
}
} else {
try {
BufferedReader br = new BufferedReader(new FileReader(manifestFile));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
manifestJson = sb.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
} catch (IOException e) {
e.printStackTrace();
return;
}
}
// Parse manifest file
try {
JSONObject jsonObject = new JSONObject(manifestJson);
String version = jsonObject.getString("version");
loadManifest(version, jsonObject);
} catch (JSONException e) {
e.printStackTrace();
return;
}
}
加载bundle
下面说说加载bundle时具体干了什么。
- 用.so文件生成dex文件,通过反射添加到类加载器的dexElements里面去
- 一个bundle对应一个.so文件,加载bundle的过程就是用PackageManager把.so文件包含的信息读取出来,将.so的activityInfo读出来,存到一个hashmap里。
public static boolean expandDexPathList(ClassLoader cl, String dexPath,
String libraryPath, String optDexPath) {
try {
File pkg = new File(dexPath);
DexFile dexFile = DexFile.loadDex(dexPath, optDexPath, 0); //.so加载成DexFile
Object element = makeDexElement(pkg, dexFile); //反射生成DexPathList的Element
fillDexPathList(cl, element); //添加到DexPathList里
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Make dex element
* @see <a href="https://android.googlesource.com/platform/libcore-snapshot/+/ics-mr1/dalvik/src/main/java/dalvik/system/DexPathList.java">DexPathList.java</a>
* @param pkg archive android package with any file extensions
* @param dexFile
* @return dalvik.system.DexPathList$Element
*/
private static Object makeDexElement(File pkg, DexFile dexFile) throws Exception {
if (sDexElementClass == null) {
sDexElementClass = Class.forName("dalvik.system.DexPathList$Element");
}
if (sDexElementConstructor == null) {
sDexElementConstructor = sDexElementClass.getConstructors()[0];
}
Class<?>[] types = sDexElementConstructor.getParameterTypes();
switch (types.length) {
case 3:
if (types[1].equals(ZipFile.class)) {
// Element(File apk, ZipFile zip, DexFile dex)
ZipFile zip = new ZipFile(pkg);
return sDexElementConstructor.newInstance(pkg, zip, dexFile);
} else {
// Element(File apk, File zip, DexFile dex)
return sDexElementConstructor.newInstance(pkg, pkg, dexFile);
}
case 4:
default:
// Element(File apk, boolean isDir, File zip, DexFile dex)
return sDexElementConstructor.newInstance(pkg, false, pkg, dexFile);
}
}
private static void fillDexPathList(ClassLoader cl, Object element)
throws NoSuchFieldException, IllegalAccessException {
if (sPathListField == null) {
sPathListField = getDeclaredField(DexClassLoader.class.getSuperclass(), "pathList");
}
Object pathList = sPathListField.get(cl);
if (sDexElementsField == null) {
sDexElementsField = getDeclaredField(pathList.getClass(), "dexElements");
}
expandArray(pathList, sDexElementsField, new Object[]{element}, true);
}
暂时先写到这,下一步会分析Small提供的gradle自定义插件。