Android Small外掛程式化架構源碼分析

來源:互聯網
上載者:User

標籤:

Android Small外掛程式化架構源碼分析目錄

概述 
Small如何使用 
外掛程式載入流程 
待改進的地方

一、概述

Small是一個寫得非常簡潔的外掛程式化架構,工程源碼位置:https://github.com/wequick/Small 
外掛程式化的方案,說到底要解決的核心問題只有三個:

  • 1.1 外掛程式類的載入

    • 這個問題的解決和其它外掛程式化架構的解決方案差不多。Android的類是由DexClassLoader載入的,通過反射可以將外掛程式包動態載入進去。Small的gradle外掛程式產生的是.so包,在初始化的時候會通過.so檔案產生.zip檔案,再由.zip檔案產生一個dex元素,反射添加到宿主類載入器的dexPathList裡。
  • 1.2 外掛程式資源的處理

    • 這裡各外掛程式化架構解決辦法一般有兩種想法:一是外掛程式間不共用資源訪問,辦法就是每個外掛程式產生一個AssertManager來訪問它自己的資源,這樣就不會存在資源id衝突的問題;另一種是大家都共用一個AssetManager,這樣外掛程式的資源是共用的,可以相互訪問,但是要解決資源id衝突的問題。Small採用的是後者,通過修改aapt的產生產物解決了資源id衝突問題,由於共用資源訪問,可以做到極小或者根本沒有資源冗餘,從而減小外掛程式包的大小;
  • 1.3 Activity註冊和生命週期問題

    *大部分外掛程式化架構解決辦法都是採用在宿主工程裡預先註冊Activity占坑,然後通過占坑Activity將生命週期回調傳回給外掛程式Activity的方式。這裡Small處理的比較有特色,通過替換 ActivityThread 裡的mInstrumentation,在Instrumentation的newActivty實現裡面執行個體化了外掛程式Activity,通過較小改動就能完全解決生命週期回調的問題。

Small的功能模組主要有: 
gradle-small外掛程式:Small中的一個gradle自訂外掛程式,用於打包組件; 
small library:提供給使用者使用的Android Library,主要提供外掛程式載入,解析等功能;

二、使用Small

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

  • 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中聲明,格式如下:

{  "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‘}
三、外掛程式載入流程

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

四、需要改進的點
  • 載入外掛程式優先順序 
    目前load外掛程式的時候是把所有外掛程式都載入進來才算準備好,應該改為只載入必備外掛程式就可以發準備好回調讓主流程繼續跑,其他外掛程式在後台繼續載入;

  • 最佳化通過PackageManager擷取包內Activity資訊 
    經實際測試,通過PackageManager擷取包內Activity資訊會耗時很大(700ms),但是載入外掛程式又需要外掛程式包裡的activity的資訊,擬通過在打包時提取相應資訊放入檔案,載入外掛程式只需要讀取檔案解析就行了;

  • 外掛程式合法性校正 
    目前的外掛程式合法性是通過包的簽名對比來實現的,也是調用的PackageManager,效率比較低.

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

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

Android Small外掛程式化架構源碼分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.