这一节我们要继续接着上一节的内容,来讲讲插件的加载过程。
二. 插件加载过程:从Bundle 步入 BundleLauncher
上一节我们读到loadBundle中,里面涉及一个方法prepareForLaunch,我没有继续往下详解,搬来这里继续给大家做个介绍。
(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这个变量已经很熟悉,在前面已经多次出现了,我们来回忆一下。
里面只有三个类ApkBundleLauncher、ActivityLauncher、WebBundleLauncher,前面已经作了部分解释,从上图可知。三者的祖先都是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;
}
这里会继续调用preloadBundle的方法来判断,然后我发现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被填充的,它里面有宿主已经注册好的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的名字规则如下:如果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属性为“lib”或者“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文件以及lib库。
@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来实现Intent重定向,使用sLoadedIntentFilters记录intent-filters来实现隐式调用。
(3)重新回到Bundle.loadBundles,分发到各个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处理。这些过程都在反射的协助下完成。
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);
继续通过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继续加载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事件进行分发。
// 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);
}
至此,启动流程的插件加载也说完了。整个启动过程基本就到这里了。
下一节我会介绍继续Small的更新流程的源码。
敬请期待!!!