Android 插件化开发——获取插件中的资源

之前做的项目是RN的项目,最近在将之前的项目用Android组件化重构一遍 + 白天还得工作。真的是忙的要命,再加上自己十一点之前肯定上床睡觉的“坏习惯”, 没办法只能在中秋节的今天来继续学习插件化。 祝各位小伙伴:中秋快乐!

之前学习了宿主APP如何加载插件APK,本篇博客学习:宿主APP加载插件APK的资源。

因为我们平时用Activity的居多,并且每个Activity都会调用各种资源,因此学习本篇博客还是很有必要的。

Android中的资源分为两类:

  1. res目录下存放的是可编译的资源文件。编译时系统会自动在R.java中生成资源文件的十六进制。
  2. assets目录下存放的是原始资源文件。APK编译的时候不会编译assets下的资源文件。因此我们要访问这些资源,只能通过AssetManager类来达到目的。

关于AssetManager,可能会有点陌生,说实话我自己在学习插件化之前,真的是压根用都没用过。 这里我捎带提一下。

**

Resources 和 AssetManager

**
对于Resources 我们可能不会陌生,我们经常用getString, getText等方法, 但是看下源码就会知道,最终调用的还是AssetManager中的私有方法。

    @NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                + Integer.toHexString(id));
    }

    public AssetManager getAssets() {
        return mAssets;
    }

而AssetsManager 直接对接Android系统底层。
Assets Manager有一个方法:addAssetPath(String path) 方法,app启动的时候会把当前的APK路径传递进去,然后我们就可以访问资源了。

我们可以通过反射获取到addAssetPath方法,并且将插件APK路径传递进去。 apk插件有几个,我们就调用几次addAssetPath,把插件的资源都放入到一个资源池中
PS: 资源池也就是:resources.arsc 文件,它是一个Hash表,存放着十六进制和资源的对应关系。

**

调用插件的资源文件

**
首先建议先看下怎样加载插件APK:
Android 插件化开发——宿主APP加载APK插件
我们接着添加代码:首先在插件项目MyPluginProject中创建一个类:

package com.liumengqiang.mypluginproject;

import android.content.Context;

import com.liumengqiang.interfacelibrary.IBaseInterface;

/**
 * author: liumengqiang
 * Date : 2019/9/13
 * Description :
 */
public class TestResource implements IBaseInterface {
    @Override
    public String getStringForResId(Context context) {
        return context.getResources().getString(R.string.test_string);
    }

    @Override
    public void setName(String name) {

    }

    @Override
    public String getName() {
        return null;
    }
}

public interface IBaseInterface {
    void setName(String name);

    String getName();

    String getStringForResId(Context context);
}

然后打包(“app-debug.apk“), 将该APK还是复制到宿主项目的assets目录下:
先将APK加载到存储内存

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        //将Assets内的资源写入到data/data/files文件夹下
        Utils.extractAssets(this, "app-debug.apk");
    }

获取插件路径以及创建DexClassLoader

File fileStreamPath = this.getFileStreamPath(“app-debug.apk”);
        String dexPath = fileStreamPath.getPath();
        File fileRelease = getDir("dex", 0);//0 Context.MODE_PRIVATE;
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath, fileRelease.getAbsolutePath(), null, getClassLoader());

这样就获得了dexPath 以及 DexClassLoader;
创建属于该插件的AsseteManager以及Resources

   /**
     *
     * @param dexPath
     */
    protected void loadResource(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            this.assetManager = assetManager;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
        theme = resources.newTheme();
        theme.setTo(super.getTheme());
    }

在要是用插件资源的Activity中,修改Theme以及Resource

   @Override
    public Resources getResources() {
        if(resources == null) {
            return super.getResources();
        }
        return resources;
    }

    //设置资源
    @Override
    public AssetManager getAssets() {
        if(assetManager == null) {
            return super.getAssets();
        }
        return assetManager;
    }

    //设置主题
    @Override
    public Resources.Theme getTheme() {
        if(theme == null) {
            return super.getTheme();
        }
        return theme;
    }

整体的代码就是:

