前言

插件化现在已经是Android工程师必备的技能之一,只是学会怎么使用是不行的,所以蹭有时间研究一下Small的源码。对于插件化主要解决的问题是四大组件的加载和资源的加载,读懂所有Small源码需要对插件化四大组件的Hook知识和资源加载要有了解,否则是无法看得懂里面的内容的。这篇文章只是对Small的阅读源码启动流程进行分析,详细的细节还是需要通过Debug在例子中去调试才能知道很多东西。在学习Small源码之前,我们可以通过编译Small的Sample,通过例子打断点调试方便查看运行流程。如果使用过Small的小伙伴可以忽略开头的过程。

Sample

下载Small源码,导入Sample项目,这里主要使用v1.2.0-beta5为例子,1.3.0开始会有个Bug

1、在Terminal中输入指令,编译公共库

gradlew buildLib -q

输出

Small building library 1of 4 - app (0x7f)
Small building library 2 of 4 - lib.analytics (0x76)[lib.analytics] remove resources dir...                         [  OK  ][lib.analytics] remove resources.arsc...                        [  OK  ][lib.analytics] remove R.java...                                [  OK  ][lib.analytics] add flags: 1...                                 [  OK  ]-> armeabi/libnet_wequick_example_lib_analytics.so (129648 bytes = 126.6 KB)
Small building library 3 of 4 - lib.style (0x79)[lib.style] split library res files...                          [  OK  ][lib.style] slice asset package and reset package id...         [  OK  ][lib.style] split library R.java files...                       [  OK  ][lib.style] split R.class...                                    [  OK  ]-> armeabi/libcom_example_mysmall_lib_style.so (5603 bytes = 5.5 KB)
Small building library 4 of 4 - lib.utils (0x73)[lib.utils] split library res files...                          [  OK  ][lib.utils] slice asset package and reset package id...         [  OK  ][lib.utils] split library R.java files...                       [  OK  ][lib.utils] add flags: 10000...                                 [  OK  ][lib.utils] split R.class...                                    [  OK  ]-> armeabi/libnet_wequick_example_small_lib_utils.so (7004 bytes = 6.8 KB)

2、在Terminal中输入指令,编译应用插件

gradlew buildBundle -q

输出

Small building bundle 1 of 6 - app.detail (0x67)[app.detail] split library res files...                         [  OK  ][app.detail] slice asset package and reset package id...        [  OK  ][app.detail] split library R.java files...                      [  OK  ][app.detail] split R.class...                                   [  OK  ]-> armeabi/libnet_wequick_example_small_app_detail.so (7616 bytes = 7.4 KB)
Small building bundle 2 of 6 - app.home (0x70)[app.home] split library res files...                           [  OK  ][app.home] slice asset package and reset package id...          [  OK  ][app.home] split library R.java files...                        [  OK  ][app.home] split R.class...                                     [  OK  ]-> armeabi/libnet_wequick_example_small_app_home.so (11582 bytes = 11.3 KB)
Small building bundle 3 of 6 - app.main (0x77)[app.main] split library res files...                           [  OK  ][app.main] slice asset package and reset package id...          [  OK  ][app.main] split library R.java files...                        [  OK  ][app.main] add flags: 10000...                                  [  OK  ][app.main] split R.class...                                     [  OK  ]-> armeabi/libnet_wequick_example_small_app_main.so (12013 bytes = 11.7 KB)
Small building bundle 4 of 6 - app.mine (0x16)[app.mine] split library res files...                           [  OK  ][app.mine] slice asset package and reset package id...          [  OK  ][app.mine] split library R.java files...                        [  OK  ][app.mine] add flags: 11111110...                               [  OK  ][app.mine] split R.class...                                     [  OK  ]-> armeabi/libnet_wequick_example_small_app_mine.so (48756 bytes = 47.6 KB)
Small building bundle 5 of 6 - app.ok-if-stub (0x6a)[app.ok-if-stub] split library res files...                     [  OK  ][app.ok-if-stub] slice asset package and reset package id...    [  OK  ][app.ok-if-stub] split library R.java files...                  [  OK  ][app.ok-if-stub] split R.class...                               [  OK  ]-> armeabi/libnet_wequick_example_small_appok_if_stub.so (20189 bytes = 19.7 KB)

3、选择app运行项目

基本使用

