文章目录
- 准备知识:
- 1、方案一
- 1、方案二
在项目中可能会遇到需要在应用内动态或静态添加皮肤或多国文字切换等资源类修改需求
准备知识:
安卓的资源打包会生成一个resources.arsc文件,将生成的apk拖到android studio可以看到里面的内容(如下图,包含了包名、资源类型type、资源id、资源名称、资源的值等信息):
这些资源主要是由AssetManager这个类管理的,并且通过类Resources供外部使用的。
主要两个方案:
一、使用和当前应用相同的Resources,资源切换时通过切换资源名字实现
二、资源切换时,创建新的Resouces,并切换Resources,不改变资源名字实现
1、方案一
Resources不变,资源名字变
直接上代码:
//获取 <string name="hello_tw">繁体Hello World!</string> 并显示在textView上
String name1 = getResources().getResourceEntryName(R.string.hello);
String type1 = getResources().getResourceTypeName(R.string.hello);
int strIde= getResources().getIdentifier(name1+"_tw" , type1 , getPackageName());
textView.setText(strIde);
该方案可以实现该资源切换(如这里的多国语言切换)功能,但是缺点也是很明显的,无法动态的添加,而且增加一种语言就要手动增加资源名称。
1、方案二
Resources变,资源名字不变
直接上代码:
//这里的资源包可以放在一个apk里,如这里的skin1.apk,可以动态下载后加载,
//也可以在打包应用apk时将资源apk放在assets文件夹下,然后第一次切换资源时copy到app的cache文件夹下面
String path = getCacheDir()+"/skin1.apk";
try {
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
File skinFile = new File(path);
if(skinFile.exists()){
skinFile.delete();
}
skinFile.createNewFile();
if(skinFile.exists()){
inputStream = getResources().getAssets().open("skin1.apk");
outputStream = new FileOutputStream(skinFile);
byte[] readBytes = new byte[1024];
int readcount = 0;
while ( (readcount = inputStream.read(readBytes)) > 0){
outputStream.write(readBytes , 0 , readcount);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inputStream != null){
inputStream.close();
}
if(outputStream != null){
outputStream.close();
}
}
//通过反射实例化一个AssetManager 并添加资源文件路径
AssetManager assetManager = AssetManager.class.newInstance();
Method method = AssetManager.class.getMethod("addAssetPath" , String.class);
method.invoke(assetManager,path);
if(skinResources != null && skinResources.getAssets() != null){
skinResources.getAssets().close();
}
//实例化一个Resources供外部直接使用
//这里的DisplayMetrics和Configuration没有变化
skinResources = new Resources(assetManager , getResources().getDisplayMetrics() , getResources().getConfiguration());
PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(path , PackageManager.GET_ACTIVITIES);
pkgName = packageInfo.packageName;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
//切换资源(比如切换主题、语言等)后,获取资源方式
//获取新的资源包下 相同资源名称的文字
String name1 = getResources().getResourceEntryName(R.string.hello);
String type1 = getResources().getResourceTypeName(R.string.hello);
int resId = skinResources.getIdentifier(name1 , type1 , pkgName);
String strText = skinResources.getString(resId);
textView.setText(strText);
//获取新的资源包下 相同资源名称的颜色
ViewGroup viewGroup = (ViewGroup) v.getParent();
String name = MainActivity.this.getResources().getResourceEntryName(R.color.my_black);
String type = MainActivity.this.getResources().getResourceTypeName(R.color.my_black);
int resColor = skinResources.getIdentifier(name,type,pkgName);
viewGroup.setBackgroundColor(resColor);
上面是两种实现方案的demo部分代码,如果有相关需求,可以参考使用这两种方案实现(这里的代码只是为了演示,并不考虑设计上的合理性哈)。
这里多说几句,实现相关需求,肯定是需要动态监听当前资源是否切换,切换后怎么刷新,这些可以考虑使用LayoutInflater的setFactory2或setFactory参考源码(mLayoutInflater.createView(name, prefix, attrs);)拦截View的创建,并且过滤需要随资源切换变化的属性,使用观察者模式,实现自动刷新。