主要流程
- 生成一个皮肤包(一个只有资源的apk)
- 原始app通过AssetManager#addAssetPath加载皮肤包,生成皮肤包Resource
- 查找对应的哪些资源进行了替换,先根据资源id通过原始app的resource找到资源的名字,然后根据此名字去找皮肤包中resource的资源id然后进行加载
- 对要进行换肤的控件设置tag,并添加LayoutInflater .setFactory2对这些需要换肤的控件进行缓存,当要换肤的时候将先通过皮肤包的Resource进行加载资源,找不到再通过原始包进行加载
1. 生成皮肤包
没啥说的,主要是要和原始app中的资源id一一对应,并且生成一个特殊的包名
2. 原始app通过AssetManager加载皮肤包
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getDeclaredMethod(ADD_ASSET_PATH, String.class);
addAssetPath.setAccessible(true);
addAssetPath.invoke(assetManager, skinPath);
skinResources = new Resources(assetManager,
appResources.getDisplayMetrics(), appResources.getConfiguration());
// 获取资源包的包名
skinPackageName = application.getPackageManager()
.getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES).packageName;
3. 根据原始app的资源ID查找皮肤包中的资源ID
String resourceName = appResources.getResourceEntryName(resourceId);
String resourceType = appResources.getResourceTypeName(resourceId);
// 根据皮肤包的包名和资源名字类型获取资源包中的资源id
int skinResourceId = skinResources.getIdentifier(resourceName, resourceType, skinPackageName);
// 如果找不到就还是使用原始app中的资源
isDefaultSkin = skinResourceId == 0;
return skinResourceId == 0 ? resourceId : skinResourceId;
4. 对要进行换肤的控件设置tag,并添加LayoutInflater.setFactory2
设置tag就是设置一个特殊的值 我们都知道界面从布局xml创建view的时候,走的是LayoutInflater中的方法,其中里面会优先走mFactory2的onCreateView方法,那么我们只要在setContentView之前设置了LayoutInfater就能将所有的View的创建都缓存下来,并且将View的属性缓存起来,比如background, textColor,src等,那么我们在变肤的时候遍历这些View将之前的属性也都遍历一遍查看皮肤包中的resource是否包含这些资源,然后判断进行加载