通过查看Sample源码了解Small的基本使用

1、在Application中注册

public Application() {Small.preSetUp(this);
}@Override
public void onCreate() {super.onCreate();// OptionalSmall.setBaseUri("http://code.wequick.net/small-sample/");Small.setWebViewClient(new MyWebViewClient());Small.setLoadFromAssets(BuildConfig.LOAD_FROM_ASSETS);
}

2、在LaunchActivity中通过Small的方法跳转

public class LaunchActivity extends Activity {private static final long MIN_INTRO_DISPLAY_TIME = 1000000000; // mμs -> 1.0s@Overrideprotected void onCreate(Bundle savedInstanceState) {requestWindowFeature(Window.FEATURE_NO_TITLE);super.onCreate(savedInstanceState);}@Overrideprotected void onStart() {super.onStart();SharedPreferences sp = LaunchActivity.this.getSharedPreferences("profile", 0);final SharedPreferences.Editor se = sp.edit();final long tStart = System.nanoTime();se.putLong("setUpStart", tStart);Small.setUp(LaunchActivity.this, new net.wequick.small.Small.OnCompleteListener() {@Overridepublic void onComplete() {long tEnd = System.nanoTime();se.putLong("setUpFinish", tEnd).apply();long offset = tEnd - tStart;if (offset < MIN_INTRO_DISPLAY_TIME) {// 这个延迟仅为了让 "Small Logo" 显示足够的时间, 实际应用中不需要getWindow().getDecorView().postDelayed(new Runnable() {@Overridepublic void run() {Small.openUri("main", LaunchActivity.this);finish();}}, (MIN_INTRO_DISPLAY_TIME - offset) / 1000000);} else {Small.openUri("main", LaunchActivity.this);finish();}}});}
}

3、启动过程

整个启动过程使用了两步过程

  • Small的预加载:Small.preSetUp()
  • Small的加载:Small.setUp()

BundleLauncher

在启动的过程中,什么最重要?那就是插件启动器最重要了。在Small的源码中涉及到三个比较重要的插件启动器

  • ActivityLauncher:主要是负责宿主App的加载过程
  • ApkBundleLauncher:主要是负责插件App的加载过程
  • WebBundleLauncher:主要是负责加载网络插件的过程

它们的都继承自BundleLauncher,启动器抽象了每一步启动的执行流程,它们的执行顺序如下图

启动流程

一、preSetUp

preSetUp的流程比较简单,主要是初始化BundleLauncher存放在List中,然后遍历BundleLauncher的onCreate()

public static void preSetUp(Application context) {if (sContext != null) {return;}sContext = context;// Register default bundle launchersregisterLauncher(new ActivityLauncher());registerLauncher(new ApkBundleLauncher());registerLauncher(new WebBundleLauncher());Bundle.onCreateLaunchers(context);
}protected static void onCreateLaunchers(Application app) {if (sBundleLaunchers == null) return;// 由于只有ApkBundleLauncher复写了onCreate(),所以只走下面的逻辑// 1、ActivityBundleLauncher.onCreate():未实现// 2、ApkBundleLauncher.onCreate():占坑// 3、WebBundleLauncher.onCreate():未实现for (BundleLauncher launcher : sBundleLaunchers) {launcher.onCreate(app);}
}

二、setUp

setUp主要是判断是否有listener参数,然后决定采取进行同步或者异步来执行加载Bundle

public static void setUp(Context context, OnCompleteListener listener) {if (sContext == null) {// Tips for CODE-BREAKINGthrow new UnsupportedOperationException("Please call `Small.preSetUp' in your application first");}if (sHasSetUp) {if (listener != null) {listener.onComplete();}return;}Bundle.loadLaunchableBundles(listener);sHasSetUp = true;
}protected static void loadLaunchableBundles(Small.OnCompleteListener listener) {Contextcontext = Small.getContext();boolean synchronous = (listener == null);if (synchronous) {loadBundles(context);return;}// Asynchronousif (sThread == null) {sThread = new LoadBundleThread(context);sHandler = new LoadBundleHandler(listener);sThread.start();}
}

异步执行其实就是执行完成后通过Handler发送消息通知完成加载,然后释放无相关的变量

private static class LoadBundleThread extends Thread {Context mContext;public LoadBundleThread(Context context) {mContext = context;}@Overridepublic void run() {loadBundles(mContext);sHandler.obtainMessage(MSG_COMPLETE).sendToTarget();}
}private static class LoadBundleHandler extends Handler {private Small.OnCompleteListener mListener;public LoadBundleHandler(Small.OnCompleteListener listener) {mListener = listener;}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_COMPLETE:if (mListener != null) {mListener.onComplete();}mListener = null;sThread = null;sHandler = null;break;}}
}

