
這一節(jié)我們要繼續(xù)接著上一節(jié)的內容,來講講插件的加載過程。

二. 插件加載過程:從Bundle 步入 BundleLauncher
上一節(jié)我們讀到loadBundle中,里面涉及一個方法prepareForLaunch,我沒有繼續(xù)往下詳解,搬來這里繼續(xù)給大家做個介紹。
(1)步入BundleLauncher的 loadBundle的前期準備。
private static void loadBundles(List<Bundle> bundles) {
sPreloadBundles = bundles;
// Prepare bundle
for (Bundle bundle : bundles) {
bundle.prepareForLaunch();
}
...
}
protected void prepareForLaunch() {
if (mIntent != null) return;
if (mApplicableLauncher == null && sBundleLaunchers != null) {
for (BundleLauncher launcher : sBundleLaunchers) {
if (launcher.resolveBundle(this)) {
mApplicableLauncher = launcher;
break;
}
}
}
}
sBundleLaunchers這個變量已經(jīng)很熟悉,在前面已經(jīng)多次出現(xiàn)了,我們來回憶一下。

里面只有三個類ApkBundleLauncher、ActivityLauncher、WebBundleLauncher,前面已經(jīng)作了部分解釋,從上圖可知。三者的祖先都是BundleLauncher,resolveBundle方法定義在此處。
/**
* Called when loading bundles by {@link Bundle#loadLaunchableBundles(Small.OnCompleteListener)}.
*
* <p>This method try to preload a bundle, if succeed, load it later.
*
* @hide
* @param bundle the loading bundle
* @return <tt>true</tt> if the <tt>bundle</tt> is resolved
*/
public boolean resolveBundle(Bundle bundle) {
if (!preloadBundle(bundle)) return false;
loadBundle(bundle);
return true;
}
這里會繼續(xù)調用preloadBundle的方法來判斷,然后我發(fā)現(xiàn)preloadBundle在ActivityLauncher和SoBundleLauncher(直接影響ApkBundleLauncher、WebBundleLauncher)中重載了。

