iOS拓展---【轉載】iOS客戶端節日換膚方案探究

[轉載]iOS客戶端節日換膚方案探究

1、前言:html

Tip: 原本這篇文章在聖誕節就已經準備好了,可是因爲種種緣由一直沒有寫完,今天將它寫出來,也算是2018年的第一篇文章了。你好,2018!ios

過去聖誕節是各大APP濃妝豔抹展示本身衣服的節日,今年的聖誕節彷佛冷清了許多,只看到了幾個APP換膚,那我就從中分析一下吧。git

2、分析:github

我認爲目前的換膚主要分紅3種,一種是返回圖片的地址,APP再根據圖片日誌去取圖片,另外一種是下載zip包而後再解壓去替換圖標,再一種是圖片資源放到包裏,接口控制是否顯示。web

2.1 實現方式一:json

我發現河狸家就是這個方式,爲何先以河狸家來舉例呢?由於朋友說它太炫酷了!因而我就從它開始分析了。緩存

我已經用越獄手機查看了河狸家APP的沙盒,並無發現本地存儲有皮膚文件。sass

因而我開始用Charles進行抓包,我在這個接口發現了疑似皮膚文件的配置信息。網絡

如圖:框架

因而我從img前綴的域名的中發下了請求到的皮膚文件,如圖,這正是tabbar的背景圖片。

值得稱讚的是河狸家的png圖片通過了webp壓縮,這也是目前APP端主流的一個圖片格式。

因此河狸家的方案是接口返回了皮膚的配置信息,配置信息中存有圖片的地址信息,而後經過圖片緩存框架去拿到圖片的。

這種狀況我認爲必定作一下處理,讓全部圖片都緩存完畢後,再顯示,否則可能會出現圖片一個個閃現出來的狀況,甚至於在網絡很差的狀況下,某個圖片顯示不出來的狀況。這個狀況我再另外一個APP上見過,具體哪一個我給忘記了(測試了好多APP,實在記不清了。。。)

2.2 實現方式二:

這裏我以微店買家版進行一個舉例,如圖這是微店買家版聖誕節皮膚。

我一樣是在安裝APP後先看沙盒裏是否有皮膚文件,一樣並無發現。下面直接去抓接口,我在assets的域名上發現了可疑的zip文件包。

如圖:

解壓這個zip文件後,發現了tabbar的圖片資源。我一樣在程序的沙盒裏面發現了一樣的文件。

如圖:

圖片資源拿到了,那麼它們是如何替換的呢?我就以微店買家版進行舉例來看一下。

我拿到微店買家版ipa脫殼後,我分別使用 Hopper Disassembler 和 class-dump 對主程序進行分析。最後發現以下信息:

從中能夠看出它是使用的Category和KVO去實現了替換皮膚的過程。給UIButton等系統類添加一個Category,添加了設置皮膚的方法,經過KVO去實現了觸發控制。

另外這裏建議皮膚下載完成以後能夠去當即觸發換膚,我在測試百度糯米APP的時候發現它是第二次啓動的時候纔去替換,可能由於它是高頻APP吧。

2.3 實現方式三:

這種方式我測試的幾個APP中沒有發現,聽朋友說某註明APP曾經就採用過這個方式。這種方式是在發版前將皮膚文件存儲到包內,經過後臺接口控制去顯示。這種狀況的優勢是便於控制,故障率小。缺點是包的體積過大,而且嚴重依賴於蘋果爸爸的審覈。

 

3、個人實現方式:

最近我也作了皮膚相關的功能,下面我說一下個人實現思路。

先上圖,看一下個人APP控制邏輯。

個人實現思路相似微店的實現方式。可是我並無使用KVO而是使用了通知註冊的方式。

APP啓動後直接加載對應的皮膚文件,同時另外一個線程去請求後臺皮膚接口,接口返回了一個zip包的連接,下載zip包,解壓後,解析裏面的config.json文件,而後我使用通知的方式去觸發換膚。具體的思路邏輯相信流程圖上已經畫的很清楚了。

控制皮膚是否顯示的邏輯徹底由後臺控制,後臺返回skinSign爲空則關閉皮膚。

