簡介
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 >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自定義插件。