1.ActivityLauncher的preloadBundle
@Override
public boolean preloadBundle(Bundle bundle) {
if (sActivityClasses == null) return false;
String pkg = bundle.getPackageName();
return (pkg == null || pkg.equals("main"));
}
sActivityClasses是在ActivityLauncher setUp被填充的,它里面有宿主已經(jīng)注冊好的Activity。sActivityClasses一般不會為null,因此都進入判斷pkg地方,pkg即包路徑,因此preloadBundle會返回false,resolveBundle也會返回false,導致ActivityLauncher會被略過。
ActivityLauncher 它的作用是啟動宿主的Activity。如果bundle.json里pkg為空或者pkg為”main”的bundle,ActivityLauncher也不做特殊處理,因為loadBundle在ActivityLauncher中不被重載,仍為空方法。當啟動的時候,把bundle里的uri當作Activity的類名。URI轉換為Activity的名字規(guī)則如下:如果URI是空的,默認是MainActivity,否則使用URI,如果類不存在,加“Activity”后綴再進行查找。
2.SoBundleLauncher的preloadBundle
@Override
public boolean preloadBundle(Bundle bundle) {
String packageName = bundle.getPackageName();
if (packageName == null) return false;
// Check if supporting
String[] types = getSupportingTypes();
if (types == null) return false;
boolean supporting = false;
String bundleType = bundle.getType();
if (bundleType != null) {
//這里為了解除對先前版本對包名的限制(*.lib.*或者*.app.*),如果在bundle.json聲明type屬性為“l(fā)ib”或者“app”,將忽略包名限制
// Consider user-defined type in `bundle.json'
for (String type : types) {
if (type.equals(bundleType)) {
supporting = true;
break;
}
}
} else {
// Consider explicit type specify in package name as following:
// - com.example.[type].any
// - com.example.[type]any
String[] pkgs = packageName.split("\\.");
int N = pkgs.length;
String aloneType = N > 1 ? pkgs[N - 2] : null;
String lastComponent = pkgs[N - 1];
for (String type : types) {
if ((aloneType != null && aloneType.equals(type))
|| lastComponent.startsWith(type)) {
supporting = true;
break;
}
}
}
if (!supporting) return false;
// Initialize the extract path
File extractPath = getExtractPath(bundle);//重載在ApkBundleLauncher和WebBundleLauncher中,定義不同的路徑。
if (extractPath != null) {
if (!extractPath.exists()) {
extractPath.mkdirs();
}
bundle.setExtractPath(extractPath);
}
//至此,存儲的目錄準備好了
// Select the bundle entry-point, `built-in' or `patch'
File plugin = bundle.getBuiltinFile();
BundleParser parser = BundleParser.parsePackage(plugin, packageName);
File patch = bundle.getPatchFile();
BundleParser patchParser = BundleParser.parsePackage(patch, packageName);
if (parser == null) {
if (patchParser == null) {
return false;
} else {
parser = patchParser; // use patch
plugin = patch;
}
} else if (patchParser != null) {
if (patchParser.getPackageInfo().versionCode <= parser.getPackageInfo().versionCode) {
//排除補丁版本號過低
Log.d(TAG, "Patch file should be later than built-in!");
patch.delete();
} else {
parser = patchParser; // use patch
plugin = patch;
}
}
bundle.setParser(parser);
// Check if the plugin has not been modified
long lastModified = plugin.lastModified();
long savedLastModified = Small.getBundleLastModified(packageName);
if (savedLastModified != lastModified) {//文件已被修改
// If modified, verify (and extract) each file entry for the bundle
if (!parser.verifyAndExtract(bundle, this)) {//校驗CRC、簽名等,通過安排解壓IO事項
bundle.setEnabled(false);
return true; // Got it, but disabled
}
Small.setBundleLastModified(packageName, lastModified);//標記最后修改時間
}
// Record version code for upgrade
PackageInfo pluginInfo = parser.getPackageInfo();
bundle.setVersionCode(pluginInfo.versionCode);
bundle.setVersionName(pluginInfo.versionName);
return true;
}
BundleParser 是一個精簡版的PackageParser.java,用來解析AndroidManifest文件,獲取package.versionCode,package.versionName,package.theme,activities,同時也會收集每個Activity對應的intent-filter。 這個類里還定義了一個內部類R,R文件里包含一個styleable類,此文件定義了平臺公共資源ID,例如版本號id,版本名稱id,主題Id等。
(2)步入BundleLauncher的 loadBundle
這里loadBundle方法在SoBundleLauncher子類(ApkBundleLauncher、AssetBundleLauncher)中被重載,先挑一個簡短的看看,也就是AssetBundleLauncher的loadBundle。
- AssetBundleLauncher的loadBundle
@Override
public void loadBundle(Bundle bundle) {
String packageName = bundle.getPackageName();
File unzipDir = new File(getBasePath(), packageName);
File indexFile = new File(unzipDir, getIndexFileName());
// Prepare index url
String uri = indexFile.toURI().toString();
if (bundle.getQuery() != null) {
uri += "?" + bundle.getQuery();
}
URL url;
try {
url = new URL(uri);
} catch (MalformedURLException e) {
Log.e(TAG, "Failed to parse url " + uri + " for bundle " + packageName);
return;
}
String scheme = url.getProtocol();
if (!scheme.equals("http") &&
!scheme.equals("https") &&
!scheme.equals("file")) {
Log.e(TAG, "Unsupported scheme " + scheme + " for bundle " + packageName);
return;
}
bundle.setURL(url);
}
這個過程主要是嘗試將默認的index.html 轉換成URL對象。
- ApkBundleLauncher的loadBundle
這里主要是通過BundleParser解釋AndroidManifest.xml,收集其中的activities,初始化LoadedApk的一些信息,加載Dex文件以及l(fā)ib庫。
@Override
public void loadBundle(Bundle bundle) {
String packageName = bundle.getPackageName();
BundleParser parser = bundle.getParser();
parser.collectActivities();
PackageInfo pluginInfo = parser.getPackageInfo();
// Load the bundle
String apkPath = parser.getSourcePath();
if (sLoadedApks == null) sLoadedApks = new ConcurrentHashMap<String, LoadedApk>();
LoadedApk apk = sLoadedApks.get(packageName);
if (apk == null) {
apk = new LoadedApk();
apk.packageName = packageName;
apk.path = apkPath;
apk.nonResources = parser.isNonResources();
if (pluginInfo.applicationInfo != null) {
apk.applicationName = pluginInfo.applicationInfo.className;
}
apk.packagePath = bundle.getExtractPath();
apk.optDexFile = new File(apk.packagePath, FILE_DEX);
// Load dex
final LoadedApk fApk = apk;
Bundle.postIO(new Runnable() {
@Override
public void run() {
try {
fApk.dexFile = DexFile.loadDex(fApk.path, fApk.optDexFile.getPath(), 0);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
// Extract native libraries with specify ABI
String libDir = parser.getLibraryDirectory();
if (libDir != null) {
apk.libraryPath = new File(apk.packagePath, libDir);
}
sLoadedApks.put(packageName, apk);
}
if (pluginInfo.activities == null) {
bundle.setLaunchable(false);
return;
}
// Record activities for intent redirection
if (sLoadedActivities == null) sLoadedActivities = new ConcurrentHashMap<String, ActivityInfo>();
for (ActivityInfo ai : pluginInfo.activities) {
sLoadedActivities.put(ai.name, ai);
}
// Record intent-filters for implicit action
ConcurrentHashMap<String, List<IntentFilter>> filters = parser.getIntentFilters();
if (filters != null) {
if (sLoadedIntentFilters == null) {
sLoadedIntentFilters = new ConcurrentHashMap<String, List<IntentFilter>>();
}
sLoadedIntentFilters.putAll(filters);
}
// Set entrance activity
bundle.setEntrance(parser.getDefaultActivityName());
}
使用sLoadedActivities記錄插件中的activities來實現(xiàn)Intent重定向,使用sLoadedIntentFilters記錄intent-filters來實現(xiàn)隱式調用。
(3)重新回到Bundle.loadBundles,分發(fā)到各個launcher postSetUp()
postSetUp前后都等待WebView 初始化完成,避免WebView asset路徑影響。
private static void loadBundles(List<Bundle> bundles) {
...
// Wait for the things to be done on UI thread before `postSetUp`,
// as on 7.0+ we should wait a WebView been initialized. (#347)
while (sRunningUIActionCount != 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (BundleLauncher launcher : sBundleLaunchers) {
launcher.postSetUp();
}
// Wait for the things to be done on UI thread before `postSetUp`,
// as on 7.0+ we should wait a WebView been initialized. (#347)
while (sRunningUIActionCount != 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
...
}
有效的postSetUp僅存與ApkBundleLauncher:
@Override
public void postSetUp() {
super.postSetUp();
if (sLoadedApks == null) {
Log.e(TAG, "Could not find any APK bundles!");
return;
}
Collection<LoadedApk> apks = sLoadedApks.values();
// Merge all the resources in bundles and replace the host one
final Application app = Small.getContext();
String[] paths = new String[apks.size() + 1];
paths[0] = app.getPackageResourcePath(); // add host asset path
int i = 1;
for (LoadedApk apk : apks) {
if (apk.nonResources) continue; // ignores the empty entry to fix #62
paths[i++] = apk.path; // add plugin asset path
}
if (i != paths.length) {
paths = Arrays.copyOf(paths, i);
}
ReflectAccelerator.mergeResources(app, sActivityThread, paths);
// Merge all the dex into host's class loader
ClassLoader cl = app.getClassLoader();
i = 0;
int N = apks.size();
String[] dexPaths = new String[N];
DexFile[] dexFiles = new DexFile[N];
for (LoadedApk apk : apks) {
dexPaths[i] = apk.path;
dexFiles[i] = apk.dexFile;
if (Small.getBundleUpgraded(apk.packageName)) {
// If upgraded, delete the opt dex file for recreating
if (apk.optDexFile.exists()) apk.optDexFile.delete();
Small.setBundleUpgraded(apk.packageName, false);
}
i++;
}
ReflectAccelerator.expandDexPathList(cl, dexPaths, dexFiles);
// Expand the native library directories for host class loader if plugin has any JNIs. (#79)
List<File> libPathList = new ArrayList<File>();
for (LoadedApk apk : apks) {
if (apk.libraryPath != null) {
libPathList.add(apk.libraryPath);
}
}
if (libPathList.size() > 0) {
ReflectAccelerator.expandNativeLibraryDirectories(cl, libPathList);
}
// Trigger all the bundle application `onCreate' event
for (final LoadedApk apk : apks) {
String bundleApplicationName = apk.applicationName;
if (bundleApplicationName == null) continue;
try {
final Class applicationClass = Class.forName(bundleApplicationName);
Bundle.postUI(new Runnable() {
@Override
public void run() {
try {
BundleApplicationContext appContext = new BundleApplicationContext(app, apk);
Application bundleApplication = Instrumentation.newApplication(
applicationClass, appContext);
sHostInstrumentation.callApplicationOnCreate(bundleApplication);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
// Lazy init content providers
if (mLazyInitProviders != null) {
try {
Method m = sActivityThread.getClass().getDeclaredMethod(
"installContentProviders", Context.class, List.class);
m.setAccessible(true);
m.invoke(sActivityThread, app, mLazyInitProviders);
} catch (Exception e) {
throw new RuntimeException("Failed to lazy init content providers: " + mLazyInitProviders);
}
}
// Free temporary variables
sLoadedApks = null;
sProviders = null;
sActivityThread = null;
}
這里可以看到它通過ReflectAccelerator.mergeResources去合并資源,里面特意增加對Android7.0的WebView Assets處理。這些過程都在反射的協(xié)助下完成。
AssetManager newAssetManager;
if (Build.VERSION.SDK_INT < 24) {
newAssetManager = newAssetManager();
} else {
// On Android 7.0+, this should contains a WebView asset as base. #347
newAssetManager = app.getAssets();
}
addAssetPaths(newAssetManager, assetPaths);
繼續(xù)通過ReflectAccelerator.expandDexPathList合并Dex
public static boolean expandDexPathList(ClassLoader cl, String[] dexPaths, DexFile[] dexFiles) {
if (Build.VERSION.SDK_INT < 14) {
return V9_13.expandDexPathList(cl, dexPaths, dexFiles);
} else {
return V14_.expandDexPathList(cl, dexPaths, dexFiles);
}
}
ReflectAccelerator.expandNativeLibraryDirectories繼續(xù)加載JNI Native庫:
public static void expandNativeLibraryDirectories(ClassLoader classLoader, List<File> libPath) {
int v = Build.VERSION.SDK_INT;
if (v < 14) {
V9_13.expandNativeLibraryDirectories(classLoader, libPath);
} else if (v < 23) {
V14_22.expandNativeLibraryDirectories(classLoader, libPath);
} else {
V23_.expandNativeLibraryDirectories(classLoader, libPath);
}
}
往下就對每個插件的Application的OnCreate事件進行分發(fā)。
// Trigger all the bundle application `onCreate' event
for (final LoadedApk apk : apks) {
String bundleApplicationName = apk.applicationName;
if (bundleApplicationName == null) continue;
try {
final Class applicationClass = Class.forName(bundleApplicationName);
Bundle.postUI(new Runnable() {
@Override
public void run() {
try {
BundleApplicationContext appContext = new BundleApplicationContext(app, apk);
Application bundleApplication = Instrumentation.newApplication(
applicationClass, appContext);
sHostInstrumentation.callApplicationOnCreate(bundleApplication);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
這里還會加載Provider的內容。
// Lazy init content providers
if (mLazyInitProviders != null) {
try {
Method m = sActivityThread.getClass().getDeclaredMethod(
"installContentProviders", Context.class, List.class);
m.setAccessible(true);
m.invoke(sActivityThread, app, mLazyInitProviders);
} catch (Exception e) {
throw new RuntimeException("Failed to lazy init content providers: " + mLazyInitProviders);
}
}
Small.preSetUp 已有對providers的處理
// Get providers
try {
f = thread.getClass().getDeclaredField("mBoundApplication");
f.setAccessible(true);
Object/*AppBindData*/ data = f.get(thread);
f = data.getClass().getDeclaredField("providers");
f.setAccessible(true);
providers = (List<ProviderInfo>) f.get(data);
} catch (Exception e) {
throw new RuntimeException("Failed to get providers from thread: " + thread);
}
這里重新加載,是為了增添一些容錯處理。
@Override
public boolean onException(Object obj, Throwable e) {
if (sProviders != null && e.getClass().equals(ClassNotFoundException.class)) {
boolean errorOnInstallProvider = false;
StackTraceElement[] stacks = e.getStackTrace();
for (StackTraceElement st : stacks) {
if (st.getMethodName().equals("installProvider")) {
errorOnInstallProvider = true;
break;
}
}
if (errorOnInstallProvider) {
// We'll reinstall this content provider later, so just ignores it!!!
// FIXME: any better way to get the class name?
String msg = e.getMessage();
final String prefix = "Didn't find class \"";
if (msg.startsWith(prefix)) {
String providerClazz = msg.substring(prefix.length());
providerClazz = providerClazz.substring(0, providerClazz.indexOf("\""));
for (ProviderInfo info : sProviders) {
if (info.name.equals(providerClazz)) {
if (mLazyInitProviders == null) {
mLazyInitProviders = new ArrayList<ProviderInfo>();
}
mLazyInitProviders.add(info);
break;
}
}
}
return true;
}
}
return super.onException(obj, e);
}
至此,啟動流程的插件加載也說完了。整個啟動過程基本就到這里了。
下一節(jié)我會介紹繼續(xù)Small的更新流程的源碼。
敬請期待!!!