9.9 竞品技术七瞥:曲径通幽
9.9.1 一切皆可配置
1.使用XML配置首页,防止因加载不到数据而没有入口
在很多电商类App中,我们会看到有一个配置文件或者JSON文件里面存放着首页展示所需要的所有数据,包括图片、文字等等,点击后能进入各个品类这些二级页面,如图9-12所示,我们可以看到,这个首页由3个Tab组成:首页、发现、个人中心,配置文件中指定了每个Tab的显示文字、点击后对应的ViewController、所需的默认图和高亮图。
图9-12 某款App首页的plist配置文件
这么做是因为,如果获取首页信息的MobileAPI接口挂了,或者,就在我们调用该接口的时候挂了,那么首页仍然能通过读取这个本地的配置文件或者JSON文件而正常显示,仍然能看到各个品类的入口,点击后进入,这样不影响生意。
但是这个配置文件或者JSON文件可能不是线上最新的数据,所以一种好的解决方案是,第一次启动App的时候把这个文件复制到本地,然后每次调用首页MobileAPI接口渠道数据后就把数据同步到这个文件,这样就确保了下次如果调用MobileAPI接口不通,仍然能显示比较新的数据。
2.配置页面的公共行为
把首页的数据配置在XML中只是第一步,这个世界上不乏野心者,他们想把更多公用的东西做成可配置化。
比如,调用MobileAPI时是否要显示进度条,进度条中是否有取消按钮,点击取消按钮后是后退到上一页还是停留在当前页面,调用MobileAPI错误是否要显示错误提示,如下所示:
<ShowSetting showLoading="1" showCancel="0" goBackAfterCancel="1" showErrorInfo="1" />
又比如,进这个页面是否要登录,如下所示:
<WindowType needLogin="1"/>
所有这些信息都定义在配置文件中。我们应该在App中编写一套页面引擎,自动读取配置信息,这样就能少写很多很多代码。开发人员就可以把更多精力放在业务逻辑的实现上。
9.9.2 App后门
任何成熟App都会为自己留一个后门,目前业界有两种做法:
·只有Debug版本能看到这个后门,而Release版本看不到。
·在线上Release版本中很深的一个页面,比如设置页面,点击某个特定的区域很多次后弹出一个对话框,要求输入密码,输入正确就能进入这个后门。
留一个后门有很多好处列举如下:
·做一个能切换服务器的页面。这样就可以在开发期间,从线上环境切换到测试环境而不需要重新打个包,极大方便了测试团队对新功能进行验收。
·要测试某个页面请求了哪些MobileAPI接口,打印出调用这些接口时输入的参数和返回JSON数据。这样就能够在线上App发现某个页面有问题时,及时在App后门中检查数据是否正常,而不用App开发人员和MobileAPI开发人员坐在一起逐行联调代码,极大节省了人力。
·对于App崩溃,我们将最后一次崩溃的信息记录在本地,然后可以通过后门看到这个崩溃信息。这对于测试期间不经意点出来的崩溃,可以迅速追踪到问题的所在。当然,另一种方案是把崩溃信息发送到服务器,然后我们去服务器抓取崩溃信息,但是这样不及时;而对于那些发现App崩溃然后来找我们的同事朋友来说,通过后门看崩溃日志是最好的途径。
·提供一个后门页面供HTML5团队进行调试,该页面内置一个WebView,加载HTML5团队正在开发的HTML页面,要支持调试。
·对我们的App进行流量测试,统计某个页面所花费的流量,包括调用MobileAPI、下载图片、上传文件、XMPP聊天等等。其中,从App启动到首页加载完成所花费的流量是我们关心的一个关键点,而手机待机时,App所花费的流量也是我们所关心的。我们需要这样一个后门页面,看到这些数据统计。
·对我们的App进行电池电量消耗测试。需要有个后门页面记录每次打开App和退出App的时间,以及这段时间内我们的App所消耗的电量。为了确保数据的准确性,需要确保手机上只安装了一个App,而且处于相同的网络环境下,比如3G。
前面说到开一个后门,提供切换服务器的功能。这样测试人员可以在这个后门页面灵活配置当前MobileAPI要连接哪个服务器。基于此,这个后台页面需要显示服务器清单列表,而这个列表从App包中的一个文件读取,此外,还要支持手动输入服务器地址,因为有时候要直接连接到MobileAPI开发人员的机器,把他们的开发机器作为临时服务器。
9.9.3 Android包中META-INF目录的妙用
对于Android批量打渠道包,每个团队都有切身的痛。每个包经过混淆和签名,都至少要3分钟时间,300多个包就是十几个小时才能全打出来,所以一般在晚上干这个事情。
一般而言,我们在App每次启动时从AndroidManifest.xml这个文件读取渠道名称,如下所示,其中360Android是渠道名称:
<application> <meta-data android:name= "UMENG_CHANNEL" android:value= "360android"/>
然后在App中,每次从AndroidManifest.xml中取出这个渠道名称,传递给友盟或者我们自己的MobileAPI接口,如下所示,演示了如何取得渠道名称的方法:
private String getChannel(Context context) { try { PackageManager pm = context.getPackageManager(); ApplicationInfo appInfo = pm.getApplicationInfo( context.getPackageName(), PackageManager.GET_META_DATA); return appInfo.metaData.getString("channel"); } catch (PackageManager.NameNotFoundException ignored) { } return ""; }
上述是传统的做法,我们接下来介绍一种更快的做法。
我也是偶然的机会,看到一些知名的App包里面的META-INF目录,会有一个0字节的文件,文件名是某个渠道的值,于是我就大胆猜测,这个文件是用来批量打渠道包的。
我上网查了一下这个META-INF目录的功用,发现修改这个目录里面的文件,是不需要重新签名App的。于是我们可以如下进行优化。
1.打包流程上的优化
打一个签名混淆过的正式包,我们称之为“母体”,然后往这个apk包中插入一个名为channel_360Android的空文件。这样一个渠道包就完成了,如图9-13所示。
图9-13 META-INF目录下的空文件
之所以在空文件的名称前面加上channel_的前缀,是为了在运行期查找这个文件的时候,可以快速找到。
准备一个渠道列表文件channel.txt,文件内容由3个渠道组成,每个渠道占一行,如下所示:
360Android 91Android baidu
接下来我们使用Python脚本build.py,遍历这个渠道列表文件,逐个生成渠道包,脚本如下所示:
import zipfile import shutil import os base_dir = '/Users/Shared/' apk_name = 'ChannelDemo' apk_path = base_dir + apk_name + '.apk' empty_file = base_dir + 'baojianqiang' f = open(empty_file, 'w') f.close() channel_file = base_dir + 'channel.txt' f = open(channel_file) lines = f.readlines() f.close() output_dir = base_dir + 'output' if not os.path.exists(output_dir) os.mkdir(output_dir) for line in lines target_channel = line.strip() target_apk = output_dir + '/ChannelDemo_' + target_channel + '.apk' shutil.copy(apk_path, target_apk) zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED) empty_channel_file = 'META-INF/channel_' + target_channel zipped.write(empty_file, empty_channel_file) zipped.close()
这样,生成第一个“母体”包需要几分钟时间,但是之后生成其他渠道包的时间就快了,就都是在apk中插入一个空文件的时间了。
2.运行期间的优化
我们改为从META-INF目录读取那个0字节的文件名称,从中得到这个apk包的渠道号,网上有很多这样的代码例子,我就不过多说了。
按照上述打包新流程,一分钟打几百个渠道包不成问题。
9.9.4 classes.dex的拆与合
一般而言,Android App中的dex文件只有一个。但是对于很多业务逻辑复杂的App,当方法数量超过dex的最大限制65535时,编译就会出错。业界称之为“爆棚”。
一种解决方案就是插件化。把那些独立的模块做成一个apk,然后在使用的时候,使用DexClassLoader进行加载。对那些暂时还没有插件化编程的App而言,这种解决方案太遥远了。
另一种解决方案就是dex分包。就是将classes.dex拆分为2个dex,让每个dex包的方法数量都小于65535。很多App都是这么做的,有的App甚至拆分成6个dex之多。
Google提供了dex拆包工具multidex。关于这个工具的使用方法,请参见CSDN上“时之沙”的博客文章 [1] 。
但multidex仍然解决不了开发调试期间方法数超过dex上限的问题,开发人员不能每次调试都使用Gradle去打包,于是我们只好把不重要的SDK引用临时去掉,比如GA打点,同时注释掉引用了这个SDK的代码,这样就能正常编译和调试了,最后在提交测试或发布市场打包的时候再把删除的引用和注释掉的代码恢复过来。
每次都这么搞可不行,严重扰乱了开发节奏,于是我们采取在代码中动态加载dex的方式。对于那些第三方SDK,比如Umeng、Google Analytics、aSmack、JPush,都是导致dex方法数徒增最终达到上限的“杀手”级SDK,所以我们优先把这些jar包提出来,放到一个apk中,作为第二个dex。这样我们就能使用DexClassLoader加载这些SDK啦。
只要能把方法数降低到65535以下,就又可以在各种IDE中正常开发调试了。
关于动态加载dex的技术,请参见以下文章,有更加详尽的介绍:
·custom class loading in dalvik [2]
·美团Android DEX自动拆包及动态加载简介 [3]
·Android dex分包方案 [4]
[1] 博文地址:http://blog.csdn.net/t12x3456/article/details/40837287。
[2] 博文地址:http://android-developers.blogspot.hk/2011/07/custom-class-loading-in-dalvik.html。
[3] 博文地址:http://tech.meituan.com/mt-android-auto-split-dex.html。
[4] 博文地址:http://my.oschina.net/853294317/blog/308583。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论