插件换肤的重点在于如何加载插件包的资源。
插件包其实就是一个apk的压缩包,加载插件包的资源,其实就是加载apk的资源。
如何才能加载apk中的资源呢?
首先android中的各种资源都对应一个唯一id,比如获取一个view,通过findViewById(int id)方法,就可以得到布局的唯一view。
同样,
设置图片
setBackground(getResources().getDrawable(id))
设置颜色
setTextColor(getResources().getColor(id))
唯一的id值怎么获得呢?
id可以通过Resources下的getIdentifier方法根据icon的名字、view设置的id名、color的名字等得到:
public int getIdentifier(String name, String defType, String defPackage) {
return mResourcesImpl.getIdentifier(name, defType, defPackage);
}
所以获取插件包的资源的核心就在:Resources,如果可以获取插件包的Resources,就可以根据名字,获取插件包的资源了。
如何获取插件包的Resources?
/**
* Create a new Resources object on top of an existing set of assets in an
* AssetManager.
*
* @deprecated Resources should not be constructed by apps.
* See {@link android.content.Context#createConfigurationContext(Configuration)}.
*
* @param assets Previously created AssetManager.
* @param metrics Current display metrics to consider when
* selecting/computing resource values.
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
而创建Resources的关键在于
Create a new Resources object on top of an existing set of assets in an AssetManager.
通过AssetManager最新的设置创建一个新的Resouces对象
AssetManager的最新设置是什么呢?
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
这个设置可以是一个文件或者压缩包路径,即我们的插件包。
通过上面的分析,接下来实现插件换肤
/**
* 加载皮肤
*/
public void loadSkin(){
boolean grant = ClientUtils.addPermission(READ_EXTERNAL_STORAGE, context);
if(!grant){
Looklog.i(TAG, "loadSkin error: 无sd卡读取权限");
//如果是外部目录,需要判断权限,否则crash
return;
}
//加载皮肤
new Thread(new Runnable() {
@Override
public void run() {
//本地存储的路径
String skinPkgPath = Local.getDVStringData(SKINPKGPATH);
//插件名
String skinName = "skin";
//缓存path
String cachePath = context.getCacheDir() + "/" + skinName;
boolean needCopy = true;
if(!TextUtils.isEmpty(skinPkgPath)){
if(skinPkgPath.equals(cachePath)){
File file = new File(skinPkgPath);
if(file != null){
if(file.exists()){
needCopy = false;
}
}
}
}
//如果缓存为空,copy
if(needCopy){
Looklog.i(TAG,"无skin缓存,需要拷贝 " + skinPkgPath + " cachePath:" + cachePath);
skinPkgPath = copySkinToCache(skinName);
Looklog.i(TAG,"无skin缓存,需要拷贝 拷贝成功>>>>:" + skinPkgPath);
}else {
Looklog.i(TAG, "有skin缓存,直接使用 " + skinPkgPath);
}
//判断是否copy成功
if(TextUtils.isEmpty(skinPkgPath)){
return;
}
//判断路径合法性
File file = new File(skinPkgPath);
if ((file == null) || !file.exists()) {
return;
}
//读取
PackageManager mPm = context.getPackageManager();
PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
if(mInfo == null)return;
skinPackageName = mInfo.packageName;
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);
Resources superRes = context.getResources();
Resources skinResource = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
if(skinResource != null){
isDefaultSkin = false;
skinResources = skinResource;
clz = skinResources.getClass();
skinVersionCode = mInfo.versionCode;
skinVersionName = mInfo.versionName;
skinLoaded = true;
Looklog.i(TAG, "Skin versionCode:" + skinVersionCode + " versionName:" + skinVersionName + " clz:" + clz + " packageName:" + skinPackageName + " path:" + skinPkgPath);
saveSkinData(skinVersionName, skinVersionCode, skinPkgPath);
}
} catch (Exception e) {
e.printStackTrace();
Looklog.i(TAG, "Skin load error " + e.getMessage());
}
}
}).start();
}
上面的skin路径,是由于将skin的插件包放在了assets目录下,如果是sd卡目录下,修改对应路径即可。
加载assets文件的话,需要先将assets路径下的skin插件包拷贝到缓存目录。
private String copySkinToCache(String fileName) {
File cacheFile = new File(context.getCacheDir(), fileName);
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = context.getAssets().open(fileName);
outputStream = new FileOutputStream(cacheFile);
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
} catch (IOException e) {
cacheFile = null;
e.printStackTrace();
}finally {
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
outputStream = null;
}
if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
inputStream = null;
}
}
if(cacheFile == null){
return "";
}
return cacheFile.getAbsolutePath();
}
定义获取color或drawable的方法:
private int getIdentifier(String name, String defType) {
int id = skinResources.getIdentifier(name, defType, skinPackageName);
if(id <= 0){
id = appResources.getIdentifier(name, defType, appPackageName);
}
return id;
}
private int getColorId(String name){
return getIdentifier(name, "color");
}
private int getDrawableId(String name){
return getIdentifier(name, "drawable");
}
public int getColor(String name){
int id = getColorId(name);
int color = getColor(id, skinResources);
if(color == -1){
color = getColor(id, appResources);
}
if(color == -1){
color = Color.WHITE;
}
return color;
}
public Drawable getDrawable(String name){
int id = getDrawableId(name);
Drawable drawable = getDrawble(id, skinResources);
if(drawable == null){
drawable = getDrawble(id, appResources);
}
if(drawable == null){
drawable = new Drawable() {
@Override
public void draw(@NonNull Canvas canvas) {
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSPARENT;
}
};
}
return drawable;
}
private Drawable getDrawble(int id, Resources resources){
try{
return resources.getDrawable(id);
} catch (Exception e){}
return null;
}
private int getColor(int id, Resources resources){
try {
return resources.getColor(id);
}catch (Exception e){}
return -1;
}
使用方式:
在values下新建theme_colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background_color_default">#ffffff</color>
</resources>
代码使用:
public int getBackgroundColor() {
return getColor("background_color_default");
}
只需要在插件包中添加一样的background_color_default,改成其他颜色
以上步骤即可实现插件换肤。