前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >资源的插件化

资源的插件化

作者头像
用户3112896
发布2019-09-26 16:40:22
9900
发布2019-09-26 16:40:22
举报
文章被收录于专栏:安卓圈安卓圈

1.android资源文件分为两类:

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

代码语言:javascript
复制
Resources resources = getResources();
String appName = resources.getString(R.string.app_name);           

第二类是assets目录下存放的原始资源文件,apk在编译时不会编译assets下的资源文件

代码语言:javascript
复制
Resources resources = getResources();
AssetManager am = getResources().getAssets();
InputStream is = getResources().getAssets().open("filename");

2.Resources内部各种方法其实都是间接调用AssetManager的内部方法。AssetManager的addAssetPath方法会在app启动的时候把当前apk的路径传进去,就能访问apk的所有资源了。在这里可以把插件apk的资源塞进去

3.apk打包时会生成一个resource.arsc文件,它就是一个Hash表,存放着每个十六进制值和资源的对应关系

***资源的插件化解决方案***

代码语言:javascript
复制
public class Dynamic implements IDynamic {

    @Override
    public String getStringForResId(Context context) {
        return context.getResources().getString(R.string.myplugin1_hello_world);
    }
}
代码语言:javascript
复制
<resources>
    <string name="app_name">Plugin1</string>
    <string name="myplugin1_hello_world">myplugin1_hello_world</string>
</resources>
代码语言:javascript
复制
public class BaseActivity 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 tv;

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);

        Utils.extractAssets(newBase, pluginName);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //第三步:加载外部的插件,生成插件对应的ClassLoader
        File extractFile = this.getFileStreamPath(pluginName);
        dexpath = extractFile.getPath();

        fileRelease = getDir("dex", 0);

        classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());
    }

    /**
     * 第一步:通过反射,创建AssetManager对象,调用addAssetPath方法,把插件Plugin的路径添加到这个AssetManager对象中
     **/
    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());
    }

    /**
     * 第二步:重写Acitivity的getAsset,getResources和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;
    }
}
代码语言:javascript
复制
public class MainActivity extends BaseActivity {
    TextView tv;

    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn_6 = (Button) findViewById(R.id.btn_6);

        tv = (TextView)findViewById(R.id.tv);

        //带资源文件的调用
        btn_6.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                loadResources();
                Class mLoadClassDynamic = null;
                try {
                    //第四步:通过反射,获取插件中的类,构造出插件类的对象dynamicObject,然后就可以让插件中的类读取插件中的资源了
                    mLoadClassDynamic = classLoader.loadClass("jianqiang.com.plugin1.Dynamic");
                    Object dynamicObject = mLoadClassDynamic.newInstance();

                    IDynamic dynamic = (IDynamic) dynamicObject;
                    String content = dynamic.getStringForResId(MainActivity.this);
                    tv.setText(content);
                    Toast.makeText(getApplicationContext(), content + "", Toast.LENGTH_LONG).show();
                } catch (Exception e) {
                    Log.e("DEMO", "msg:" + e.getMessage());
                }
            }
        });
    }
}

下面举个例子,就是类似QQ换皮肤

fork了强哥的github代码到自己的github下 https://github.com/king1039/Dynamic3

将代码跑起来,Plugin1和Plugin2打成apk放到HostApp的assets下

贴下主要代码

代码语言:javascript
复制
public class BaseActivity extends Activity {

    private AssetManager mAssetManager;
    private Resources mResources;
    private Resources.Theme mTheme;

    protected HashMap<String, PluginInfo> plugins = new HashMap<String, PluginInfo>();

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);

        Utils.extractAssets(newBase, "plugin1.apk");
        Utils.extractAssets(newBase, "plugin2.apk");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        genegatePluginInfo("plugin1.apk");
        genegatePluginInfo("plugin2.apk");
    }

    protected void genegatePluginInfo(String pluginName) {
        File extractFile = this.getFileStreamPath(pluginName);
        File fileRelease = getDir("dex", 0);
        String dexpath = extractFile.getPath();
        DexClassLoader classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());

        plugins.put(pluginName, new PluginInfo(dexpath, classLoader));
    }

    protected void loadResources(String dexPath) {
        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();
        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;
    }
}
代码语言:javascript
复制
public class ResourceActivity extends BaseActivity {

    /**
     * 需要替换主题的控件
     * 这里就列举三个:TextView,ImageView,LinearLayout
     */
    private TextView textV;
    private ImageView imgV;
    private LinearLayout layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_resource);

        textV = (TextView) findViewById(R.id.text);
        imgV = (ImageView) findViewById(R.id.imageview);
        layout = (LinearLayout) findViewById(R.id.layout);

        findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                PluginInfo pluginInfo = plugins.get("plugin1.apk");

                loadResources(pluginInfo.getDexPath());

                doSomething(pluginInfo.getClassLoader());
            }
        });

        findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                PluginInfo pluginInfo = plugins.get("plugin2.apk");

                loadResources(pluginInfo.getDexPath());

                doSomething(pluginInfo.getClassLoader());
            }
        });
    }

    private void doSomething(ClassLoader cl) {
        try {
            Class clazz = cl.loadClass("jianqiang.com.plugin1.UIUtil");

            String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);
            textV.setText(str);

            Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);
            imgV.setBackground(drawable);

            layout.removeAllViews();
            View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);
            layout.addView(view);

        } catch (Exception e) {
            Log.e("DEMO", "msg:" + e.getMessage());
        }
    }
}

简单来说,就是针对不同的apk生成不同的ClassLoader,然后通过反射框架取出相应的资源,最终加载显示

doSomething还有另外一个种写法,直接访问R.java的内部类drawable/string/layout中的相应字段对应的十六进制值(这个好屌)

代码语言:javascript
复制
private void doSomething(ClassLoader cl) {
    try {
        Class stringClass = cl.loadClass("jianqiang.com.plugin1.R$string");
        int resId1 = (int) RefInvoke.getStaticFieldObject(stringClass, "hello_message");
        textV.setText(getResources().getString(resId1));


        Class drawableClass = cl.loadClass("jianqiang.com.plugin1.R$drawable");
        int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass, "robert");
        imgV.setBackground(getResources().getDrawable(resId2));

        Class layoutClazz = cl.loadClass("jianqiang.com.plugin1.R$layout");
        int resId3 = (int) RefInvoke.getStaticFieldObject(layoutClazz, "main_activity");
        View view = (View) LayoutInflater.from(this).inflate(resId3, null);
        layout.removeAllViews();
        layout.addView(view);

    } catch (Exception e) {
        Log.e("DEMO", "msg:" + e.getMessage());
    }
}

总的来说,资源插件化就是通过反射AssetManager和addAssetPath来加载插件资源

--摘自《android插件化开发指南》

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 安卓圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档