不管是异步还是同步,最后它们都是要执行loadBundles来加载的

private static void loadBundles(Context context) {JSONObject manifestData;try {//获取 /data/data/<application package>/files 目录下的 bundle.jsonFile patchManifestFile = getPatchManifestFile();//获取 SharedPreferences 存储的bundle.json文件String manifestJson = getCacheManifest();//情况一:如果插件信息有缓存,那么加载缓存的内容,并保存到patchManifestFile,清除缓存if (manifestJson != null) {// 加载SharedPreferences中的缓存的文件并保存到patchManifestFile文件中if (!patchManifestFile.exists()) patchManifestFile.createNewFile();PrintWriter pw = new PrintWriter(new FileOutputStream(patchManifestFile));pw.print(manifestJson);pw.flush();pw.close();// 清除SharedPreferences中的缓存setCacheManifest(null);} else if (patchManifestFile.exists()) {//情况二:缓存过后以后就从patchManifestFile读取缓存的数据BufferedReader br = new BufferedReader(new FileReader(patchManifestFile));StringBuilder sb = new StringBuilder();String line;while ((line = br.readLine()) != null) {sb.append(line);}br.close();manifestJson = sb.toString();} else {//情况三:第一次加载都是从assets目录下加载的InputStream builtinManifestStream = context.getAssets().open(BUNDLE_MANIFEST_NAME);int builtinSize = builtinManifestStream.available();byte[] buffer = new byte[builtinSize];builtinManifestStream.read(buffer);builtinManifestStream.close();manifestJson = new String(buffer, 0, builtinSize);}// Parse manifest filemanifestData = new JSONObject(manifestJson);} catch (Exception e) {e.printStackTrace();return;}// 解析数据,Manifest存放有所有插件的Bundle信息Manifest manifest = parseManifest(manifestData);if (manifest == null) return;//遍历所有启动器的setUp方法setupLaunchers(context);// 加载所有插件loadBundles(manifest.bundles);
}protected static void setupLaunchers(Context context) {if(sBundleLaunchers == null) return;for (BundleLauncher launcher : sBundleLaunchers) {// 1、ActivityBundleLauncher.setUp():将宿主的Activity保存到List中// 2、ApkBundleLauncher.setUp():Hook 出 pending intent// 3、WebBundleLauncher.setUp():创建出WebViewlauncher.setUp(context);}
}

这里会有三种方式进行读取bundle.json文件的方式,分别是

  1. 优先读取缓存中的bundle.json
  2. 从patch中读取bundle.json
  3. 从assets中读取bundle.json

private static void loadBundles(List<Bundle> bundles) {sPreloadBundles = bundles;// Prepare bundle// 遍历所有插件(Bundle)按顺序执行注册过的BundleLauncher的prepareForLaunch方法,即preloadBundle和loadBundle// 如果某一插件符合下面的判断要求,则会进行相对应的赋值并返回,赋值过后的启动器将会影响到后面的加载过程// 1、ActivityBundleLauncher:判断当前的插件是否为宿主本身,如果是则赋值为ActivityBundleLauncher// 2、ApkBundleLauncher:判断当前的插件是否为app或者lib,如果是则赋值为ApkBundleLauncher// 3、WebBundleLauncher:不做处理,一般不会到这一步// 由于ApkBundleLauncher没有复写prepareForLaunch方法,所以只能到父类的soBundleLauncher中实现for (Bundle bundle : bundles) {bundle.prepareForLaunch();}// Handle I/Oif (sIOActions != null) {ExecutorService executor = Executors.newFixedThreadPool(sIOActions.size());for (Runnable action : sIOActions) {executor.execute(action);}executor.shutdown();try {if (!executor.awaitTermination(LOADING_TIMEOUT_MINUTES, TimeUnit.MINUTES)) {throw new RuntimeException("Failed to load bundles! (TIMEOUT > "+ LOADING_TIMEOUT_MINUTES + "minutes)");}} catch (InterruptedException e) {e.printStackTrace();}sIOActions = null;}// 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();}}// Notify `postSetUp' to all launchersfor (BundleLauncher launcher : sBundleLaunchers) {// 遍历所有BundleLauncher的postSetUp// 1、ActivityBundleLauncher.onCreate():未实现// 2、ApkBundleLauncher.onCreate():开始加载并合并插件// 3、WebBundleLauncher.onCreate():未实现launcher.postSetUp();}// Wait for the things to be done on UI thread after `postSetUp`,// like creating a bundle application.while (sRunningUIActionCount != 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}// Free all unused temporary variables// 释放所有插件的资源for (Bundle bundle : bundles) {if (bundle.parser != null) {bundle.parser.close();bundle.parser = null;}bundle.mBuiltinFile = null;bundle.mExtractPath = null;}
}

到这里setUp就结束了,其余的就是每个启动器的执行,下面用图片总结下Small的setUp过程

ActivityLauncher

主要作用:解析宿主的Activity存储起来

public class ActivityLauncher extends BundleLauncher {private static HashSet<String> sActivityClasses;protected static boolean containsActivity(String name) {return sActivityClasses != null && sActivityClasses.contains(name);}/*** 在宿主App里面注册的 Activity 添加到 sActivityClasses 中去(包含占坑的Activity)** @param context*/@Overridepublic void setUp(Context context) {super.setUp(context);// Read the registered classes in host's manifest fileFile sourceFile = new File(context.getApplicationInfo().sourceDir);BundleParser parser = BundleParser.parsePackage(sourceFile, context.getPackageName());parser.collectActivities();ActivityInfo[] as = parser.getPackageInfo().activities;if (as != null) {sActivityClasses = new HashSet<String>();for (ActivityInfo ai : as) {sActivityClasses.add(ai.name);}}}/*** 如果是存在宿主或者插件则返回true,保证可以执行loadBundle* 如果是不存在则返回false,就不会去执行loadBundle* <p>* ActivityLauncher 是用来启动宿主 Activity 的** @param bundle* @return*/@Overridepublic boolean preloadBundle(Bundle bundle) {if (sActivityClasses == null) return false;String pkg = bundle.getPackageName();return (pkg == null || pkg.equals("main"));}/*** openUri执行第一时机** @param bundle*/@Overridepublic void prelaunchBundle(Bundle bundle) {super.prelaunchBundle(bundle);//Bundle存储需要加载的Intent信息//TODO: 后面会获取Intent进行跳转Intent intent = new Intent();bundle.setIntent(intent);// Intent extras - class// 获取bundle的入口Activity类名String activityName = bundle.getActivityName();// 判断是否为插件if (!sActivityClasses.contains(activityName)) {if (activityName.endsWith("Activity")) {throw new ActivityNotFoundException("Unable to find explicit activity class " +"{ " + activityName + " }");}String tempActivityName = activityName + "Activity";if (!sActivityClasses.contains(tempActivityName)) {throw new ActivityNotFoundException("Unable to find explicit activity class " +"{ " + activityName + "(Activity) }");}activityName = tempActivityName;}intent.setComponent(new ComponentName(Small.getContext(), activityName));// Intent extras - params// Query参数:存储Uri跳转使用的String query = bundle.getQuery();if (query != null) {intent.putExtra(Small.KEY_QUERY, '?' + query);}}/*** openUri执行第二时机** @param bundle* @param context*/@Overridepublic void launchBundle(Bundle bundle, Context context) {prelaunchBundle(bundle);super.launchBundle(bundle, context);}
}

ApkBundleLauncher

启动器的执行流程我也在前面贴出来了,具体也按照这个流程

一、onCreate

主要工作:反射四大组件的变量,等待占坑

@Override
public void onCreate(Application app) {super.onCreate(app);Object/*ActivityThread*/ thread;List<ProviderInfo> providers;Instrumentation base;ApkBundleLauncher.InstrumentationWrapper wrapper;Field f;// Get activity thread// 反射获取ActivityThreadthread = ReflectAccelerator.getActivityThread(app);// Replace instrumentation// 替换 mInstrumentation,作用:获取sHostInstrumentation,获取当前启动的Intent信息,并对Intent进行操作try {f = thread.getClass().getDeclaredField("mInstrumentation");f.setAccessible(true);base = (Instrumentation) f.get(thread);wrapper = new ApkBundleLauncher.InstrumentationWrapper(base);f.set(thread, wrapper);} catch (Exception e) {throw new RuntimeException("Failed to replace instrumentation for thread: " + thread);}// Inject message handler// 替换 mH和mCallback ,作用:拦截Activity的启动等事件ensureInjectMessageHandler(thread);// Get providers// 获取该APP的 ProviderInfo 列表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);}// 将这些变量保存起来sActivityThread = thread;sProviders = providers;sHostInstrumentation = base;sBundleInstrumentation = wrapper;
}

二、setUp

主要作用:动态代理创建pendingIntent,目前没看出什么作用

@Override
public void setUp(Context context) {super.setUp(context);Field f;// AOP for pending intent// 通过TaskStackBuilder创建的pendingIntenttry {f = TaskStackBuilder.class.getDeclaredField("IMPL");f.setAccessible(true);final Object impl = f.get(TaskStackBuilder.class);InvocationHandler aop = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Intent[] intents = (Intent[]) args[1];for (Intent intent : intents) {sBundleInstrumentation.wrapIntent(intent);intent.setAction(Intent.ACTION_MAIN);intent.addCategory(Intent.CATEGORY_LAUNCHER);}return method.invoke(impl, args);}};Object newImpl = Proxy.newProxyInstance(context.getClassLoader(), impl.getClass().getInterfaces(), aop);f.set(TaskStackBuilder.class, newImpl);} catch (Exception ignored) {Log.e(TAG, "Failed to hook TaskStackBuilder. \n" +"Please manually call `Small.wrapIntent` to ensure the notification intent can be opened. \n" +"See https://github.com/wequick/Small/issues/547 for details.");}
}

三、preloadBundle

由于ApkBundleLauncher没有重写preloadBundle方法,所以从父类SoBundleLauncher中找

主要作用:插件类型的校验和版本校验,最后保存所需变量;当前方法是ApkBundleLauncher分配启动器的必经方法,如果返回true表示当前启动器分配成功

@Override
public boolean preloadBundle(Bundle bundle) {String packageName = bundle.getPackageName();if (packageName == null) return false;// Check if supporting// 获取支持的插件类型,ApkBundleLauncher 支持 `app` 和 `lib`,WebBundleLauncher 支持`web`String[] types = getSupportingTypes();if (types == null) return false;//按支持的type与package名对比,快速判断此BundleLauncher能否解析此插件boolean supporting = false;String bundleType = bundle.getType();if (bundleType != null) {// Consider user-defined type in `bundle.json'// 如果在 `bundle.json' 中设置了type,就去根据type来找到合适的BundleLauncherfor (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// 如果没有指定type,就尝试根据包名来判断,看里面是否包含app、lib或者web等//  - com.example.[type].any//  - com.example.[type]any// TODO 这里可以打断点看看是否支持加载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;}}}//如果该BundleLauncher不支持该Bundle类型,直接返回if (!supporting) return false;// Initialize the extract path// 获取提取路径,ApkBundleLauncher和AssetBundleLauncher分别有不同的定义File extractPath = getExtractPath(bundle);if (extractPath != null) {if (!extractPath.exists()) {extractPath.mkdirs();}bundle.setExtractPath(extractPath);}// Select the bundle entry-point, `built-in' or `patch'// 获取插件文件/data/data/<包名>/app_small_base/<包名>.apk文件File plugin = bundle.getBuiltinFile();// 解析AndroidManifest.xml文件,得到插件的版本,主题风格,Activity,收集intent-filter等BundleParser parser = BundleParser.parsePackage(plugin, packageName);// 获取补丁文件/data/data/<包名>/app_small_patch/<包名>.apk文件File patch = bundle.getPatchFile();// 解析AndroidManifest.xml文件,得到补丁的版本,主题风格,Activity,收集intent-filter等BundleParser patchParser = BundleParser.parsePackage(patch, packageName);// 如果插件为空if (parser == null) {// 如果补丁为空if (patchParser == null) {// 直接返回return false;} else {// 如果插件为空,补丁不为空,则加补丁parser = patchParser; // use patchplugin = 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 patchplugin = 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)) {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;
}

四、loadBundle

主要作用:加载插件或补丁的Manifests.xml的所有信息,并存储到LoadApk,LoadApk则是保存着插件加载所需的实体

@Override
public void loadBundle(Bundle bundle) {String packageName = bundle.getPackageName();// 解析插件或补丁的Manifests.xml,这一步的是否插件或补丁来源于上一步的判断BundleParser parser = bundle.getParser();// 收集其Activityparser.collectActivities();PackageInfo pluginInfo = parser.getPackageInfo();// Load the bundleString 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 dexfinal LoadedApk fApk = apk;Bundle.postIO(new Runnable() {@Overridepublic 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 ABIString libDir = parser.getLibraryDirectory();if (libDir != null) {apk.libraryPath = new File(apk.packagePath, libDir);}sLoadedApks.put(packageName, apk);}if (pluginInfo.activities == null) {return;}// Record activities for intent redirectionif (sLoadedActivities == null)sLoadedActivities = new ConcurrentHashMap<String, ActivityInfo>();for (ActivityInfo ai : pluginInfo.activities) {//记录插件的所有Activity信息sLoadedActivities.put(ai.name, ai);}// Record intent-filters for implicit action// 收集 intent-filters for implicit actionConcurrentHashMap<String, List<IntentFilter>> filters = parser.getIntentFilters();if (filters != null) {if (sLoadedIntentFilters == null) {sLoadedIntentFilters = new ConcurrentHashMap<String, List<IntentFilter>>();}sLoadedIntentFilters.putAll(filters);}// Set entrance activity// 设置该插件的manifest中定义的入口Activitybundle.setEntrance(parser.getDefaultActivityName());
}

五、postSetUp

主要作用:把所有Bundle中的Dex、Resource和NativeLib通过反射Merge到宿主中

@Override
public void postSetUp() {super.postSetUp();if (sLoadedApks == null) {Log.e(TAG, "Could not find any APK bundles!");return;}// 取出需要加载的插件 LoadedApkCollection<LoadedApk> apks = sLoadedApks.values();// Merge all the resources in bundles and replace the host onefinal Application app = Small.getContext();String[] paths = new String[apks.size() + 1];// 添加宿主app的资源路径paths[0] = app.getPackageResourcePath(); // add host asset pathint i = 1;// 添加各个插件的资源路径for (LoadedApk apk : apks) {if (apk.nonResources) continue; // ignores the empty entry to fix #62paths[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 loaderClassLoader 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 recreatingif (apk.optDexFile.exists()) apk.optDexFile.delete();Small.setBundleUpgraded(apk.packageName, false);}i++;}ReflectAccelerator.expandDexPathList(cl, dexPaths, dexFiles);// 为宿主class loader扩展它的native library路径,这个路径包含了插件的native library路径// 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);}// 调用所有插件Application的`onCreate' 方法// Trigger all the bundle application `onCreate' eventfor (final LoadedApk apk : apks) {String bundleApplicationName = apk.applicationName;if (bundleApplicationName == null) continue;try {final Class applicationClass = Class.forName(bundleApplicationName);Bundle.postUI(new Runnable() {@Overridepublic 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 providersif (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 variablessLoadedApks = null;sProviders = null;
}

WebBundleLauncher

主要作用:负责加载Small中的WebActivity

public class WebBundleLauncher extends AssetBundleLauncher {private static final String FD_BASE = "small_web";private static final String FILE_INDEX = "index.html";@Overrideprotected String[] getSupportingTypes() {return new String[] {"web"};}@Overrideprotected String getBasePathName() {return FD_BASE;}@Overrideprotected String getIndexFileName() {return FILE_INDEX;}@Overrideprotected Class<? extends Activity> getActivityClass() {return WebActivity.class;}@Overridepublic void setUp(Context context) {super.setUp(context);if (Build.VERSION.SDK_INT < 24) return;Bundle.postUI(new Runnable() {@Overridepublic void run() {// In android 7.0+, on firstly create WebView, it will replace the application// assets with the one who has join the WebView asset path.// If this happens after our assets replacement,// what we have done would be come to naught!// So, we need to push it enOOOgh ahead! (#347)new android.webkit.WebView(Small.getContext());}});}
}

结语

简单来说,Small启动流程如下图,下一步就是等跳转的时候,将我们的预先占好的坑交给它们去骗过系统的检测

Android进阶——Small源码分析之启动流程详解相关推荐

  1. Nginx源码分析:启动流程

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> nginx简介 Nginx的作为服务端软件,表现的主要特点是更快.高扩展.高可靠性.低内存消 ...

  2. Android进阶——ExoPlayer源码分析之宽带预测策略的算法详解

    前言 由于国内基础设施非常优秀,在平时的开发中,很少会关注网络情况,很容易忽略弱网情况下的网络状况,如果项目属于国外App,则需要考虑到当前的基础设施和网络情况,特别是播放视频的时候,需要通过动态调整 ...

  3. Kubelet源码分析(一):启动流程分析

    源码版本 kubernetes version: v1.3.0 简介 在Kubernetes急群众,在每个Node节点上都会启动一个kubelet服务进程.该进程用于处理Master节点下发到本节点的 ...

  4. 以太坊Go-ethereum源码分析之启动流程

    以太坊源码编译需要gov1.7以上,及C编译器,执行make geth 即可编译项目,编译后可执行的geth文件. Makefile文件: geth:build/env.sh go run build ...

  5. Flask1.1.4 Werkzeug1.0.1 源码分析:启动流程

    基于QuickStart中的一个demo来分析 from flask import Flaskapp = Flask(__name__)@app.route("/") def he ...

  6. UVM源码分析之factory机制详解

    前言 作者在学习了一段时间的UVM factory源码之后写下此文,旨在记录自己的学习成果,毕竟好记性不如烂笔头嘛,当然如果能帮助到对这部分有疑惑的同仁就更好了.作者是在笔记本电脑上的windows环 ...

  7. spring源码分析之spring-jms模块详解

    0 概述 spring提供了一个jms集成框架,这个框架如spring 集成jdbc api一样,简化了jms api的使用. jms可以简单的分成两个功能区,消息的生产和消息的消费.JmsTempl ...

  8. spring源码分析之spring-web http详解

    spring-web是spring webmvc的基础,它的功能如下: 1. 封装http协议中client端/server端的request请求和response响应及格式的转换,如json,rss ...

  9. RocketMQ源码:NameSrv启动全过程详解

    文章目录 1. 概述 2. NamesrvController 简介 3. NamesrvController 创建过程 4. NamesrvController 初始化 5. NamesrvCont ...

最新文章

  1. vivado的ip核使用-pll
  2. Realm发布Realm .NET,扩展支持.NET技术栈
  3. 准确率99.9%!如何用深度学习最快找出放倒的那张X光胸片(代码+数据)
  4. android 使用浏览器打开指定页面
  5. 【Matplotlib】【Python】如何使用matplotlib绘制各种图形
  6. 【kafka】消费组 死掉 kafka Marking the coordinator dead for group
  7. 素数环(nyoj488)
  8. matlab在运筹学,MATLAB在运筹学(单纯形法)教学中的应用
  9. Java基础篇:去探索String类
  10. Liunx 内核漏洞
  11. 程序MD5校验的作用
  12. 数值分析(1)-绪论:误差
  13. 常见浏览器兼容性问题与解决方案(CSS)
  14. 概率图模型(03): 模板模型(动态贝叶斯, 隐马尔可夫和Plate模型)
  15. 数学画图软件_关于数学建模(或科研绘图)的画图学习建议
  16. RabbitMQ之业务场景(四):动态创建,删除队列工具类,拿来即用
  17. 基尔霍夫电流定律KCL,基尔霍夫电压定律KVL
  18. APP 跳转微信小程序和回调
  19. JavaScript:Promise进阶知识
  20. c语言科学记数法正确表示方法,输入: 用科学记数法表示的数据。即为符合C语言表示的科学记数法表示。 输出: 该...

热门文章

  1. 对接保利威直播api-php
  2. 角平分线上的点到角两边的距离相等易混淆点
  3. TIOBE 9 月编程语言排行榜:Python 直逼第一,仅差 0.16%!
  4. Arduino+A4988+步进电机
  5. 项目启动出现Table ‘XXX.qrtz_LOCKS‘ doesn‘t exist处理办法
  6. 隐写术浅谈(二):LSB隐写与IDAT隐写
  7. 【RAP】CAS PIA 快速浏览入口地址
  8. 多尺度特征表示在深度学习中的重要意义
  9. linux系统内存used占用过高问题排查
  10. 源发行版本 17 需要目标发行版 17