Android程序换肤架构

 

目录

1、名词解释

2、 背景

2.1 换肤面临的问题

2.2 换肤的目标

2.3 换肤的难点

3、实现方案

3.1 Res-Placeholder:资源的占位符

3.2 skin

3.3 模块

3.4 产品


1、名词解释

(1)skin:皮肤

应用程序主题,整体风格

(2)onlineRes:线上资源文件(onlineSkin对应的资源)

程序正在使用的皮肤使用的资源文件

(3)migrateRes:迁移的资源(migrateSkin对应的资源)

程序即将使用的新皮肤使用的资源文件

(4)色板:一套皮肤对应的基本色

2、 背景

2.1 换肤面临的问题

在业务的发展过程中,App存在整体换肤的需求。如果我们对换肤和资源没有实现良好的管理,则会导致应用程序使用大量不可管理的资源。从而造成以下三个问题:

  • 即使新皮肤完全替代了老皮肤,由于业务代码里面不规范使用了资源文件,onlineRes无法被清除
  • onLineRes和migrateRes混在一起,随着换肤越来越多,造成资源文件膨胀无法管理
  • 业务逻辑里面直接使用资源的引用,导致每次换肤需要修改大量的业务代码(与换肤的定位不符合)

2.2 换肤的目标

  1. 支持不修改业务代码换肤:不修改业务代码,仅仅通过修改编译时候指向资源文件的位置,实现换肤
  2. 支持逐步换肤:支持按页面逐步换肤,逐步实现整个app的换肤(提前换肤的功能的上线时间)
  3. 支持老皮肤资源删除:换肤之后,老的皮肤资源文件可完全删除
  4. 支持皮肤升级和降级:换肤之后程序支持在新老皮肤之间切换,从而实现在皮肤的
  5. 支持不同的分支使用不同的皮肤:对于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)组成部分

  1. 可通过id覆盖的资源:color,drawable,theme,等等。实现机制参见【3.2:色板】
  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:使用的皮肤,功能的展示样子

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章