package com.liumengqiang.testdemoproject;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import com.liumengqiang.interfacelibrary.IBaseInterface;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class TestActivity extends BaseActivity {

    private TextView textView;


    public AssetManager assetManager;
    protected Resources resources;
    protected Resources.Theme theme;

    protected DexClassLoader  dexClassLoader;

    protected String dexPath;

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        //将Assets内的资源写入到data/data/files文件夹下
        Utils.extractAssets(this, "app-debug.apk");
    }

    @RequiresApi(api = Build.VERSION_CODES.CUPCAKE)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        textView = findViewById(R.id.text_view);

        addPluginDex("app-debug.apk");

        loadResource();
        try {
            Class aClass = dexClassLoader.loadClass("com.liumengqiang.mypluginproject.TestResource");
            Object newInstance = aClass.newInstance();
            IBaseInterface iBaseInterface = (IBaseInterface) newInstance;
            textView.setText(iBaseInterface.getStringForResId(TestActivity.this) + " = ");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //加载插件获取DexClassLoader
    protected void addPluginDex(String pluginName) {
        File fileStreamPath = this.getFileStreamPath(pluginName);
        dexPath = fileStreamPath.getPath();
        File fileRelease = getDir("dex", 0);//0 Context.MODE_PRIVATE;
        dexClassLoader = new DexClassLoader(dexPath, fileRelease.getAbsolutePath(), null, getClassLoader());

    }

    @Override
    public Resources getResources() {
        if(resources == null) {
            return super.getResources();
        }
        return resources;
    }

    //设置资源
    @Override
    public AssetManager getAssets() {
        if(assetManager == null) {
            return super.getAssets();
        }
        return assetManager;
    }

    //设置主题
    @Override
    public Resources.Theme getTheme() {
        if(theme == null) {
            return super.getTheme();
        }
        return theme;
    }


    /**
     *
     * @param dexPath
     */
    protected void loadResource() {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            this.assetManager = assetManager;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
        theme = resources.newTheme();
        theme.setTo(super.getTheme());
    }

}

由于我们可能会有很多插件,因此我们可以这样,为了方便,我们code review,在基类的BaseActivity中创建一个HashMap, key为:插件名称, value为对象PluginInfo, 这个对象PluginInfo有两个成员变量:插件APP的路径以及对应的DexClassLoader。

package com.liumengqiang.testdemoproject;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import com.liumengqiang.testdemoproject.proxy.hook.HookInstrumentation;
import com.liumengqiang.testdemoproject.proxy.hook.ReflexUtil;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

import dalvik.system.DexClassLoader;

/**
 * author: liumengqiang
 * Date : 2019/6/21
 * Description :
 */
public class BaseActivity extends AppCompatActivity {


    public AssetManager assetManager;
    protected Resources resources;
    protected Resources.Theme theme;

//    protected DexClassLoader  dexClassLoader;

//    protected String dexPath;

    protected HashMap pluginMap = new HashMap<>();

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        //将Assets内的资源写入到data/data/files文件夹下
        Utils.extractAssets(this, "app-debug.apk");
    }

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


        addPluginDex("app-debug.apk");
    }

    //加载插件获取DexClassLoader
    protected void addPluginDex(String pluginName) {
        File fileStreamPath = this.getFileStreamPath(pluginName);
        String dexPath = fileStreamPath.getPath();
        File fileRelease = getDir("dex", 0);//0 Context.MODE_PRIVATE;
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath, fileRelease.getAbsolutePath(), null, getClassLoader());
        //加入HashMap
        pluginMap.put(pluginName, new PluginInfo(dexPath, dexClassLoader));
    }

    @Override
    public Resources getResources() {
        if(resources == null) {
            return super.getResources();
        }
        return resources;
    }

    //设置资源
    @Override
    public AssetManager getAssets() {
        if(assetManager == null) {
            return super.getAssets();
        }
        return assetManager;
    }

    //设置主题
    @Override
    public Resources.Theme getTheme() {
        if(theme == null) {
            return super.getTheme();
        }
        return theme;
    }

    /**
     *
     * @param dexPath
     */
    protected void loadResource(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            this.assetManager = assetManager;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
        theme = resources.newTheme();
        theme.setTo(super.getTheme());
    }
}

package com.liumengqiang.testdemoproject;

import dalvik.system.DexClassLoader;

/**
 * author: liumengqiang
 * Date : 2019/9/13
 * Description :
 */
public class PluginInfo {
    private String dexPath;

    DexClassLoader dexClassLoader;

    public PluginInfo(String dexPath, DexClassLoader dexClassLoader) {
        this.dexPath = dexPath;
        this.dexClassLoader = dexClassLoader;
    }

    public String getDexPath() {
        return dexPath;
    }

    public void setDexPath(String dexPath) {
        this.dexPath = dexPath;
    }

    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }

    public void setDexClassLoader(DexClassLoader dexClassLoader) {
        this.dexClassLoader = dexClassLoader;
    }
}

到这里,插件化的学习已经进行了一半了,说实话,学习插件化,不是因为我要学会插件化,而是插件化到底是是什么? 到底是什么知识点支撑了插件化技术? 这些知识点是怎么组合成了插件化技术? 这才是我学习插件化的原因。

Android开发交流群:

Android 插件化开发——获取插件中的资源_第1张图片

你可能感兴趣的:(Android插件化开发,android开发)