下面看一下個人config.json文件的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
     "home_navi" : {
         "colors" : {
             "color_background" "#ffffff"
         },
         "images" : {
             "image_logo" "home_topLogo"
         }
     },
     "home_tabbar" : {
         "colors" : {
             "color_background" "#F9F9F9" ,
             "color_button_normal" "#999999" ,
             "color_button_selected" "#444444"
         },
         "images" : {
             "image_one_button_normal" "tab按鈕1圖片" ,
             "image_one_button_selected" "tab按鈕1選中圖片" ,
             "image_two_button_normal" "tab按鈕2圖片" ,
             "image_two_button_selected" "tab按鈕2選中圖片" ,
             "image_three_button_normal" "tab按鈕2圖片" ,
             "image_three_button_selected" "tab按鈕2選中圖片"
         },
         "values" : {
             "value_one_button" "tab按鈕1" ,
             "value_two_button" "tab按鈕2" ,
             "value_three_button" "tab按鈕3"
         }
     },
     "loading" : {
         "resources" : {
             "resource_refreshImage"  "refresh.gif"
        
     }
}

配置文件中,分爲首頁導航(home_navi)、首頁tabbar(home_tabbar)、加載loading(loading)三個業務模塊。在每一個業務模塊下均可以有4個功能模塊分別是顏色(colors)、圖片(images)、值(values)、資源(resources),這4個模塊根據本身的須要進行添加。colors控制的是顏色,這裏我以16進制值爲準。images控制的是圖片,最普通的png文件。values控制的是值。resources控制的是資源文件,例如json、gif等文件。

我建立了一個UIView的Category,在這個Category中我加了一個方法,以下:

1
- ( void )configSkinMapModule:(NSString *)module skinMap:(NSDictionary *)skinMap;

假設我須要給導航欄添加換膚的功能,我只須要加上以下代碼:

1
2
3
4
5
6
7
     [_tabbarButton configSkinMapModule:kSkin_MODULE_HOME_TABBAR skinMap:
      @{kSkinMapKey_button_image : @ "image_one_button_normal" ,
        kSkinMapKey_button_selectedImage : @ "image_one_button_selected" ,
        kSkinMapKey_button_titleColor : @ "color_button_normal" ,
        kSkinMapKey_button_titleSelectedColor : @ "color_button_selected" ,
        kSkinMapKey_button_title : @ "value_one_button"
        }];

我會建立一個SkinConstants文件去定義一下,替換的方式標識。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// button相關
static  NSString *  const  kSkinMapKey_button_image = @ "kSkinMapKey_button_image" ;
static  NSString *  const  kSkinMapKey_button_highlightedImage = @ "kSkinMapKey_button_highlightedImage" ;
static  NSString *  const  kSkinMapKey_button_selectedImage = @ "kSkinMapKey_button_selectedImage" ;
static  NSString *  const  kSkinMapKey_button_disabledImage = @ "kSkinMapKey_button_disabledImage" ;
static  NSString *  const  kSkinMapKey_button_titleColor = @ "kSkinMapKey_button_titleColor" ;
static  NSString *  const  kSkinMapKey_button_titleHighlightedColor = @ "kSkinMapKey_button_titleHighlightedColor" ;
static  NSString *  const  kSkinMapKey_button_titleSelectedColor = @ "kSkinMapKey_button_titleSelectedColor" ;
static  NSString *  const  kSkinMapKey_button_titleDisabledColor = @ "kSkinMapKey_button_titleDisabledColor" ;
static  NSString *  const  kSkinMapKey_button_title = @ "kSkinMapKey_button_title" ;
 
// label相關
static  NSString *  const  kSkinMapKey_label_text = @ "kSkinMapKey_label_text" ;
static  NSString *  const  kSkinMapKey_label_textColor = @ "kSkinMapKey_label_textColor" ;
static  NSString *  const  kSkinMapKey_label_backgroundColor = @ "kSkinMapKey_label_backgroundColor" ;
 
// imageview相關
static  NSString *  const  kSkinMapKey_imageView_image = @ "kSkinMapKey_imageView_image" ;
static  NSString *  const  kSkinMapKey_imageView_gif = @ "kSkinMapKey_imageView_gif" // gif動畫
static  NSString *  const  kSkinMapKey_imageView_backgroundColor = @ "kSkinMapKey_imageView_backgroundColor" ;

