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中输入指令,编译公共库

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();

    // Optional
    Small.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

    @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()

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

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发送消息通知完成加载,然后释放无相关的变量

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来加载的

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

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存储起来

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

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

@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,目前没看出什么作用

@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表示当前启动器分配成功

@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则是保存着插件加载所需的实体

@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到宿主中

@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

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启动流程如下图,下一步就是等跳转的时候,将我们的预先占好的坑交给它们去骗过系统的检测

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_30379689/article/details/79208290
今日推荐