App研发录
#1.重构
1.1重新规划Android项目结构
将业务与逻辑分离,建立包结构。
###1.4 实体化编程
#####1.4.1使用fastJSON或GSON
坑:代码混淆时带来的混乱
- 加了符号Annotation的实体属性,一使用就崩溃。
- 当有泛型属性时,一使用就崩溃。
|
|
###1.5 Adapter模板
使用ViewHolder机制
###1.6 类型安全转换函数
类型转换可能会带来的crash。
尤其是网络中json数据的转换,有可能为空,或者类型发生变化。
substring时,start和end与length之间的关系。
#2. Android网络底层框架设计
###2.1 网络底层封装
####2.1.2 AsyncTask的优缺点
用AsyncTask封装网络请求
- 缺点:屏蔽了线程池,无法取消已经发出的网络请求;所以,当快速在App页面之间跳转时,导致网络请求队列积压,造成堵塞和延时。
###2.2 App数据缓存设计
根据实际需要对网络请求的数据进行缓存。
GET类型可以进行缓存,而POST为增删改数据,所以,不可以进行缓存。
直接写入文件缓存。
###2.3 MockService
###2.5 HTTP头
19700101 long
GZIP压缩:在HTTPRequest
的头中的Accept-Encoding
字段添加gzip
,同样,在解析HTTPResponse
时,也要判断是否存在gzip
,并做相应的解析。
#3. Android经典场景设计
###3.1 App图片缓存设计
UIL:在onDestroy()里手动清理内存缓存;
|
|
###3.2对网络流量进行优化
#####3.2.1通信层的优化
- MobileAPI接口返回的数据,要使用gzip进行压缩。
- APP与MoblieAPI之间的数据传递,通常都是遵守JSON协议的。JSON是XML格式的,并且以字符存在,所以存在压缩空间。ProtoBuffer传输协议,是二进制格式,在大数据量传输时有有时。
- 避免频繁调用MobileAPI,可以一次获取数据的MobileAPI调用,尽量一次完成。
- MobileAPI使用的是HTTP无状态短连接。TCP协议是长连接,速度会更快。但server能支持的长连接个数不多,所以需要更多的服务器集成。
- 要建立取消网络请求的机制。提供
cancelRequest()
方法,用于在页面跳转时,清空网络请求队列。 - 增加重试机制。在请求失败时,进行3次重试;只对GET进行重试,POST不可以进行重试,以避免表单的重复提交。
#####3.2.2图片策略优化
server提供根据要求返回不同分辨率的图片机制,并在不同的网络环境中请求不同质量的图片。
###3.4 App与HTML5的交互
PhoneGap
###3.5 消灭全局变量
存在的问题:当内存不足的时候,系统会回收一部分闲置的资源,由于App被切换到后台,所以之前存放的全局变量很容易被回收,这时再切换到前台继续使用,在使用某个全局变量的时候,就会因为全局变量的值为空而崩溃。
#4. Android命名规范和编码规范
命名规范
编码规范
小工具:checkstyle
#5. Crash异常收集与统计
收集:把Crash收集到本地数据库。
统计:对每天线上大量的Crash进行去重、分类
5.1 异常收集
UncaughtExceptionHandler
第三方统计工具:腾讯Bugly
###5.2异常收集与统计
数据分析类型:
重复的Crash:
- 不同设备在不同时间发出来的重复Crash。要检查是否只对某些机型或Android版本才会发生类似问题。
- 不同设备在一个时间段发出来的重复Crash。要检查MobileAPI是否返回了脏数据而App没有使用try...catch...语句捕获到。
- 相同设备在短时间内频繁发送重复Crash。可能是因为App没有做好本贵后的善后工作,导致在试图重启崩溃的Activity过程中发生了循环的Crash。这时需要去除重复数据。
每笔异常包含的数据信息:
- exception_name: Crash对应的异常名称。
exception_stack:Crash的详细信息。
exception_name只能作为Crash的参考标准,而产生Crash的真正原因,则隐藏在exception_stack中。
- exception_stack中的OutOfMemory,都是内存溢出导致的。但不包含OutOfMemory的异常,也是有可能是因为OOM导致的,例如ResourcesNotFoundException,有时就是因为OOM引起的。
- NullPointerException,要仔细观察后面的信息。
- 窗体泄露这类问题,基本都是想关闭弹出框时,发现承载它的宿主已经不在。
- ListView和Adapter相关的Crash基本都发生在分页获取数据的场景,数据源发生了改变,却没有及时通知ListView和Adapter。
#6. Crash异常分析
###6.1 Java语法相关的异常
#####6.1.10 不能随便使用asListArrays.asList()
的返回值类型为java.util.Arrays$ArrayList
,而不是ArrayList
;Arrays$ArrayList
没有实现add
和remove
方法,所以在调用时就会报错,可以使用以下解决方案:
|
|
#####6.1.12 NoClassDefFoundError
如果打包时,B和A分别位于不同的dex中,这是如果在A所在的dex中把A类删除了,那么在运行时执行到这句话时就会抛出NoClassDefFoundError
的异常信息。
插件化编程的时候会容易产生此类异常,因为要使用DexClassLoader。
当使用第三方SDK时,也会有可能产生该类异常。
###6.2 Activity相关的异常
#####6.2.4 无法再非Activity的Context中启动Activity(不能启动BroadcastReceiver)
在service、application的Context中启动Activity时就会抛出此类异常,这是可以为intent
添加flag,在新的task中启动,从而解决:
#####6.2.5 startActivityForResult不能回传
异常中关键字:
|
|
当在onActivityResult()
中发生某些异常时,特别是和回传的值相关的操作,就会表现为此类异常,而实际上可能是空指针异常等。
###6.3序列化相关的异常
#####6.3.2序列化时未指定ClassLoader
当实现Parcelable的类中包含自定义的类时,如:
|
|
所以将有可能引发异常的代码修改为:
|
|
ClassLoader相关概念:
当ClassLoader为空时,系统会采取默认的ClassLoader。
Android有两种不同的ClassLoader:framework ClassLoader和apk ClassLoader,其中framework ClassLoader知道怎么加载Android系统内部的类;apk ClassLoader知道怎么加载我们自己写的类,也知道怎么加载Android系统内部的类。
在App刚启动时,默认ClassLoader是apk ClassLoader,但在系统内存不足应用被系统回收会再次启动,这个默认ClassLoader会变为framework ClassLoader,所以对于我们自己的类会报
ClassNotFoundException
。#####6.3.3 反序列化时发现类找不到:被ProGuard混淆导致的崩溃
在ProGuard文件中keep这个类。
###6.4 列表相关的异常
#####6.4.1 Adapter数据源变化但是没通知ListView
#####6.4.2 ListView滚动时点击刷新按钮后崩溃
###6.5 窗体相关的异常
#####6.5.8 AlertDialog.resolveDialogTheme
#####6.5.10 子线程不能修改UI
子线程不能修改UI,这句话不是绝对的,当在onCreate
中使用了新的线程去操作UI控件时,有可能会正常运行,也有可能会产生错误。这是因为,对于线程的检查机制还没有起作用。查看源码,可以发现,对于线程的检查实在viewRoot
的requestLayout()
中去checkThread
,而在onCreate
中,可能还没有执行requestLayout()
方法。
如果需要在其它线程中修改UI,则可以使用Handler
或者Activity
的runOnUiThread
或者使用AsyncTask
。
在线程中调用Looper.prepare()
和Looper.loop()
,可以用于解决Dialog
和Toast
相关的操作问题,但对于UI空间的操作,可能是无效的。
###6.6资源相关的异常
#####6.6.2 StackOverflowError
第一种原因,发生这种事情,主要是因为Layout布局文件结构嵌套层次太深。应尽量控制在5层以内。
第二种原因,在App退出的时候,如果App中有多个线程,那么在退出App的时候可能不能完全关闭App,即使使用finish
方法也做不到,必须使用System.exit(0)这样的语句才可以。这是因为finish
方法只能退出当前Activity,但还可能有其他Activity未关闭,这些Activity中有没结束的线程,从而会有一些资源没有释放。
无论哪种情况,最终都是由无限递归引起的。应避免这种情况的发生。
#####6.6.3 UnsatisfiedLinkError
这是因为在特定平台上的so没有被加载到而引起的,应检查对应平台的so是否存在。
#####6.6.7 TransactionTooLargeException
Binder最大通常限制为1MB,如果大于1MB的话,就会抛出TransactionTooLargeException的异常。
避免传输过大的数据量,尤其是图片以及数据采集可能带来的大的数据量。
###6.7 系统碎片化相关的异常
#####6.7.2 RemoteViews
使用场景:
- AppWidget
- Notification
#####6.7.8 DeadObjectException
当某对象已经被系统回收,我们却还在使用它,就会产生此类异常。
此类异常可能引起系统级错误,从而迫使Android重启,在未Root的机器上,可以达到重启Android设备的目的。
#####6.7.10 ViewFlipper引发的血案
在Activity中使用ViewFlipper控件,进行横竖屏切换操作时,就会发生这种异常。这是由于onDetachedFromWindow()
在onAttachedToWindow()
之前被调用所致。
解决方案是,重写ViewFlipper的onDetachedFromWindow()
,对super
的调用进行try...catch...
:
|
|
###6.10 其他情况的异常
#####6.10.8 Monkey点击过快导致的崩溃
增加延迟函数:
|
|
使用方法:
|
|
#7. ProGuard技术
###7.1 ProGuard简介
ProGuard是一个开源项目,在SourceForge上进行维护。
ProGuard的四个功能:
- 压缩(Shrink):侦测并移除代码中无用的类、字段、方法、和特性(Attribute)。
- 优化(Optimize):对字节码进行优化,移除无用的指令。
- 混淆(Obfuscate):使用a、b、c、d这样简短而无意义的名称,对类、字段和方法进行重命名。
- 预检(Preveirfy):在Java平台上对处理后的代码进行预检。
|
|
使用了某一版本ProGuard后,尽量不要升级,以稳定为首。
#8. 持续集成
###8.4 自动打包
搭建自动打包服务器,并提供定时打包以及手动打包页面,同时提供打包列表页,方便随时查看历史打包记录。方便测试人员测试。
选择合适的自动打包工具。
###8.9 单元测试
#9. APP竞品技术分析
###9.1 竞品分析概述
#####9.1.1 App竞品定义
参考意义:
社区类和视频类App,他们的广告系统做得是最好的。
电商类(OTA 和O2O)App的产品详情页和订单填写页具有很好的参考价值。
活动运营做得最好的仍然是电商类App。
社交类App的聊天功能做得是最好的,尤其是高并发的架构实现。
新闻类App的推送的及时性和到达率是比较有价值的。
拿来主义:拿来不等于生搬硬套,理解其用意,才能取长补短。
###9.2 App安装包的结构
#####9.2.1 Android安装包的结构
resources.arscz, 这个文件是编译后的二进制资源文件的索引,也就是apk文件的资源表(索引)。
lib目录下的子目录存放个各个ABI相关的so文件。
META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全。但这个目录下的文件却不会被签名,从而给力我们无限的想象空间。
assets目录下面可以看到很多基础数据,以及一些本地会使用到的HTML、CSS和JavaScript文件。
res目录下的anim子目录很值得研究,这个目录存放app的所有动画效果。
res目录中的很多XML文件打开后是乱码,AndroidManifest.xml也是如此,那是因为打包的时候对XML文件进行了压缩,所以看到的往往是全角的字符和乱码,不便于查找到我们想要看的内容。有一款神器用于看到apk包中正常的内容,
AXMLPrinter2.jar
,它可以将apk中已经处理过的XML还原为可读格式。命令如下:
|
|
###9.3 竞品技术一:开启速度
流程:
Splash广告 --> 引导页 --> 首页
- Splash广告,首先加载默认图,同时异步下载下一次将要显示的图片。下一次打开时,则直接显示上次下载的图,并同时去检查是否需要更新图片。
- 引导页,建议不要超过4页。可做一些动画,建议使用gif,原生动画太费时费力。
- 进入首页之前,可以引导用户选择一些城市或其它有价值的个别个人信息。
- App的首页设计,目前较为流行的做法是尽可能多的把产品放在首页。统计分析的结果是,首页的内容点击率较高。
还可以做的事情:
- 友盟打点统计,统计激活数。
- 注册推送。
- 如果是从消息推送点击进入的App,则要根据推送协议,跳转到具体的页面。
- 初始化崩溃收集机制,如果上次崩溃时没有来得及发送崩溃信息,那么这次发送。
分析工具:
嗅探器:WireShark
###9.4 竞品技术二:HTML5打开速度
#####9.4.1 把HTML5页面嵌入到Zip包中
使用WebView打开HTML5页面时,可使用以下方法加快打开速度:
- 在app中较为频繁打开的页面,嵌入要打开的HTML5相关文件,把HTML5文件、图片、CSS和JS文件放进压缩包,嵌入到app包中。App每次启动的时候,启动一个线程异步把Zip包解压到本地目录下,然后每次从本地读取HTML5页面。
- 为HTML5文件加入版本控制机制,每次加载之前,向server请求最新版本号并与本地做对比,如果HTML5有更新,则重新下载新的Zip包,并替换掉本地的数据。
- 在每次App发版之前都要嵌入最新的Zip包,以防止App中的HTML5包太老,而需要每个打开app的人都去下载新的Zip包。
#####9.4.2 Zip包的增量更新机制
#####9.4.3 使用WebView预先加载HTML5并缓存到本地
###9.5 竞品技术三:安装包的大小
#####9.5.3 png和jpg的区别及使用场景
png有透明通道,而jpg没有,此外png是无损压缩的,而jpg是有损压缩的,所以png中存储的信息会很多,体积自然就大了。
对于png,一般可以对其做硬件加速,所以对于同一张图,png虽然体积比jpg大,但是加载速度却要快一些。
所以,在设备中,App包里的图推荐用png,从网络中下载的图考虑流量问题应使用jpg。
但对于使用不会特别频繁,尺寸又特别大的图,也可以考虑使用jpg。
Google发布的WebP,压缩率要高于jpg,可以使用,但在iOS中需要引入WebP解码器才能使用。
iOS中,启动页的图片必须是png的,否则会通不过审核。
#####9.5.9 字体文件的学问
字体是矢量的,所以将某些简单图案设计为字体可以有效的减小安装包体积。
可以使用下述网站将单色icon转换成字体文件:http://icomoon.io
。或者使用FontLab这样的工具来制作。
然后将ttf
文件放在assets
目录下,将icon和十六进制编码的映射关系保存在drawable资源文件中,如:
|
|
或者设计成Drawble对象,然后设置给ImageView这样的控件。
###9.6 竞品技术四:性能优化
#####9.6.1 App自动选取最佳服务器的策略
遍历配置文件中的服务器列表,多次测算同一个服务器的来回走路时间,取其平均,选择最佳服务器,同时,服务器可返回服务器处理能力参考值,以CPU占用率作为参考结合网络请求返回时间,进行服务器的选择与判断。
#####9.6.2 使用TCP+Protobuf
区别于HTTP+JSON的连接方式。在一定程度上可以改善用户体验,但同时会增加服务器压力,所以也要做好切换回HTTP+JSON的机制,App端架构采用Build模式和策略模式以适应这两种机制的切换。
###9.7 竞品技术五:数据采集工具
#####9.7.2 打点统计
需要解决的两个问题:
- 如何在发版前就能检查出漏打的和打错的点。
- 如果在筏板后发现漏打的和打错的店,快速修复快速上线,而不必等新版本发布。
打点的分类:
- 页面打点
- 事件打点
#####9.7.3 ABTest
定义:对某一页面的UI样式修改或者数据呈现,使用两种方案测试效果,经数据采集比较后,确定使用哪一种方案。
###9.8 竞品技术六:热修复
Android可以使用插件化编程,但比较费时费力,iOS不可以使用插件化编程。
所以较为合适的方案是在Native和HTML5之间灵活的切换。
#####9.8.2 使用脚本编程
在iOS中使用Lua进行编程。
Android中也有AndroLua开源项目、Dexposed等可以做热修复。
###9.9 竞品技术七:曲径通幽
#####9.9.1 一切皆可配置
- 使用XML或JSON配置首页,防止因加载不到数据而没有入口。
- 配置页面的公共行为,如:请求网络时是否需要显示进度条,进度条中是否有取消按钮,点击取消按钮后退到上一页还是停留在当前页面,请求错误时是否显示错误提示等。
#####9.9.2 APP后门
用途:
- 做一个能切换服务器的页面,在开发期间可以在测试环境和线上环境之间切换。
- 测试某个页面请求了哪些网络请求,打印出调用这些接口时输入的参数和返回JSON数据。
- 收集App崩溃信息,方便测试期间追踪错误来源。
- 提供一个后门页面供HTML5团队进行调试。
- 对APP进行流量统计。
- 对APPP进行电池电量消耗测试。
#####9.9.3 使用META-INF目录打渠道包
打包混淆签名等耗费时间过长,如果在AndroidManifest.xml中定义meta的方式来获取渠道名称,需要每次重新打包,如果打包数量过多,则耗费数小时时间。
可以在META-INF目录下放置一个空文件,文件名则为渠道名,每次去读这个文件的名字即可。因为META-INF目录下文件的修改不需要重新签名,所以使用脚本批量操作就会很快。
###9.11 竞品技术九:第三方SDK
#####9.11.1 HTML5
- PhoneGap
- WebViewJavascriptBridge.js
- zepto.js
- CryptoJS
- mraid.js
#####9.11.2 iOS
#####9.11.3 Android
- aSmack XMPP协议的开源库
- EventBus
#####9.11.4 others
- Pinyin4j 汉字转换为拼音
- Countly 统计分析平台
#10. 项目管理决定了开发速度
###10.1 团队建设:
- 产品经理
- 开发
- 测试
开发与测试的比例应大约在6:1
#####10.1.1 测试团队的工作:
- 召开测试用例评审会
- 手动测试
- 全功能回归测试
- 探索性测试
- 渠道包测试
- MobileAPI发布上线前的测试工作
- 压力测试
- Monkey测试
- 客户投诉回访
#####10.1.3 开发人员
抱怨最多的:
- 一句话的需求。
- 开发过程中,产品需求频繁变动。
- 产品经理搞不清楚业务逻辑,直到开发过程中才发现有问题。
- UI设计图、切图、标注图不到位。
#####10.2 优化团队结构,让敏捷流程跑得更快
#####10.3 App敏捷开发
Lua
#####10.7 总结
团队管理:弱管理,有一个主题,自由发挥。
项目管理:强管理,实时跟进项目进度,保证项目进度。
20个技术点
- Activity相关。App应用开发,以Activity使用最多,涉及LaunchMode、onSaveInstanceState、生命周期等技术。
- Fragment相关技术。用的人不少,想明白咋回事的人不多。这里推荐一本书《Creating Dynamic UI with Android Fragments》.
- 序列化技术。有Parcelable和Serializable两种。前者是基于Service的,后者是基于Bundle的,二者实现原理不同,但是达到的效果差不多。
- ImageLoader的原理和使用。类似的,还可以学习Facebook新近开源的Fresco,它对图片的处理会更好一些。
- fastJSON或GSON的使用。做App不会用实体自动匹配JSON,相当于白做。
- 多线程相关。包括Handler、Looper、ExecutorService等。
- Adapter和ListView。这两个技术捆在一起,经常容易崩溃,尤其是分页的时候,要仔细研究深刻领会。
- 用户Cookie设计。需要把登录机制彻底搞清楚,包括在HttpRequest头中夹带Cookie来进行用户身份验证的技术。
- 网络请求封装。使用AsyncTask的网络底层封装,使用Handler+Runnable的网络底层封装。
- Android与HTML5的交互。包括Android调用HTML5的方法,以及HTML5调用Android的方法。
- 代码混淆。没用过ProGuard,不知道keep相关语法,就还是初级水平。
- Android打包机制。涉及Android SDK中的若干命令。对Android打包过程做的每一件事都很清楚。进一步是Android多项目依赖的打包技术。Ant、Gradle或者Maven,掌握其中一种打包机制即可。
- 线上Crash分析并修复。要具备分析Crash信息修复线上Crash的能力。
- 内存泄露。包括内存优化、内存泄露的场景、MAT工具的使用。
- 调试工具。包括DDMS、Eclipse或者Android Studio的调试功能。
- Monkey机制。Android开发人员如何对一款App进行Monkey测试。这算是附加技能吧。
- 单元测试。这里指的是JUnit。对复杂的算法写过单元测试以保证其没有问题。
- GIT的高级功能。包括Stage、Rebase、Revert、Stash、Cherry Pick和Sub Module等概念。如果项目中使用的是SVN,那么要掌握SVN的版本管理策略。
- 插件化编程。哪怕知道一点DexClassLoader的概念也好。这年头,没做过插件化编程,出门面试都不好意思说自己是做Android开发的。
- 设计模式。对常见的设计模式如工厂、生成器、适配器、代理、策略模式耳熟能详。