相信從名字大家就能看出來,每個定義都是UIKit裏面的一個方法。

而後我說一下剛纔那個Category中加的方法,其中module對應的正是config.json中的業務模塊,例如home_navi。skinMap中的key是替換的方式標識正是SkinConstants中的定義,value則是config.json中的對應的模塊的key值。

也就是上面加的方法的意思是給這個home_navi業務模塊中的某一個button增長了修改普通模式圖片(kSkinMapKey_button_image)、修改選中模式圖片(kSkinMapKey_button_selectedImage)、普通模式文字顏色(kSkinMapKey_button_titleColor)、修改選中模式圖片(kSkinMapKey_button_selectedImage)、修改文字值(kSkinMapKey_button_title)的功能。

咱們在通知觸發方法中使用以下代碼去執行替換過程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- ( void )changeSkin
{
     NSDictionary *map = self.skinMap;
     if  ([self isKindOfClass:[UIButton  class ]]) {
         UIButton *obj = (UIButton *)self;
         if  (map[kSkinMapKey_button_image]) {
             [obj setImage:SkinImage(map[kSkinMapKey_button_image]) forState:UIControlStateNormal];
         }
         if  (map[kSkinMapKey_button_highlightedImage]) {
             [obj setImage:SkinImage(map[kSkinMapKey_button_highlightedImage]) forState:UIControlStateHighlighted];
         }
         if  (map[kSkinMapKey_button_selectedImage]) {
             [obj setImage:SkinImage(map[kSkinMapKey_button_selectedImage]) forState:UIControlStateSelected];
         }
         if  (map[kSkinMapKey_button_disabledImage]) {
             [obj setImage:SkinImage(map[kSkinMapKey_button_disabledImage]) forState:UIControlStateDisabled];
         }
         if  (map[kSkinMapKey_button_titleColor]) {
             [obj setTitleColor:SkinColor(map[kSkinMapKey_button_titleColor]) forState:UIControlStateNormal];
         }
       ...如下省略...
}

同時我本地會存有一個localConfig.json用於管理本地的須要替換皮膚的模塊,內容和config.json如出一轍。只是他取的都是本地默認的皮膚資源配置。

SkinImage是處理images模塊的,這個宏定義是pngResourceForSign:方法的宏,用於去處理該加載哪一個圖片文件。

關於colors、resources等其餘模塊我就不一一介紹了,都是大同小異。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 獲取Png資源
- (UIImage *)pngResourceForSign:(NSString *)sign;
{
     NSArray *array = [sign componentsSeparatedByString:@ "." ];
     NSString *module = array.firstObject;
     NSString *key = array.lastObject;
     NSDictionary *moduleDic = self.configData[module];
     NSDictionary *imageDic = moduleDic[@ "images" ];
     NSString *value = imageDic[key];
     // 這裏已經在初始化的時候作了判斷,self.path有值則爲後臺皮膚,無值則爲本地默認皮膚。
     if  (!self.path.length) {
         return  [UIImage imageNamed:value];
     }
     NSString *filePath = [self.path stringByAppendingFormat:@ "/%@" ,value];
     UIImage *image = [UIImage imageWithContentsOfFile:filePath];
     return  image;
}

上面的例子就是_tabbarButton執行configSkinMapModule:skinMap:方法註冊了一個通知,判斷後臺是否啓用換膚,啓動換膚則加載config.json文件,沒有則加載localConfig.json本地默認皮膚。

以上就是我實現換膚方式的一個思路。

4、總結:

以上各類實現方式都各有各的好處,個人實現方式也有須要優化的地方,例如能夠在後臺接口上加入時間控制,能夠實現提早的緩存方案,而沒必要每次都是在用戶眼皮底下換。若是你有更好的實現方案歡迎一塊兒交流。

參考資料:

  1. github·ThemeManager

  2. github·SwiftTheme

  3. iOS換膚方案

  4. github·EasyTheme

  5. 「節日換膚」通用技術方案__iOS端實現 

相關文章
相關標籤/搜索