之前做的项目是RN的项目,最近在将之前的项目用Android组件化重构一遍 + 白天还得工作。真的是忙的要命,再加上自己十一点之前肯定上床睡觉的“坏习惯”, 没办法只能在中秋节的今天来继续学习插件化。 祝各位小伙伴:中秋快乐!
之前学习了宿主APP如何加载插件APK,本篇博客学习:宿主APP加载插件APK的资源。
因为我们平时用Activity的居多,并且每个Activity都会调用各种资源,因此学习本篇博客还是很有必要的。
Android中的资源分为两类:
关于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开发交流群: