iOS客户端节日换肤方案探究

转自:https://www.ianisme.com的博客

一、前言:

tip: 本来这篇文章在圣诞节就已经准备好了,但是由于种种原因一直没有写完,今天将它写出来,也算是2018年的第一篇文章了。你好,2018!

过去圣诞节是各大APP浓妆艳抹展现自己衣服的节日,今年的圣诞节似乎冷清了许多,只看到了几个APP换肤,那我就从中分析一下吧。

二、分析:

我认为目前的换肤主要分成3种,一种是返回图片的地址,APP再根据图片日志去取图片,另一种是下载zip包然后再解压去替换图标,再一种是图片资源放到包里,接口控制是否显示。

2.1 实现方式一:

我发现河狸家就是这个方式,为什么先以河狸家来举例呢?因为朋友说它太炫酷了!于是我就从它开始分析了。

我已经用越狱手机查看了河狸家APP的沙盒,并没有发现本地存储有皮肤文件。

于是我开始用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曾经就采用过这个方式。这种方式是在发版前将皮肤文件存储到包内,通过后台接口控制去显示。这种情况的优点是便于控制,故障率小。缺点是包的体积过大,并且严重依赖于苹果爸爸的审核。

三、我的实现方式:

最近我也做了皮肤相关的功能,下面我说一下我的实现思路。
先上图,看一下我的APP控制逻辑。

我的实现思路类似微店的实现方式。但是我并没有使用KVO而是使用了通知注册的方式。
APP启动后直接加载对应的皮肤文件,同时另一个线程去请求后台皮肤接口,接口返回了一个zip包的链接,下载zip包,解压后,解析里面的config.json文件,然后我使用通知的方式去触发换肤。具体的思路逻辑相信流程图上已经画的很清楚了。
控制皮肤是否显示的逻辑完全由后台控制,后台返回skinSign为空则关闭皮肤。

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

