• Android插件化(三)载入插件apk中的Resource资源


    Android载入插件apk中的Resource资源

    简单介绍

    怎样载入未安装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就可以。也就是说,用一个类做分发。

    Android插件化相关资料

  • 相关阅读:
    不要同时使用ReentrantLock类与synchronized关键字锁定会修改同一个资源的不同方法
    java中volatile关键字的含义
    浅谈操作系统对内存的管理
    Java线程池使用说明
    写在清明节之后
    PY一天一学
    24小时只睡了1个小时
    关于团队关于吐槽
    出尔反尔
    时间都去哪儿了?
  • 原文地址:https://www.cnblogs.com/cxchanpin/p/7345654.html
Copyright © 2020-2023  润新知