Android Small插件化框架源碼分析

Android Small插件化框架源碼分析

目錄

概述 
Small如何使用 
插件加載流程 
待改進的地方html

1、概述

Small是一個寫得很是簡潔的插件化框架,工程源碼位置:https://github.com/wequick/Small 
插件化的方案,說到底要解決的核心問題只有三個:linux

  • 1.1 插件類的加載git

    • 這個問題的解決和其它插件化框架的解決方法差很少。Android的類是由DexClassLoader加載的,經過反射能夠將插件包動態加載進去。Small的gradle插件生成的是.so包,在初始化的時候會經過.so文件生成.zip文件,再由.zip文件生成一個dex元素,反射添加到宿主類加載器的dexPathList裏。
  • 1.2 插件資源的處理github

    • 這裏各插件化框架解決辦法通常有兩種想法:一是插件間不共享資源訪問,辦法就是每一個插件生成一個AssertManager來訪問它本身的資源,這樣就不會存在資源id衝突的問題;另外一種是你們都共用一個AssetManager,這樣插件的資源是共享的,能夠相互訪問,可是要解決資源id衝突的問題。Small採用的是後者,經過修改aapt的生成產物解決了資源id衝突問題,因爲共享資源訪問,能夠作到極小或者根本沒有資源冗餘,從而減少插件包的大小;
  • 1.3 Activity註冊和生命週期問題web

    *大部分插件化框架解決辦法都是採用在宿主工程裏預先註冊Activity佔坑,而後經過佔坑Activity將生命週期回調傳回給插件Activity的方式。這裏Small處理的比較有特點,經過替換 ActivityThread 裏的mInstrumentation,在Instrumentation的newActivty實現裏面實例化了插件Activity,經過較小改動就能徹底解決生命週期回調的問題。json

Small的功能模塊主要有: 
gradle-small插件:Small中的一個gradle自定義插件,用於打包組件; 
small library:提供給用戶使用的Android Library,主要提供插件加載,解析等功能;緩存

2、使用Small

2.1工程命名 
首先Small對工程名稱以下要求:bash

  • app:host工程
  • app.*:app插件工程;
  • lib.*:library插件工程;
  • web.*:web插件工程;
  • 其餘:其餘assert 工程;

2.2 插件引入 
在host工程的rootProject的build.gradle中須要引入small插件; 
引入small插件後,默認幫你的全部工程引入了一個library依賴small,咱們經過small提供的各類接口來實現插件化得一些功能,好比加載插件,打開某個插件中的ui界面,建立某個插件提供的fragment對象等;微信

2.3 插件聲明 
做爲host程序,要作的最重要的事情就是插件管理,插件跳轉uri聲明: 
插件聲明在host程序的assert/bundle.json中聲明,格式以下:app

{
  "version": "1.0.0", "bundles": [ { "uri": "lib.utils", "pkg": "net.wequick.example.small.lib.utils" }, { "uri": "lib.style", "pkg": "com.example.mysmall.lib.style" }, { "uri": "main", "pkg": "net.wequick.example.small.app.main" }, { "uri": "home", "pkg": "net.wequick.example.small.app.home", "rules"{ "page1",".MyPage1", "page2","net.wequick.example.small.app.home.MyPage2" } }, { "uri": "message", "pkg": "net.wequick.example.small.app.message" }, { "uri": "find", "pkg": "net.wequick.example.small.app.find" }, { "uri": "mine", "pkg": "net.wequick.example.small.app.mine" }, { "uri": "detail", "pkg": "net.wequick.example.small.app.detail" }, { "uri": "about", "pkg": "net.wequick.example.small.web.about" } ] }

bundles 中的每一個元素都是一個插件的聲明;

{
      "uri": "home", "pkg": "net.wequick.example.small.app.home", "rules"{ "page1",".MyPage1", "page2","net.wequick.example.small.app.home.MyPage2" } }

在採用small框架的應用中,跳轉插件的界面都是經過uri來指定的,也就是一個uri惟一對應一個插件; 
pkg是插件的包名; 
rules:若是插件提供了多個界面供其餘人使用,咱們須要經過rules將它們區分開來;

舉個例子:

Small.openUri("home", context); 

上面這行語句是打開一個插件的界面,home對應的就是上面的uri字段,咱們經過home,查找到對應的插件,而後它會打開這個插件在AndroidManifest.xml中聲明的第一個Activity。

若是你要調起插件中聲明的其餘acitivity,你就須要用到rules了,首先是在bundles.json中聲明你要跳轉的acitivity,如上,若是你想調用home插件中的界面activity MyPage1, 
你只須要寫以下語句:

Small.openUri("home/page1", context); 

這個時候調用到的就是net.wequick.example.small.app.home.MyPage1這個類對應的activity了

你在調用插件的時候也能夠經過queryparameter傳參:

Small.openUri("home?from=main", context); 在調起的插件工程獲取參數: Uri uri = Small.getUri(this); if (uri != null) { String from = uri.getQueryParameter("from"); // Do stuff by `from' }

2.4 插件加載管理 
在host中咱們通常要作兩件事情: 
初始化插件的baseUri; 
這個通常在Application的onCreate完成:Small.setBaseUri(「http://m.wequick.net/demo/「);

另外是加載全部插件(待優化,一次加載全部插件過於耗時,咱們應該是隻加載必備插件,而後再慢慢加載其餘插件) 
Small.setUp(this, new net.wequick.small.Small.OnCompleteListener() { 
@Override 
public void onComplete() { 
mContentView.postDelayed(new Runnable() { 
@Override 
public void run() { 
Small.openUri(「main」, LaunchActivity.this); 
finish(); 

}, 2000); 

}); 
setUp提供了插件加載完成的回調,通常咱們等插件加載完才能經過openUri去啓動界面展現;

2.5 經常使用操做

  • 打開界面: 
    Small.openUri(「main」, context);

  • 建立插件提供的fragment : 
    Fragment fragment = Small.createObject(「fragment-v4」, 「home」, context); 
    若是沒有經過rules指定類名,默認的類名是 包名.MainFragment 
    若是指定了類名,和前面的規則同樣。 
    createObject的第一個參數目前僅支持」fragment」或者」fragment-v4」

  • 獲取某個插件界面的Intent 
    有時候咱們不是直接打開界面,好比通知欄通知,咱們須要設置一個PendingIntent ,那這個時候須要的是一個Intent 
    此時可經過獲取:

Intent intent = Small.getIntentOfUri("main",context)
  • 獲取調用時候的query信息:
//調用 Small.openUri("home?from=main", context); //參數獲取; Uri uri = Small.getUri(this); if (uri != null) { String from = uri.getQueryParameter("from"); // Do stuff by `from' }

3、插件加載流程

Small的核心類比較少,主要包含三類:

  • Small:接口類,提供用戶能使用的各種接口;
  • Bundle: 表明插件,保存了插件的所有信息,控制了插件的load流程,以及lauch流程;它會調用各種BundleLauncher來幹活;
  • BundleLauncher:有多個子類,好比.app.,.lib.類的插件,對應的是ApkBundleLauncher,.web.*對應的就是WebBundleLauncher,其餘對應的就是ActivityLauncher

插件相關的操做主要有load和lauch:

其中應用啓動的時候要準備插件環境,進行的就是load操做,主要是解析插件信息並緩存起來,並將插件dex和資源添加到host;load完成才能進行其餘插件操做

  • 在load插件的時候,通常分兩步: 
    preloadBundle,通常判斷插件可否被加載,返回false就不須要進行加載了;這裏咱們通常進行插件合法性檢查 
    loadBundle(Bundle bundle):真正加載解析插件的各種信息並存入Bundle對象; 
    lauch指的是通常指加載插件界面,典型的就是調用Small.openUri打開插件界面;
  • lauch插件的時候也分兩步: 
    prelaunchBundle(Bundle bundle):準備Bundle的一些必要信息:通常是生成bundle的intent信息,主要是要啓動的類名的生成;(見ApkBundleLauncher) 
    launchBundle(Bundle bundle, Context context):判斷插件是否能加載,能加載就啓動acitivity;

3.1 load的流程

Small.setup --> Bundle.setupLaunchers (調用各BundleLauncher的setUp,其中ApkBundleLauncher的setUp會替換掉ActivityThread的mInstrumentation成員變量) -->Bundle.loadLaunchableBundles(解析bundle.json,並加載bundle) -->Bundle.loadManifest(讀取了bundle.json) --> Bundle.loadBundles ( 開啓異步線程進行加載) 異步線程裏面的流程: -->Bundle.prepareForLaunch -->遍歷ActivityLauncher,WebBundleLauncher,ApkBundleLauncher,調用它們的BundleLauncher.resolveBundle 方法尋找合適的BundleLauncher解析插件包; -->BundleLauncher.preloadBundle(主要是判斷可否加載) -->BundleLauncher.loadBundle(正真的加載解析動做) 除了直接使用的三個BundleLauncher類(ActivityLauncher,WebBundleLauncher,ApkBundleLauncher),還有兩個中間類,SoBundleLauncher 和AssetBundleLauncher, 其中SoBundleLauncher主要是提供了一個preloadBundle函數實現,裏面實現了1 按支持的type與package名對比,快速判斷此BundleLauncher可否解析此插件;2 校驗插件簽名是否合法來肯定是否要解析次插件; AssetBundleLauncher從SoBundleLauncher繼承,幹了WebBundleLauncher 要作的loadBundle大部分的活。 

3.2 啓動界面流程

Small.openUri --> Bundle.getLaunchableBundle -->Bundle.matchesRule(經過uri匹配合適的bundle) --> Bundle.launchFrom-->ApkBundleLauncher.launchBundle-->ApkBundleLauncher.prelaunchBundle-->BundleLauncher.launchBundle->Activity.startActivityForResult -->ApkBundleLauncher.InstrumentationWrapper.execStartActivity--》ApkBundleLauncher.InstrumentationWrapper.wrapIntent(將插件activity類保存在intent的category中,同時將intent的component裏面的類替換爲host 中聲明的佔位Activity,以經過ActivityManager的檢查)--》ApkBundleLauncher.InstrumentationWrapper.newActivity(取出intent中的插件activity類,並實例化返回,用於接收生命週期回調) --》ApkBundleLauncher.InstrumentationWrapper.callActivityOnCreate(加入插件apk到AssertManager中,用於讀取插件資源,應用插件Theme)

3.3 插件打包過程

todo

4、須要改進的點

  • 加載插件優先級 
    目前load插件的時候是把全部插件都加載進來纔算準備好,應該改成只加載必備插件就能夠發準備好回調讓主流程繼續跑,其餘插件在後臺繼續加載;

  • 優化經過PackageManager獲取包內Activity信息 
    經實際測試,經過PackageManager獲取包內Activity信息會耗時很大(700ms),可是加載插件又須要插件包裏的activity的信息,擬經過在打包時提取相應信息放入文件,加載插件只須要讀取文件解析就好了;

  • 插件合法性校驗 
    目前的插件合法性是經過包的簽名對比來實現的,也是調用的PackageManager,效率比較低.

  • 字符串資源超過128編譯報錯問題 
    目前發現插件處理資源的時候,若是工程中存在strings.xml中有字符串資源超過128字符就會報錯,還有存在字符串樣式的也會有問題;應該是做者對資源索引表中的StringPoll結構理解有誤致使。

      • 嵌入式企鵝圈原創團隊由阿里、魅族、nvidia、龍芯、炬力、拓爾思等資深工程師組成。百分百原創,每週兩篇,分享嵌入式、Linux、物聯網、GPU、Android、自動駕駛等技術。歡迎掃碼關注微信公衆號:嵌入式企鵝圈,實時推送原創文章!

相關文章
相關標籤/搜索