{
    "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中我加了一个方法,如下:

- (void)configSkinMapModule:(NSString *)module skinMap:(NSDictionary *)skinMap;
假设我需要给导航栏添加换肤的功能,我只需要加上如下代码:
 [_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文件去定义一下,替换的方式标识。

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";

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";

static NSString * const kSkinMapKey_imageView_image = @"kSkinMapKey_imageView_image";
static NSString * const kSkinMapKey_imageView_gif = @"kSkinMapKey_imageView_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)的功能。

我们在通知触发方法中使用如下代码去执行替换过程

- (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等其他模块我就不一一介绍了,都是大同小异。

- (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];

    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本地默认皮肤。
以上就是我实现换肤方式的一个思路。

四、总结:

以上各种实现方式都各有各的好处,我的实现方式也有需要优化的地方,例如可以在后台接口上加入时间控制,可以实现提前的缓存方案,而不必每次都是在用户眼皮底下换。如果你有更好的实现方案欢迎一起交流。

参考资料:
1.github·ThemeManager
2.github·SwiftTheme
3.iOS换肤方案
4.github·EasyTheme
5.「节日换肤」通用技术方案__iOS端实现

原文地址:https://www.cnblogs.com/weicyNo-1/p/8182339.html

时间: 2024-10-09 22:52:44

iOS客户端节日换肤方案探究的相关文章

.NET Web后台动态加载Css、JS 文件,换肤方案

后台动态加载文件代码: //假设css文件:TestCss.css #region 动态加载css文件 public void AddCss() { HtmlGenericControl _CssFile = new HtmlGenericControl("link"); _CssFile.ID = "CssFile"; _CssFile.Attributes["rel"] = "stylesheet"; _CssFile.A

「节日换肤」通用技术方案__iOS端实现

一.问题的提出 不知道大家有没有发现, 元旦期间, 很多APP界面里的图标都换成了具有节日气氛的样式, 而在过了元旦节之后, 这些图标又悄无声息的变回了本来的面貌. 这些具有短暂生命周期.而又必须在固定时间节点上展示的节日皮肤, 究竟是如何实现的呢? 显然, 通过发布新版本可以实现, 但是对于iOS端的应用来说, 面对苹果APP Store不确定的审核时间, 开发人员往往需要提前1~2周完成并提交审核, 而且每到一个节日都要重新发布一个新版本, 难免略显被动. 热更新是一个不错的选择! 试想一下

网站换肤方案汇总

1.实现网站换肤功能,一般最先想到的是用全局class控制样式切换缺点:全局控制CSS,在项目不大,换肤样式不多的情况下,还能勉强够用.但是换肤样式很多的话,代码会非常臃肿,不利于维护. 2.切换引入CSS样式的href属性值,来达到切换样式的目的. <link id="skin" rel="stylesheet" href="./skin/skin.css"> document.querySelector('#skin').href

Delphi中实现ListView滚动条的换肤方案

首先是要骗过WM_NCPAINT消息.这个十分容易.WM_NCPAINT消息的wParam是一个区域的句柄.当它不为1时,从它里面CLIP掉滚动条的区域,再传给原窗口过程即可.当它为1时,创建一个包含控件全客户区域的Region,再从中CLIP掉滚动条的区域,传给原窗口过程.然后是WM_HSCROLL和WM_VSCROLL消息.在调用原窗口过程之前需要去掉窗口的WS_HSCROLL和WS_VSCROLL样式,否则窗口过程就会在消息中绘制滚动条.调用后需要恢复.同时为避免窗口在WM_STYLECH

QT自定义精美换肤界面

陆陆续续用QT开发过很多项目,也用QT写过不少私活项目,也写过N个工具,一直梦寐以求能像VC一样可以很方便的有个自定义的界面,QSS的强大让我看到了很好的希望,辗转百度谷歌无数次,一直搜索QT相关的换肤文章,绝大部分的是一些简单的按钮文本样式,要做到整体换肤程度几乎不行,QTCN论坛里的奋斗的孩子写了个模仿360安全卫士系列,让我既惊喜有遗憾,惊喜的是能够用QT实现一个这么完整的360安全卫士界面,确实不错,也支持多种换肤,遗憾的是我下载过的是VC版本的,对于一直执着于用Qt Creator 来

Android主题换肤 无缝切换

2016年7月6日 更新:主题换肤库子项目地址:ThemeSkinning,让app集成换肤更加容易.欢迎star以及使用,提供改进意见. 更新日志: v1.3.0:增加一键切换切换字体(初版)v1.2.1:完善之前版本View的创建v1.2.0:增加对换肤属性自定义扩展v1.1.0:可以直接加载网络上的皮肤文件 今天再给大家带来一篇干货. Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重启直接实现无缝切换,可高仿网易云音乐的主题换肤. 这个链接是本次的Demo打包出来的

教程: Android应用如何实现换肤功能

本节课程的目的:学会换肤的方案及实现,掌握不同换肤方案的优缺点及适用场合. 希望各位同学做到:学习某一个技巧就掌握透彻,多练习.最好举一反三.触类旁通,掌握分析问题解决问题的思路和方法. 我讲解的是原理以及实现的关键技术点,细节.优化及与课程主题不相关的或初级的内容可能不会讲解.这是免费培训,精力有限,暂时只能做到让普通的变优秀,让优秀的变卓越.你要是已经卓越了来教教我吧.暂时不做入门培训. 国内有很多的软件都支持皮肤定制,这也是与国外软件重大不同之一,国外用户注重社交.邮件等功能,国内用户则重

200行代码打造超越一线互联网公司的换肤架构

本专栏专注分享大型Bat面试知识,后续会持续更新,喜欢的话麻烦点击一个关注 面试官: 网易云QQ的换肤是怎么做到的,你对换肤有了解吗?看过换肤的原理没? 心理分析:没有接触过换肤技术 第一次听到该名词肯定会很茫然.面试官考的是对资源加载,监听布局,有没有了解.本文从换肤实战一对一讲解.告诉你如何做以及实现.文章末尾带换肤项目源码 求职者: 从监听布局开始到 换肤原理,详细给面试官讲解换肤的原理 接下来我们一起分享这篇干货. Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重

iOS开发——使用技术OC片&amp;换肤处理(白天与黑夜)

换肤处理(白天与黑夜) 一:首先是使用简单的方法实现 不记得谁说的了,中国的用户大概是世界上最喜欢多皮肤功能的用户了.我很讨厌写安卓程序,图形界面设计工具及其难用,还不如手 写,编辑器慢 如蜗牛,智能提示总是跟不上我输入的速度,相同的功能,安卓的代码量至少是iOS的三倍,每写一行代码,都觉得自己的手指在滴血.可是安卓灵活统一的 style功能确实很贴心!5之前,iOS平台上实现相同的功能一直没有个比较好的办法. 苹果将所有界面组件的设定,都绑定在一个叫UIAppearance的协议上了,你可以简