17年春節(jié)前這段時間一直抱著Small(aarVersion = '1.1.0-alpha1'),打算從零開始一個新的項目筒占。心想Small源代碼也該好好看看,究竟從哪里開始才好呢蜘犁? 還是從官方例子出發(fā)翰苫,從啟動開始深入。
首先從Small在Github 上的Sample項目開始了解Small的基本狀況这橙,如果要深入了解Small 的具體實現(xiàn)奏窑,就必須深入到其中的DevSample項目,因為這才是Small的源碼所在屈扎。
通過UML工具(CodeIris 插件)可以一窺其內(nèi)部埃唯,得到相關(guān)的類圖,這里是以Smalll類為源頭鹰晨,省略一部分類墨叛,大致了解到核心所在。
從實際應(yīng)用Small過程中模蜡,想必會關(guān)注到bundle.json這個文件漠趁,可以看得出BundleLaucher 是個關(guān)鍵類。因此將其抽取出來忍疾,分析關(guān)聯(lián)的類結(jié)構(gòu)(這里省略覆蓋方法)闯传。
BundleLauncher以及 SoBundleLauncher 都是抽象類,具體的應(yīng)用得細看ApkBundleLauncher膝昆、ActivityLauncher這兩個類丸边,我會在后面具體分析到。
到此荚孵,我們已經(jīng)大致了解Small的源碼目錄結(jié)構(gòu)妹窖,我們要退一步從Small Sample項目開始,也就是用實際應(yīng)用的項目開始溯源收叶。
一. Small 啟動流程
(一)Small 預(yù)處理
首先我們看一下宿主app的Application類:
public class Application extends android.app.Application {
public Application() {
// This should be the very first of the application lifecycle.
// It's also ahead of the installing of content providers by what we can avoid
// the ClassNotFound exception on if the provider is unimplemented in the host.
Small.preSetUp(this);
}
@Override
public void onCreate() {
super.onCreate();
// Optional
Small.setBaseUri("http://m.wequick.net/demo/");//設(shè)定基本的跳轉(zhuǎn)地址
Small.setWebViewClient(new MyWebViewClient());//設(shè)置網(wǎng)頁的基本回調(diào)
Small.setLoadFromAssets(BuildConfig.LOAD_FROM_ASSETS);
}
private static final class MyWebViewClient extends WebViewClient {
...
}
}
Small.java內(nèi)部基本都是靜態(tài)方法骄呼,是Small框架的程序入口。在Application的構(gòu)造方法中調(diào)用Small.preSetUp(this),意思就是這應(yīng)該在application的生命周期中最早調(diào)用的蜓萄,也是安裝Content Providers 之前隅茎,可以避免在宿主中發(fā)生ClassNotFound 異常.
具體來看看preSetUp里面吧。
public static void preSetUp(Application context) {
if (sContext != null) {
return;
}
sContext = context;
// Register default bundle launchers
registerLauncher(new ActivityLauncher());
registerLauncher(new ApkBundleLauncher());
registerLauncher(new WebBundleLauncher());
Bundle.onCreateLaunchers(context);
}
通過Bundle.registerLauncher方法添加三個BundleLauncher到Bundle.sBundleLaunchers列表嫉沽,在通過 Bundle.onCreateLaunchers啟動這三者的onCreate方法辟犀,但其實只有ApkBundleLauncher覆蓋了OnCreate 方法。
ApkBundleLauncher是bundle加載的管理類绸硕,我們看看它的OnCreate是怎么定義的堂竟。
@Override
public void onCreate(Application app) {
super.onCreate(app);
Object/*ActivityThread*/ thread;
List<ProviderInfo> providers;
Instrumentation base;
ApkBundleLauncher.InstrumentationWrapper wrapper;
Field f;
// 通過getActivityThread反射獲取ActivityThread的對象
thread = ReflectAccelerator.getActivityThread(app);
// 將自定義的InstrumentationWrapper替換掉原來的mInstumentation
// Small通過占坑的方式管理Activity的.重點就在這里
try {
f = thread.getClass().getDeclaredField("mInstrumentation");//取得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);
}
// 繼續(xù)替換Message Handler,用于恢復(fù)Activity Info 到真實的Activity
try {
f = thread.getClass().getDeclaredField("mH");
f.setAccessible(true);
Handler ah = (Handler) f.get(thread);
f = Handler.class.getDeclaredField("mCallback");
f.setAccessible(true);
f.set(ah, new ApkBundleLauncher.ActivityThreadHandlerCallback());
} catch (Exception e) {
throw new RuntimeException("Failed to replace message handler for thread: " + thread);
}
// 獲取App的provider列表
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;
}
ApkBundleLauncher.InstrumentationWrapper 這個是關(guān)鍵類出嘹,Android activities受Instrumentation監(jiān)控。每一個Activity由Activity的startActivityForResult
方法啟動咬崔,通過instrumentation的execStartActivity方法激活生命周期税稼;
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity( // Override entry 1
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
...
}
}
在ActivityThread的performLaunchActivity方法中通過instrumentation的newActivity方法實例化。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity( // Override entry 2
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
...
}
Small 想要做到動態(tài)注冊Activity垮斯,首先在宿主manifest中注冊一個命名特殊的占坑activity來欺騙startActivityForResult以獲得生命周期郎仆,再欺騙performLaunchActivity來獲得插件activity實例。又為了處理之間的信息傳遞兜蠕,因此有了后面的ActivityThreadHandlerCallback丸升。
(二)LaunchActivity 啟動初始化
Small.setUp這個方法需要放在OnStart()中執(zhí)行,避免一些問題的產(chǎn)生牺氨,初始化完成之后,回調(diào)onComplete方法墩剖。完成加載之后猴凹,之后就交給插件的業(yè)務(wù)邏輯了。
@Override
protected 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() {
@Override
public void onComplete() {
long tEnd = System.nanoTime();
se.putLong("setUpFinish", tEnd).apply();
long offset = tEnd - tStart;
if (offset < MIN_INTRO_DISPLAY_TIME) {
// 這個延遲僅為了讓 "Small Logo" 顯示足夠的時間, 實際應(yīng)用中不需要
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
Small.openUri("main", LaunchActivity.this);
finish();
}
}, (MIN_INTRO_DISPLAY_TIME - offset) / 1000000);
} else {
Small.openUri("main", LaunchActivity.this);
finish();
}
}
});
}
現(xiàn)在我們跟蹤進setUp方法岭皂,看看里面究竟做了什么郊霎。
public static void setUp(Context context, OnCompleteListener listener) {
if (sContext == null) {
// Tips for CODE-BREAKING
throw new UnsupportedOperationException(
"Please call `Small.preSetUp' in your application first");
}
if (sHasSetUp) {
if (listener != null) {
listener.onComplete();
}
return;
}
Bundle.loadLaunchableBundles(listener);
sHasSetUp = true;
}
可以看到Small確認還沒初始化之后,就遞給了Bundle類爷绘,在其內(nèi)部執(zhí)行靜態(tài)方法loadLaunchableBundles书劝,我們繼續(xù)往下看。
protected static void loadLaunchableBundles(Small.OnCompleteListener listener) {
Context context = Small.getContext();
boolean synchronous = (listener == null);
if (synchronous) {
loadBundles(context);
return;
}
// Asynchronous
if (sThread == null) {
sThread = new LoadBundleThread(context);
sHandler = new LoadBundleHandler(listener);
sThread.start();
}
}
這個方法很簡單土至,啟動一個LoadBundleThread線程购对,一個Handler處理完成后的事項。在LoadBundleThread里面我們肯定會看到bundle.json的處理陶因。
private static class LoadBundleThread extends Thread {
Context mContext;
public LoadBundleThread(Context context) {
mContext = context;
}
@Override
public void run() {
loadBundles(mContext);
sHandler.obtainMessage(MSG_COMPLETE).sendToTarget();
}
}
這里和之前的版本比較骡苞,會發(fā)現(xiàn)少了Bundle.setupLaunchers(Context)方法,仔細往下就會發(fā)現(xiàn)其實它被延后到loadBundles內(nèi)部了。
private static void loadBundles(Context context) {
JSONObject manifestData;
try {
File patchManifestFile = getPatchManifestFile();// app路徑/file/bundle.json
String manifestJson = getCacheManifest();//SharedPreferences的bundle.json字段
if (manifestJson != null) {
// Load from cache and save as patch
if (!patchManifestFile.exists()) patchManifestFile.createNewFile();
PrintWriter pw = new PrintWriter(new FileOutputStream(patchManifestFile));
pw.print(manifestJson);
pw.flush();
pw.close();
// Clear cache
setCacheManifest(null);
} else if (patchManifestFile.exists()) {
// Load from patch
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 {
// Load from built-in `assets/bundle.json'
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 file
manifestData = new JSONObject(manifestJson);
} catch (Exception e) {
e.printStackTrace();
return;
}
Manifest manifest = parseManifest(manifestData);
if (manifest == null) return;
setupLaunchers(context);
loadBundles(manifest.bundles);
}
由上可知解幽,處理bundle.json 有位置優(yōu)先級關(guān)系:manifestJson > patchManifestFile > assets/bundle.json 贴见,也就是SharedPreferences > File > Assets 。因此躲株,我們最初的配置在Assets里面的bundle.json 會在最后處理片部。
接著回到先前提及的setupLaunchers,回憶一下霜定,在preSetUp中我們看到添加了3個BundleLauncher档悠,這里就是對三者的setUp進行調(diào)用。
protected static void setupLaunchers(Context context) {
if (sBundleLaunchers == null) return;
for (BundleLauncher launcher : sBundleLaunchers) {
launcher.setUp(context);
}
}
-
ActivityLauncher的setUp方法
我們繼續(xù)跟蹤進去然爆,首先看看ActivityLauncher的setUp方法,它將注冊在宿主的activities添加到sActivityClasses里面站粟。
public class ActivityLauncher extends BundleLauncher {
...
@Override
public void setUp(Context context) {
super.setUp(context);
// Read the registered classes in host's manifest file
PackageInfo pi;
try {
pi = context.getPackageManager().getPackageInfo(
context.getPackageName(), PackageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException ignored) {
// Never reach
return;
}
ActivityInfo[] as = pi.activities;
if (as != null) {
sActivityClasses = new HashSet<String>();
for (ActivityInfo ai : as) {
sActivityClasses.add(ai.name);
}
}
}
...
}
- ApkBundleLauncher的setUp方法
這里與之前的ApkBundleLauncher的onCreate方法相呼應(yīng),通過動態(tài)代理的方式曾雕,將Intent參數(shù)重新包裝來完成上面所說的欺騙方式奴烙。
public class ApkBundleLauncher extends SoBundleLauncher {
...
@Override
public void setUp(Context context) {
super.setUp(context);
Field f;
// AOP for pending intent
try {
f = TaskStackBuilder.class.getDeclaredField("IMPL");
f.setAccessible(true);
final Object impl = f.get(TaskStackBuilder.class);
InvocationHandler aop = new InvocationHandler() {
@Override
public 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) {
ignored.printStackTrace();
}
}
...
}
我們進去wrapIntent看看,如果不是精確命中,intent.getComponent()為空剖张,因此首先處理交給宿主的沖突問題切诀,如果是系統(tǒng)或者宿主的Action,直接退出搔弄;
private void wrapIntent(Intent intent) {
ComponentName component = intent.getComponent();
String realClazz;
if (component == null) {
// Try to resolve the implicit action which has registered in host.
component = intent.resolveActivity(Small.getContext().getPackageManager());
if (component != null) {
// A system or host action, nothing to be done.
return;
}
// Try to resolve the implicit action which has registered in bundles.
realClazz = resolveActivity(intent);
if (realClazz == null) {
// Cannot resolved, nothing to be done.
return;
}
} else {
realClazz = component.getClassName();
if (realClazz.startsWith(STUB_ACTIVITY_PREFIX)) {
// Re-wrap to ensure the launch mode works.
realClazz = unwrapIntent(intent);
}
}
if (sLoadedActivities == null) return;
ActivityInfo ai = sLoadedActivities.get(realClazz);
if (ai == null) return;
// Carry the real(plugin) class for incoming `newActivity' method.
intent.addCategory(REDIRECT_FLAG + realClazz);
String stubClazz = dequeueStubActivity(ai, realClazz);
intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
}
否則嘗試處理插件內(nèi)的沖突(resolveActivity(intent)),初始失敗則退出幅虑。
private String resolveActivity(Intent intent) {
if (sLoadedIntentFilters == null) return null;
Iterator<Map.Entry<String, List<IntentFilter>>> it =
sLoadedIntentFilters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, List<IntentFilter>> entry = it.next();
List<IntentFilter> filters = entry.getValue();
for (IntentFilter filter : filters) {
if (filter.hasAction(Intent.ACTION_VIEW)) {
// TODO: match uri
}
if (filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
// custom action
if (filter.hasAction(intent.getAction())) {
// hit
return entry.getKey();
}
}
}
}
return null;
}
如果精確命中,先解開(unwrapIntent(intent);),后面重新選取一個未使用的activity坑與之使用顾犹。
private String[] mStubQueue;
/** Get an usable stub activity clazz from real activity */
private String dequeueStubActivity(ActivityInfo ai, String realActivityClazz) {
if (ai.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
// In standard mode, the stub activity is reusable.
// Cause the `windowIsTranslucent' attribute cannot be dynamically set,
// We should choose the STUB activity with translucent or not here.
Resources.Theme theme = Small.getContext().getResources().newTheme();
theme.applyStyle(ai.getThemeResource(), true);
TypedArray sa = theme.obtainStyledAttributes(
new int[] { android.R.attr.windowIsTranslucent });
boolean translucent = sa.getBoolean(0, false);
sa.recycle();
return translucent ? STUB_ACTIVITY_TRANSLUCENT : STUB_ACTIVITY_PREFIX;
}
int availableId = -1;
int stubId = -1;
int countForMode = STUB_ACTIVITIES_COUNT;
int countForAll = countForMode * 3; // 3=[singleTop, singleTask, singleInstance]
if (mStubQueue == null) {
// Lazy init
mStubQueue = new String[countForAll];
}
int offset = (ai.launchMode - 1) * countForMode;
for (int i = 0; i < countForMode; i++) {
String usedActivityClazz = mStubQueue[i + offset];
if (usedActivityClazz == null) {
if (availableId == -1) availableId = i;
} else if (usedActivityClazz.equals(realActivityClazz)) {
stubId = i;
}
}
if (stubId != -1) {
availableId = stubId;
} else if (availableId != -1) {
mStubQueue[availableId + offset] = realActivityClazz;
} else {
// TODO:
Log.e(TAG, "Launch mode " + ai.launchMode + " is full");
}
return STUB_ACTIVITY_PREFIX + ai.launchMode + availableId;
}
private static String unwrapIntent(Intent intent) {
Set<String> categories = intent.getCategories();
if (categories == null) return null;
// Get plugin activity class name from categories
Iterator<String> it = categories.iterator();
while (it.hasNext()) {
String category = it.next();
if (category.charAt(0) == REDIRECT_FLAG) {
return category.substring(1);
}
}
return null;
}
- ApkBundleLauncher的setUp方法的setUp方法
啟動一個新的android 本身的WebView倒庵,這里作者特別注釋到:在Android7.0上,在第一次創(chuàng)建WebView的時候炫刷,它會用WebView的Assets路徑替換掉原Application Assets路徑擎宝,一旦發(fā)生我們先前所做的努力化為泡影,因此我們盡可能將它推到前面設(shè)置浑玛。
@Override
public void setUp(Context context) {
super.setUp(context);
if (Build.VERSION.SDK_INT < 24) return;
Bundle.postUI(new Runnable() {
@Override
public 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());
}
});
}
準備工作都完成了绍申,接著到調(diào)用loadBundles的方法,加載所有模塊顾彰,這里在 prepareForLaunch() 包含了很多處理极阅,這里暫且不詳細講了,不然又要扯很遠再回來涨享,下一節(jié)會詳細講筋搏。
等待這一切完成之后,就通過Handler通知完成厕隧,回到LaunchActivity的OnStart內(nèi)繼續(xù)執(zhí)行拆又。
private static void loadBundles(List<Bundle> bundles) {
sPreloadBundles = bundles;
// Prepare bundle
for (Bundle bundle : bundles) {
bundle.prepareForLaunch();//會產(chǎn)生IO操作
}
// Handle I/O
if (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 launchers
for (BundleLauncher launcher : sBundleLaunchers) {
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;
}
}
至此儒旬,啟動流程基本講完了,插件的加載過程帖族,也就是prepareForLaunch()部分栈源,我將在下一節(jié)再詳細介紹。
敬請期待J恪甚垦!!