目录
1、名词解释
(1)skin:皮肤
应用程序主题,整体风格
(2)onlineRes:线上资源文件(onlineSkin对应的资源)
程序正在使用的皮肤使用的资源文件
(3)migrateRes:迁移的资源(migrateSkin对应的资源)
程序即将使用的新皮肤使用的资源文件
(4)色板:一套皮肤对应的基本色
2、 背景
2.1 换肤面临的问题
在业务的发展过程中,App存在整体换肤的需求。如果我们对换肤和资源没有实现良好的管理,则会导致应用程序使用大量不可管理的资源。从而造成以下三个问题:
- 即使新皮肤完全替代了老皮肤,由于业务代码里面不规范使用了资源文件,onlineRes无法被清除
- onLineRes和migrateRes混在一起,随着换肤越来越多,造成资源文件膨胀无法管理
- 业务逻辑里面直接使用资源的引用,导致每次换肤需要修改大量的业务代码(与换肤的定位不符合)
2.2 换肤的目标
- 支持不修改业务代码换肤:不修改业务代码,仅仅通过修改编译时候指向资源文件的位置,实现换肤
- 支持逐步换肤:支持按页面逐步换肤,逐步实现整个app的换肤(提前换肤的功能的上线时间)
- 支持老皮肤资源删除:换肤之后,老的皮肤资源文件可完全删除
- 支持皮肤升级和降级:换肤之后程序支持在新老皮肤之间切换,从而实现在皮肤的
- 支持不同的分支使用不同的皮肤:对于A分支使用皮肤A,B分支使用皮肤B,他们包含的资源文件可能不完全相同,但是分支A和分支B均能编译通过
2.3 换肤的难点
(1)难点1:资源ID的管理
为了实现【目标1:支持不修改业务代码换肤】和【目标2:支持逐步换肤】,业务代码里面是要满足一下功能:
- 业务代码里面使用的资源文件在新皮肤和老皮肤里面的的ID相同
- 在逐步换肤的过程中onlineRes和migrateRes同时存在于项目之中
- 对于相同的Res,业务代码需要根据不同的情况使用onlineRes和migrateRes里面的资源
(2)难点2:迁移逻辑的管理
为了实现【目标2:支持逐步换肤】,【目标3:支持老皮肤资源删】和【目标4:支持皮肤升级和降级】,业务代码需要满足以下功能:
- 根据是否IF_MIGRATE的变量,切换到不同的UI实现的逻辑,并且在业务里面里面尽可能的减少IF_MIGRATE的使用
(3)难点3:不同flavor使用资源不同,但是编译能通过
为了实现【目标5:支持不同的分支使用不同的皮肤】,业务代码需要保证:
- 保证不同分支使用资源不完全相同的情况下,能够编译通过
3、实现方案
为了满足以上目标和解决以上的两个难点,设计了如下的皮肤架构的组织方案。其由如下几个部分组织起来
- Res-Placehoder:资源占位符
- Skin:皮肤
- 模块:一个业务模块
- 产品:不同的产品由不同的模块组织起来
迁移完成前架构:
迁移完成以后架构:
3.1 Res-Placeholder:资源的占位符
(1)作用
保证不同flavor使用资源不一致也能编译通过
(2)组成部分
- 可通过id覆盖的资源:color,drawable,theme,等等。实现机制参见【3.2:色板】
- 不能通过id覆盖的资源:font
font采用的是assets存储的,Android在编译的时候,不能同时存在两个文件名相同的assets,因此不能才资源覆盖的形式实现。
因此,在online-skin里面添加了font1,那么在migrate-skin里面也要添加font1才能保证代码在使用online-skin和migrate-skin的时候都能编译通过
(3)资源赋值
均为假值,而且明显是假的,保证编译之后立刻能发现
3.2 skin
每一套皮肤均有自己的实现,皮肤之间的相同定位的资源id相同,且实现对UIlib里面资源占位符的覆盖。皮肤由以下几个部分组成:
- 色板:整套皮肤的主体色(不同皮肤的色板id均相同),详情参见下文【色板】
- 皮肤内,跨业务使用的资源:如图片的占位符
举个例子:在DetailModule和FeedModule两个业务场景下,对于migrateSkin,其使用的图片的占位符image_placeholder.drawable应该是相同的,因此其应该放在该migrateSkin的drawable文件下面
3.2.1 色板
一套皮肤里面包含的主体色,每个颜色都有其对应的功能。比如说online-skin里面包含的色板如下:
在migrate-skin里面包含的色板如下:
其中,c0,c1,c2,c3代表的含义均相同,只是在不同的皮肤里面取值不同。
因此在UIlib里面,其需要有如下的color占位符,用于保证其他的分支即使不使用这个资源依然能够编译通过。(eg:即使<online-skin,flavor1>在代码里面不使用b1的颜色,但是仍然需要在UIlib里面添加b1,用户保证<online-skin,flavor1>代码能够编译通过)。在UIlib里面添加资源占位符的标准有以下两个:
(1)公共色板包含的id(理论上所有公共色板包含的id均相同),此条件可以考虑弱化,后期可考虑删除此条件
(2)该皮肤特有的资源,并且在代码里面直接有该资源id的引用
3.3 模块
每一个具体的业务,如feed,detail等。每一个业务单独的管理其资源和代码文件,因此其有以下两部分组成:code和res,
在迁移的过程中(<online-skin→migrate-skin>),该module使用的资源由以下三部分组成:
- online res
- migrate res
- module res
在迁移完成之后(<migrate-skin>),该module使用的资源,由以下两部分组成
- migrate res
- module res
切记不在module里面放访问其他module申明的资源和其他皮肤定义的资源(eg:在feed module使用online-skin的之后,只能访问online-skin和feed module里面的代码)
3.3.1 模块代码实践
为了避免代码中反复出现if(migrate)的语句,我们将所有的资源管理在ResHelper里面,有ResHelper负责资源的加载:
- AbsResourceHepler:全局资源的加载
- FeedResourceHelper:业务相关资源的加载
public class AbsResourceHepler {
protected static int pickIconInDifferentStyle(int styleA, int styleB) {
if (ApplicationVariables.useVenusStyle) {
return styleB;
}
return styleA;
}
}
public static int getImageCoverPlaceHolderRes() {
return pickIconInDifferentStyle(R.drawable.simple_image_holder_listpage, R.color.C4_test);
}
3.4 产品
不同的模块组装起来就成了不同的产品,每一个产品由以下两个维度进行定义<module,skin>
- module:包含的模块,所拥有的功能
- skin:使用的皮肤,功能的展示样子