Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android插件化(三)加載插件apk中的Resource資源

Android插件化(三)加載插件apk中的Resource資源

編輯:關於Android編程

如何加載未安裝apk中的資源文件呢?我們從android.content.res.AssetManager.java的源碼中發現,它有一個私有方法addAssetPath,只需要將apk的路徑作為參數傳入,我們就可以獲得對應的AssetsManager對象,然後我們就可以使用AssetsManager對象,創建一個Resources對象,然後就可以從Resource對象中訪問apk中的資源了。總結如下:
1.新建一個AssetManager對象 2.通過反射調用addAssetPath方法 3.以AssetsManager對象為參數,創建Resources對象即可。

代碼如下:

package net.mobctrl.hostapk;

import java.io.File;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;

/**
 * @Author Zheng Haibo
 * @PersonalWebsite http://www.mobctrl.net
 * @version $Id: LoaderResManager.java, v 0.1 2015年12月11日 下午7:58:59 mochuan.zhb
 *          Exp $
 * @Description 動態加載資源的管理器
 */
public class BundlerResourceLoader {

    private static AssetManager createAssetManager(String apkPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            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;
    }

    /**
     * 獲取Bundle中的資源
     * @param context
     * @param apkPath
     * @return
     */
    public static Resources getBundleResource(Context context){
        AssetsManager.copyAllAssetsApk(context);
        File dir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
        String apkPath = dir.getAbsolutePath()+"/BundleApk.apk";
        System.out.println("debug:apkPath = "+apkPath+",exists="+(new File(apkPath).exists()));
        AssetManager assetManager = createAssetManager(apkPath);
        return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
    }

}

DEMO

注意:我們使用Resources對象,獲取資源時,傳遞的ID必須是離線apk中R文件對應的資源的ID。如果使用getIdentifier方法,第一個參數是資源名稱,第二個參數是資源類型,第三個參數是離線apk的包名,切記第三個參數。

Resources resources = BundlerResourceLoader.getBundleResource(getApplicationContext());
        imageView = (ImageView)findViewById(R.id.image_view_iv);
        if(resources != null){
            String str = resources.getString(resources.getIdentifier("test_str", "string", "net.mobctrl.normal.apk"));
            String strById = resources.getString(0x7f050001);//注意,id參照Bundle apk中的R文件
            System.out.println("debug:"+str);
            Toast.makeText(getApplicationContext(),strById, Toast.LENGTH_SHORT).show();

            Drawable drawable = resources.getDrawable(0x7f020000);//注意,id參照Bundle apk中的R文件
            imageView.setImageDrawable(drawable);
        }

上述代碼是加載離線apk中的字符串和Drawable資源,那麼layout資源呢?

問題引入

我們使用LayoutInflate對象,一般使用方法如下:

View view = LayoutInflater.from(context).inflate(R.layout.main_fragment, null);

其中,R.layout.main_fragment我們可以通過上述方法獲取其ID,那麼關鍵的一步就是如何生成一個context?直接傳入當前的context是不行的。
解決方案有2個:
- 1.創建一個自己的ContextImpl,Override其方法。
- 2.通過反射,直接替換當前context的mResources私有成員變量。<>br
當然,我們是使用第二種方案:

    @Override
    protected void attachBaseContext(Context context) {
        replaceContextResources(context);
        super.attachBaseContext(context);
    }

    /**
     * 使用反射的方式,使用Bundle的Resource對象,替換Context的mResources對象
     * @param context
     */
    public void replaceContextResources(Context context){
        try {
            Field field = context.getClass().getDeclaredField("mResources");
            field.setAccessible(true);
            field.set(context, mBundleResources);
            System.out.println("debug:repalceResources succ");
        } catch (Exception e) {
            System.out.println("debug:repalceResources error");
            e.printStackTrace();
        }
    }

我們在Activity的attachBaseContext方法中,對Context的mResources進行替換,這樣,我們就可以加載離線apk中的布局了。

資源文件的打包過程

如果想要做到插件化,需要了解Android資源文件的打包過程,這樣可以為每一個插件進行編號,然後按照規則生成R文件。例如,以攜程DynamicAPK為例,它將插件的R文件按照如下規則:

1.R文件為int型,前8位代表插件的Id,其中兩個特殊的Id:Host是0x7f,android系統自帶的是以0x01開頭. 2.緊跟著的8位是區分資源類型的,比如layout,id,string,dimen等 3.後面16位是資源的編號

按照上述規則生成對應的插件apk。然後在運行時,我們可以寫一個ResourceManager類,它繼承自Resource對象,然後所有的Activity,都將其context的mResource成員變量修改為ResourceManager類,然後Override其方法,然後在加載資源時,根據不同的id的前綴,查找對應插件的Resource即可。也就是說,用一個類做分發。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved