• Android 插件化开发(三):资源插件化


    在前面的文章中我们成功的加载了外部的Dex(Apk)并执行了插件的Bean代码。这时我们会想,能不能加载并运行插件Apk的Activity。答案当然是能,否则后续我们的研究就没意义了,但是想实现Activity的插件化运行,我们必须要解决一个问题——如何使用插件中的资源

    本文我们就讲一下插件的资源加载机制,并讲述一下如何实现资源的插件化。

    一、资源的加载机制

    Android的资源文件分为两类:

    第一类是res目录下存放的可编辑的资源文件,这类文件在编译时系统会自动在R文件中生成资源文件的16进制值。

    例如:

    public final class R {
        public static final class anim {
          public static final int abc_fade_in=0x7f050000;
          public static final int abc_fade_in=0x7f050000;
          ... 
        } 
    }

    我们在平时的开发时,访问这类资源比较简单,使用Context的getResource方法即可得到res下的各种资源。如下面代码所示:

    String content = mContext.getResource().getString(R.string.content);

    第二类是assets目录下存放的原始资源文件。apk在编译时不会编译assets下的文件,我们不能使用R.的方式访问,只能使用AssetsManager类的open方法来获取assets目录下的文件资源。

    而AssetsManager又源于Resources类的getAssets方法,如下面代码所示:

    Resource resource = getResource();
    AssetsManager am = getResource().getAssets();
    InputStream is  = getResource().getAssets().open("filename");

    通过上面的分析,我们可以初步做出一个结论:我们能使用Resources类是一个很重要的类,通过此类提供的相关API,我们能操作资源的加载。

    二、资源插件化的解决方案

    谈及资源插件化,我们不得不对AssetsManager的API多说一些。

    AssetsManager中有一个addAssetsPath(String Path)方法,App启动的时候就会将当前的apk路径传进去,接下来AssetsManager和Resources就能访问当前apk的所有资源了。

    AssetsManager的addAssetsPath方法不对外,但是我们可以通过反射的方式,把插件apk的路径传到这个方法,这样就把插件的资源添加到一个资源池中了。App有几个插件,我们就调用几次addAssetsPath方法,把插件的资源都塞到池子里。

    这里我们以加载插件Apk里面的字符串资源为目标,实战一下资源插件化:

    首先我们在插件app的string.xml里面定义字符串资源:

     <string name="myplugin1_hello_world">Hello Plugin</string>

    然后我们在宿主app编写如下代码:

    public class MainActivity extends Activity {
    
        private AssetManager mAssetManager;
    
        private Resources mResources;
    
        private Resources.Theme mTheme;
    
        private String dexPath = null;    //apk文件地址
    
        private File fileRelease = null;  //释放目录
    
        protected DexClassLoader classLoader = null;
    
        private String pluginName = "plugin1.apk";
    
        TextView mTextView;
    
        @Override
        protected void attachBaseContext(Context newBase) {
            super.attachBaseContext(newBase);
            extractAssets(newBase, pluginName);
        }
    
        @SuppressLint("NewApi")
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            File extractFile = this.getFileStreamPath(pluginName);
            dexPath = extractFile.getPath();
    
            fileRelease = getDir("dex", 0);
    
            classLoader = new DexClassLoader(dexPath, fileRelease.getAbsolutePath(), null, getClassLoader());
    
            mTextView = findViewById(R.id.tv);
    
            //带资源文件的调用
            findViewById(R.id.btn_6).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    loadResources();
                    try {
                        Class mLoadClassDynamic = classLoader.loadClass("com.plugin1.Dynamic");
                        Object dynamicObject = mLoadClassDynamic.newInstance();
                        IDynamic dynamic = (IDynamic) dynamicObject;
                        String content = dynamic.getStringForResId(MainActivity.this);
                        mTextView.setText(content);
                        Toast.makeText(getApplicationContext(), content + "", Toast.LENGTH_LONG).show();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        protected void loadResources() {
            try {
                AssetManager assetManager = AssetManager.class.newInstance();
                Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
                addAssetPath.invoke(assetManager, dexPath);
                mAssetManager = assetManager;
            } catch (Exception e) {
                e.printStackTrace();
            }
            Resources superRes = super.getResources();
            superRes.getDisplayMetrics();
            superRes.getConfiguration();
            mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
            mTheme = mResources.newTheme();
            mTheme.setTo(super.getTheme());
        }
    
        @Override
        public AssetManager getAssets() {
            return mAssetManager == null ? super.getAssets() : mAssetManager;
        }
    
        @Override
        public Resources getResources() {
            return mResources == null ? super.getResources() : mResources;
        }
    
        @Override
        public Resources.Theme getTheme() {
            return mTheme == null ? super.getTheme() : mTheme;
        }
    
    
        /**
         * 把Assets里面得文件复制到 /data/data/files 目录下
         *
         * @param context
         * @param sourceName
         */
        public static void extractAssets(Context context, String sourceName) {
            AssetManager am = context.getAssets();
            InputStream is = null;
            FileOutputStream fos = null;
            try {
                is = am.open(sourceName);
                File extractFile = context.getFileStreamPath(sourceName);
                fos = new FileOutputStream(extractFile);
                byte[] buffer = new byte[1024];
                int count = 0;
                while ((count = is.read(buffer)) > 0) {
                    fos.write(buffer, 0, count);
                }
                fos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                closeSilently(is);
                closeSilently(fos);
            }
        }
    
        private static void closeSilently(Closeable closeable) {
            if (closeable == null) {
                return;
            }
            try {
                closeable.close();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }

    以上代码分为四个逻辑部分:

    1).  loadResources方法。通过反射创建AssetManager对象,调用addAssetPath方法,把插件Plugin1路径添加到这个AssetManager对象中。从此这个AssetManager就只为插件Plugin1服务了。在这个AssetManager对象的基础上,创建对应的Resource和Theme对象。

    2). 重写Activity的getAsset,getResource和getTheme方法。重写逻辑见上面的代码。

    3). 加载外部的插件,生成这个插件对应的ClassLoader。

    4). 通过反射,获取插件中的类,构造出插件类的对象,然后就可以让插件类读取插件中的资源了。

    上述内容代码仓库地址为:https://github.com/renhui/RHPluginProgramming/tree/master/DexResAccess-master

    基于这个思路,我们可以尝试使用插件资源替换当前显示的内容,实现换肤效果,核心思想是一样的,这里就不过多赘述了。

  • 相关阅读:
    Mysql 高可用方案讨论
    python 自动化之路 day 20 Django进阶/BBS项目【一】
    python 自动化之路 day 18 前端内容回顾、补充/Django安装、创建
    python 自动化之路 day 19 Django基础[二]
    Mysql配置文件读取顺序
    Mysql中查看每个IP的连接数
    循环杀死Mysql sleep进程脚本
    JS设计模式——5.单体模式(用了这么久,竟全然不知!)
    JS设计模式——4.继承(示例)
    JS设计模式——4.继承(概念)
  • 原文地址:https://www.cnblogs.com/renhui/p/11907493.html
Copyright © 2020-2023  润新知