android 换肤框架详解2-LayoutInflater源码解析-CSDN博客
android 换肤框架详解3-自动换肤原理梳理-CSDN博客
- 换肤框架流程
1,通过AssetManager获取换肤的资源文件
2,通过原文件中的resId获取到res名称和res类型,比如resId未R.color.red,这里的名称就是red,类型就是color
3,在换肤的资源文件AssetManager中,用原文件的resId的res名称和res类型获取到换肤资源文件中的ResId,在通过AssetManager.getXXX拿到对应的资源
4,将拿到的资源文件部署到View
- 代码模块
1,创建资源包


由于资源包只需要里面的资源文件,多余的内容都可以删除到,这里只留如下内容

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" />
</manifest>
gradle中不需要其他的引用包
build.gradle.kts
plugins {
id("com.android.application")
}
android {
namespace = "com.kx.skin"
compileSdk = 33
defaultConfig {
applicationId = "com.kx.skin"
minSdk = 28
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
}
在原本的app和换肤资源的app中同时创建名称相同的资源


将编译好的skin apk放到assets目录下

-
创建资源文件的AssetManager
public static Resources getThemeResources(Context context) { try { //通过反射创建AssetManager
// AssetManager assetManager = AssetManager.class.newInstance();
// Method add = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
//通过反射加载路径
// int cookie = (int) add.invoke(assetManager, "/sdcard/skin/skin.skin");//将assets中的文件拷贝到自己私有的文件中 File skinFile = copyAssetToFiles(context, "skin-debug.apk", // assets 下的相对路径 "skin-debug.apk"); // 目标文件名 boolean exists = skinFile.exists(); // true 表示存在 Log.e(TAG, "加载文件 exists " + exists + " getAbsolutePath " + skinFile.getAbsolutePath()); // 创建资源Resources AssetManager assetManager = new AssetManager(); int cookie = assetManager.addAssetPath(skinFile.getAbsolutePath()); if (cookie == 0) { Log.e(TAG, "加载失败,路径无效或权限不足"); } Resources oldRes = context.getResources(); Resources newRes = new Resources(assetManager, oldRes.getDisplayMetrics(), oldRes.getConfiguration()); return newRes; } catch (Throwable e) { e.printStackTrace(); Log.d(TAG, "Throwable " + e); } return null; }
2,在换肤的资源文件AssetManager中,用原文件的resId的res名称和res类型获取到换肤资源文件中的ResId,在通过AssetManager.getXXX拿到对应的资源
public static int getThemeResourcesColorResId(Context context, int resId) {
Log.d(TAG, "getThemeResourcesColor resId " + resId);
Resources resources = context.getResources();
//包名:资源类型/资源名
// String resName = resources.getResourceName(resId);
//返回格式:ic_launcher(仅资源名)R.drawable.ic_launcher,R.color.ic_launcher
String resName = resources.getResourceEntryName(resId);
//资源类型类型drawable,color
String typeName = resources.getResourceTypeName(resId);
Log.d(TAG, "getThemeResourcesColor resName " + resName);
Log.d(TAG, "getThemeResourcesColor typeName " + typeName);
//获取主题资源文件
Resources newResources = getThemeResources(context);
//通过资源名称,获取到资源ID
int newResId = newResources.getIdentifier(
resName, // 资源名
typeName, // 资源类型
"com.kx.skin" // 应用包名
);
Log.d(TAG, "getThemeResourcesColor newResId " + newResId);
//通过资源ID,获取到对应资源的值
int newColorResId = newResources.getColor(newResId, null);
Log.d(TAG, "getThemeResourcesColor newColorResId " + newColorResId);
return newColorResId;
}
代码调用
int resId = ThemeModeChange.getThemeResourcesColorResId(
ThemeActivity.this,
R.color.content
);
tv_content.setBackgroundColor(resId);