Android外掛程式化基礎(4),動態啟動外掛程式中的Activity

來源:互聯網
上載者:User

Android外掛程式化基礎(4),動態啟動外掛程式中的Activity
Android外掛程式化基礎(4),動態啟動外掛程式中的ActivityAuthor:鄭海波-莫川簡介

如何動態啟動外掛程式中的Activity呢?我們首先分析,啟動外掛程式中的Activity需要做那些準備?

1.外掛程式中Activity類的載入
也就是ClassLoader的問題。由第一節課中的MultiDex可以知道,我們可以動態載入apk,然後將外掛程式中的class載入到當前的ClassLoader當中。這樣,外掛程式中的class和宿主中的class同屬一個ClassLoader,它們之間的相互調用問題也就解決了。 2.外掛程式中Activity在AndroidManifest.xml中的註冊問題
由於外掛程式apk有自己的AndroidManifest檔案,為了能夠在運行時,動態啟動外掛程式中的Activity,需要在打包時,將外掛程式apk的Activity註冊移動到宿主apk的AndroidManifest檔案中。 3.外掛程式中Activity的資源載入問題
處理外掛程式中的資源載入問題,是外掛程式化最難的問題之一!我們需要考慮很多問題:
1.外掛程式Activity運行時如何即時擷取Resources對象,並且能夠根據外掛程式包名對應下的R檔案的id,尋找到Resources中的資源。
2.外掛程式apk中的資源檔與其他外掛程式及宿主之間的資源名稱衝突如何解決?
3.宿主及各外掛程式的資源如何統一併且方便的管理?問題的解決:1.類的載入

我們使用之前我們改造的AssetsMultiDexLoader,來載入assets目錄下的apk。由於之前的部落格已經說明了問題,再次不在贅述。

2.外掛程式中的Activity在宿主AndroidManifest中註冊

這件事情需要在打包時處理,也就是說,我們需要改造我們的打包工具,在打包時,將各個外掛程式的AndroidManifest檔案合并到宿主AndroidManifest檔案中。

3.外掛程式資源的載入問題

我們需要在編譯過程和運行過程分別做處理:

3.1 編譯過程

我們首先回顧一下Android打包的過程:
1.產生R.java檔案
比如:

aapt package -f -m -J ./gen -S res -M AndroidManifest.xml -I D:\android_sdk_for_studio\platforms\android-22\android.jar

2.清空bin目錄
清空上次產生的檔案
3.編譯java檔案和jar包

javac -encoding GBK -target 1.5 -bootclasspath D:\android_sdk_for_studio\platforms\android-22\android.jar -d bin src\net\mobctrl\normal\apk\*.java gen\net\mobctrl\normal\apk\R.java -classpath libs\*.jar

4.使用dx工具打包成classes.dex

dx --dex --output=C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\classes.dex C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\

5.編譯成資源檔

aapt package -f -M AndroidManifest.xml -S res -I D:\android_sdk_for_studio\platforms\android-22\android.jar -F bin\resources.ap_ --non-constant-id

6.使用sdklib.jar工具產生未簽名的apk

java -cp D:\android_sdk_for_studio\tools\lib\sdklib.jar com.android.sdklib.build.ApkBuilderMain bin\MyCommond.apk -v -u -z bin\resources.ap_ -f bin\classes.dex -rf C:\Users\mochuan.zhb\newworkspace\BundleApk5\src

7.使用jarsigner對apk進行簽名
jarsigner -verbose -keystore C:\test.keystore -storepass 123456 -keypass 123456 -signedjar C:\projectdemo-signed.apk C:\test.apk test
 

3.2編譯過程的修改

為瞭解決外掛程式與外掛程式之間以及宿主之間的資源衝突問題,我們需要對外掛程式進行編號,修改R檔案的產生過程。我們知道,R檔案中的ID是一個int類型,總共32位。那麼這32位分別代表什麼含義呢?
1.前8位代表外掛程式的packageId,其中兩個特殊的Id:Host是0x7f,android系統內建的是以0x01開頭.
2.緊跟著的8位是區分資源類型的,比如layout,id,string,dimen等
3.後面16位是資源的編號

為瞭解決資源的命名衝突,一般由以下2中方法:

1.約定

