|
在前面的文章中我们成功的加载了外部的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
基于这个思路,我们可以尝试使用插件资源替换当前显示的内容,实现换肤效果,核心思想是一样的,这里就不过多赘述了。
来源:https://www.cnblogs.com/renhui/p/11907493.html |