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

前言

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

Sample

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

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

1
gradlew buildLib -q

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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中输入指令,编译应用插件

1
gradlew buildBundle -q

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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中注册

1
2
3
4
5
6
7
8
9
10
11
12
13
public Application() {
Small.preSetUp(this);
}
@Override
public void onCreate() {
super.onCreate();
// Optional
Small.setBaseUri("http://code.wequick.net/small-sample/");
Small.setWebViewClient(new MyWebViewClient());
Small.setLoadFromAssets(BuildConfig.LOAD_FROM_ASSETS);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class LaunchActivity extends Activity {
private static final long MIN_INTRO_DISPLAY_TIME = 1000000000; // mμs -> 1.0s
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
}
@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" 显示足够的时间, 实际应用中不需要
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();
}
}
});
}
}

3、启动过程

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

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

BundleLauncher

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

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

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

这里写图片描述

启动流程

一、preSetUp

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
}
protected static void loadLaunchableBundles(Small.OnCompleteListener listener) {
Contextcontext = 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();
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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();
}
}
private static class LoadBundleHandler extends Handler {
private Small.OnCompleteListener mListener;
public LoadBundleHandler(Small.OnCompleteListener listener) {
mListener = listener;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_COMPLETE:
if (mListener != null) {
mListener.onComplete();
}
mListener = null;
sThread = null;
sHandler = null;
break;
}
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private static void loadBundles(Context context) {
JSONObject manifestData;
try {
//获取 /data/data/<application package>/files 目录下的 bundle.json
File 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 file
manifestData = 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():创建出WebView
launcher.setUp(context);
}
}

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

  1. 优先读取缓存中的bundle.json
  2. 从patch中读取bundle.json
  3. 从assets中读取bundle.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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/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) {
// 遍历所有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存储起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
*/
@Override
public void setUp(Context context) {
super.setUp(context);
// Read the registered classes in host's manifest file
File 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
*/
@Override
public boolean preloadBundle(Bundle bundle) {
if (sActivityClasses == null) return false;
String pkg = bundle.getPackageName();
return (pkg == null || pkg.equals("main"));
}
/**
* openUri执行第一时机
*
* @param bundle
*/
@Override
public 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
*/
@Override
public void launchBundle(Bundle bundle, Context context) {
prelaunchBundle(bundle);
super.launchBundle(bundle, context);
}
}

ApkBundleLauncher

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

一、onCreate

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@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
// 反射获取ActivityThread
thread = 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,目前没看出什么作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public void setUp(Context context) {
super.setUp(context);
Field f;
// AOP for pending intent
// 通过TaskStackBuilder创建的pendingIntent
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) {
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表示当前启动器分配成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
@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来找到合适的BundleLauncher
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
// 如果没有指定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 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)) {
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则是保存着插件加载所需的实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Override
public void loadBundle(Bundle bundle) {
String packageName = bundle.getPackageName();
// 解析插件或补丁的Manifests.xml,这一步的是否插件或补丁来源于上一步的判断
BundleParser parser = bundle.getParser();
// 收集其Activity
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) {
return;
}
// Record activities for intent redirection
if (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 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
// 设置该插件的manifest中定义的入口Activity
bundle.setEntrance(parser.getDefaultActivityName());
}

五、postSetUp

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
@Override
public void postSetUp() {
super.postSetUp();
if (sLoadedApks == null) {
Log.e(TAG, "Could not find any APK bundles!");
return;
}
// 取出需要加载的插件 LoadedApk
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];
// 添加宿主app的资源路径
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);
// 为宿主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' 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;
}

WebBundleLauncher

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class WebBundleLauncher extends AssetBundleLauncher {
private static final String FD_BASE = "small_web";
private static final String FILE_INDEX = "index.html";
@Override
protected String[] getSupportingTypes() {
return new String[] {"web"};
}
@Override
protected String getBasePathName() {
return FD_BASE;
}
@Override
protected String getIndexFileName() {
return FILE_INDEX;
}
@Override
protected Class<? extends Activity> getActivityClass() {
return WebActivity.class;
}
@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());
}
});
}
}

结语

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

这里写图片描述

坚持原创技术分享,您的支持将鼓励我继续创作!