團隊在開發時,對資源的命名進行約定,比如各業務線按照一定的規則命名,大家准守規則,避免重複。
然後在打包時,我們對各個外掛程式的資源進行合并,統一產生R檔案,所有外掛程式和宿主的R檔案內容都是完全一樣的,資源都儲存在宿主專案的資源中。
說明:Google使用的Android打包過程,就是這樣的。比如主project依賴lib_project1,lib_project2等,在編譯主project的時候,它就將各個lib項目的資源都複製到主project中,然後使用aapt進行統一產生R檔案,產生多個不同包名的R檔案,但是R檔案的內容是完全一樣的。

2.修改aapt

為了從機制上避免資源名稱重複的問題,我們可以通過修改aapt的源碼,讓其可以根據不同的packageId產生不同的id。也就是說,R檔案中的id由以下32位組成:
[packageId(8)][resourceType(8)][resourcesSeq(16)]
我們為每一個外掛程式分配一個packageId,範圍是(1,127).
修改aapt的源碼之後,我們需要改動打包過程中的第一步和第五步,在產生R檔案和編譯資源的時候,使用我們改造後的aapt。這樣,最終產生的apk,其R檔案就是按照我們分配的方式。
我們可以通過反編譯,查看test.apk中的R檔案,如所示,我們設定的packageId是5:

【test.apk的反編譯圖】
反編譯之後的id需要轉化為16進位顯示。

反編譯步驟:
1.解壓apk檔案
2.使用d2j-dex2jar工具,將dex轉化為jar
3.使用Java-Decompiler反編譯jar

3.3運行過程中資源的載入1.將外掛程式apk載入到當前的AssetsManager中

核心代碼:

    /**     * 修改AssetManager     *      * @param assetManager     * @param apkPaths     * @return     */    private static AssetManager modifyAssetManager(AssetManager assetManager,            List apkPaths) {        if (apkPaths == null || apkPaths.size() == 0) {            return null;        }        try {            for (String apkPath : apkPaths) {                try {                    AssetManager.class.getDeclaredMethod("addAssetPath",                            String.class).invoke(assetManager, apkPath);                } catch (Throwable th) {                    System.out.println("debug:createAssetManager :"                            + th.getMessage());                    th.printStackTrace();                }            }            return assetManager;        } catch (Throwable th) {            System.out.println("debug:createAssetManager :" + th.getMessage());            th.printStackTrace();        }        return null;    }    /**     * 擷取整個App的資源管理員中的資源     *      * @param context     * @param apkPath     * @return     */    public static Resources getAppResource(Context context) {        System.out.println("debug:getAppResource ...");        AssetsManager.copyAllAssetsApk(context);        // 擷取dex檔案清單        File dexDir = context.getDir(AssetsManager.APK_DIR,                Context.MODE_PRIVATE);        File[] szFiles = dexDir.listFiles(new FilenameFilter() {            @Override            public boolean accept(File dir, String filename) {                return filename.endsWith(AssetsManager.FILE_FILTER);            }        });        if (szFiles == null || szFiles.length == 0) {            return context.getResources();        }        System.out.println("debug:getAppResource szFiles = "+szFiles.length);        List apkPaths = new ArrayList();        for (File f : szFiles) {            Log.i(TAG, "load file:" + f.getName());            apkPaths.add(f.getAbsolutePath());            System.out.println("debug:apkPath = " + f.getAbsolutePath());        }        AssetManager assetManager = modifyAssetManager(context.getAssets(),                apkPaths);        AppResource resources = new AppResource(                assetManager, context.getResources().getDisplayMetrics(),                context.getResources().getConfiguration());        return resources;    }
2.Application中Resources的載入

核心代碼

public class HostApplication extends Application {    private Resources mAppResources = null;    private Resources mOldResources = null;    @Override    public void onCreate() {        super.onCreate();        mOldResources = super.getResources();        AssetsMultiDexLoader.install(this);// 載入assets中的apk        installResource();    }    @Override    public Resources getResources() {        if(mAppResources == null){            return mOldResources;        }        return this.mAppResources;    }    private void installResource() {        if (mAppResources == null) {            mAppResources = BundlerResourceLoader.getAppResource(this);// 載入assets中的資來源物件        }    }    @Override    public AssetManager getAssets() {        if (this.mAppResources == null) {            return super.getAssets();        }        return this.mAppResources.getAssets();    }}
3.外掛程式Activity替換Resource對象

以BundleActivity為例:

public class BundleActivity extends BaseActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.bundle_layout);        findViewById(R.id.text_view).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_LONG).show();            }        });    }    @Override    public Resources getResources() {        return getApplication().getResources();    }}

最關鍵的是,重寫getResources()方法,使用Application的Resource替換當前的Resource方法。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.