如何打造一款高质量的Android移动应用

发表于:2019-01-02来源:未知作者:周智点击数: 标签:
随着移动互联网红利的结束,移动应用开发的爆发期已经结束,现在已经进入稳定期,现在大家讲得最多是用户体验和应用质量,现在各种移动应用功能同质化很严重,所以如何打造出

随着移动互联网红利的结束,移动应用开发的爆发期已经结束,现在已经进入稳定期,现在大家讲得最多是用户体验和应用质量,现在各种移动应用功能同质化很严重,所以如何打造出一款高质量的移动应用是留住用户的的先决条件。

过去的 iOS 开发者可能做梦也想不到,现在也要开始适配屏幕和双卡双待,更不用说Android那么多如繁星的机型,厂家和操作系统,如果应用要出海,还要面对几十个国家不同的语言和环境。另一方面,我们的业务越来越复杂,如何管理上十几个上百个模块,以及还要面对React Native,Flutter,Kotlin,Tensorflow等各种语言跟框架堆积在一起的情况,所以做一款高质量的应用需要做很多的工作。一个应用至少要经过开发,编译CI,测试,灰度和发布几个阶段,见如下图所示:

1.png

除了在开发测试以及灰度阶段对应用进行分析外,应用一旦安装到用户手机上后对用户操作数据的分析才是最难捕获的,所以选择一款成熟稳定的移动端APM(Application Performance Management)平台是我们分析数据的坚实后盾,国内像阿里,腾讯,美团点评,饿了么,爱奇艺这些大厂都在大力投入研发应用性能管理平台。Google今年也发力Android Vitals监控,新增了耗电,权限管理等模块。

移动APM质量平台好处

1、统一管理,所有阶段的异常数据都汇总到一个平台;

2、统一三端,现在大部分应用都由Android,IOS,H5多个端组成,随着技术的发展还可能增加React Native,Flutter,Kotlin等新技术模块的监控,所以统一各个端非常重要。

2.png

移动应用的质量主要包括稳定性和性能,像崩溃,卡死,白屏这些问题对于用户而言是致命的,另一大类问题就是性能问题,安装包大小,启动,耗时,耗电,流量等范畴,具体分类如下:

3.png

由于Android碎片化和国内Android生态的乱象,手机厂商的随便定制ROM,导致国内Android应用需要对各个厂商的手机进行适配,在今年11月份举办的Android绿色联盟开发者大会上推出的应用体验标准,对应用的兼容性,稳定性,性能,功能和安全做了详细的定义,我们可以根据这组数据对我们的Android应用进行优化。

4.png

虽然移动APM质量平台可以帮助我们快速发现和定位问题,但是监控不能保证实现高质量,这里还需要程序员进行分析和优化,根据上面提到的移动应用质量指标,本文从崩溃,内存优化,卡顿定位和分析,以及应用启动等几个方面浅谈一下如何进行优化。

Android app崩溃率可以用:UV崩溃率=发生崩溃的UV / 登录UV,只要用户发生过一次崩溃就会被计算到。

1、Android崩溃分类:

        1、java崩溃;

        2、Native崩溃。

简单来说,Java崩溃就是在Java代码中,出现了未捕获异常,导致程序异常退出,Java崩溃相对来说比较容易捕获。在Android系统中有一个UncaughtExceptionHandler类,可以在uncaughtException回调函数中对异常进行捕获然后上报到APM质量平台。但是Native崩溃会比较麻烦,Native崩溃一般是在c/c++代码中访问了非法地址,也可能是地址对齐出现了问题,或者发生了程序主动abort,这些都会产生signal信号,导致程序异常退出。

2、Native崩溃的捕获流程:

        1、编译阶段:编译c/c++的时候需要把符号信息保留下来;

        2、客户端,捕获到异常的时候,尽可能地将有用的信息保存到本地,然后选择适当的时机上报服务器

        3、服务端,读取客户端上报的日志文件,寻找的的符号文件,生成可读的c/c++调用栈。目前Native崩溃捕获最成熟的方案就是google的breakpad方案,在github上git clone https://github.com/google/breakpad.git ,可以在Linux或者mac平台上编译出minidump_stackwalk,dump_syms工具。通过dump_sysm工具可以生成发生崩溃so文件的符号表,通过mindump_stackwalk工具可以生成上报native崩溃日志的调用栈,结合符号表就能定位到发生崩溃的位置。

崩溃处理

        1、Java崩溃类型比较明显,实际开发过程中NullPointerException空指针的情况比较多,从后台获取的数据没有判空就就进行使用等情况容易产生空指针异常,或者OutOfMemoryError,使用了大图片没有及时释放导致内存耗尽;

          2、Native崩溃需要观察signal,code,fault addr等信息;

        3、ANR的时候先看主线程的堆栈,是否因为锁等待导致,接着看ANR日志文件中iowait、CPU、GC、system server等信息,进一步确定是I/O问题,或者是CPU竞争问题,还是由于大量GC导致卡死。

内存优化

内存优化是崩溃优化中非常重要的一个部分,类似OOM,很多的异常退出都是由于内存问题引起的。内存引起的第一个问题就是异常,包括OOM,内存分配失败,内存整体不足引起应用被杀死;内存造成的第二个问题是卡顿,java内存不足会引起频繁地GC,除了频繁GC造成卡顿外,物理内存不足会引起系统触发low memoery kill机制。

5.png

内存优化主要从以下三个方面入手

        1、设备分级;

        2、bitmap优化;

        3、内存泄漏。

Facebook 开发的检测手机主流配置工具device-year-class,我们可以对低端手机关闭复杂的动画效果,使用565格式图片,使用更小的缓存策略来提升应用在低端机上的体验。

6.png

根据以上的设备内存分配图,可以使用一下代码,根据不同设备使用不同的动画显示策略。

 

 

if (year >= 2013) {
   // Do advanced animation
} else if (year > 2010) {
   // Do simple animation
} else {
   // Phone too slow, don't do any animations
}

 

一个空的进程也会占用10MB的内存,所以在低端机器上尽可能减少应用启动进程数,减少常驻进程数,尽量不要使用进程保活技术。一个80MB的应用很难在512MB内存的手机上流畅地运行起来,可以针对低端机用户推出轻量版本,比如facebook Lite,今日头条极速版本都是这个思路。

Bitmap内存一般占据应用总内存很大一部分,把bitmap放到native内存,虽然可以减少GC频繁调用带来的问题,提高了系统内存利用率,但是并不能解决bitmap占用内存过大的问题。Bitmap优化前提就是限制图片的调用,即限制Bitmap.createBitmap,BitmapFactory相关接口的调用,可以考虑使用统一的图片库比如Glide,Fresco等。检测大图片,例如长宽远远大于view甚至屏幕的宽高,就需要对这个大图片进行优化,重复图片监控,如果多个bitmap的像素数据完全一致,就应该删除冗余的图片。内存泄漏,应该从架构上进行设计,例如,避免长周期的对象持有短周期对象,各种监听器尽量不要引用Activity或者Fragment的context。Java内存泄漏可以借助类似LeakCanary自动化检查工具,可以做到Activity和Fragment的内存泄漏检查。

应用卡顿都和CPU时间相关,CPU时间分为两种:用户时间和系统时间。用户时间是应用程序执行代码消耗的时间;系统时间是执行内核系统调用所消耗的时间,包括I/O、锁、中断以及其他系统调用时间。通过cat /proc/stat得到整个系统CPU的使用情况,然后通过cat /proc/[pid]/stat获取某个进程的CPU使用情况,如果CPU使用率长期大于60%,标识系统处于繁忙状态,就需要进一步分析用户时间和系统时间的比例。对于普通的应用程序,系统时间一般不会超过30%,如果超过这个值,就需要进一步检查是不是I/O过多,或者是其他系统调用问题。

Andriod卡顿排查的主流工具

1、Traceview;

Traceview利用Android Runtime函数调用的event事件,将函数运行的耗时和调用关系写入trace文件,此工具本身有很大的性能开销,有时无法真是反应实际情况,比如一个函数本身耗时1秒,但是Traceview工具开启后可能变成了5秒。

2、Nanoscope;

Nanoscope是uber开源的工具,它直接修改Android虚拟机源码,在ArtMethod执行入口和执行结束位置增加埋点代码,将所有信息写入到内存,等到trace结束统一生成结果文件,它不会带来额外的性能开销,可以任意分析一个应用,但是需要自己刷ROM,目前只支持Nexus 6P。

3、systrace;

Systrace利用Linux的ftrace工具,在系统各个关键位置增加了监控埋点,可以对Graphics,Activity Manager,Dalvik VM,System server进行监控,而且性能开销非常低,但是它不支持应用程序代码耗时分析,使用起来有一定的局限性。

4、Simpleperf。

Simpleperf,可以分析Native函数耗时,它是Android5.0以后增加的性能分析工具,它可以监控dex,verify class等的耗时,在Android studio3.2可以直接在profiler中直接使用。总的来说卡顿分析的话,如果分析Native代码耗时,可以选择simpleperf;如果想分析系统调用可以选择systrace;如果想分析整个程序执行流程的耗时可以选择traceview或者插桩版本的systrace。

Android APP启动过程优化

Android APP启动过程:

        1、点击桌面图标解析Manifest;

        2、Application创建,闪屏Activity创建;

        3、MainActivity主界面创建;

        4、启动完成界面可操作。

7.png

根据整个启动流程我们可以把启动优化分为:闪屏优化,业务梳理,业务优化,线程优化,GC优化和系统调用优化。

一般应用都会先创建SplashActivity,然后在创建MainActivity,如果能把两个Activity合成一个,可以节省100ms左右的优化,通过MainActivity先展示SplashFragment,展示完毕有remove掉,同时在闪屏的2秒时间内进行首页网络数据的缓存,同时采用viewstub形式对activity_main的布局进行懒加载,防止首页过于复杂耽误view的解析时间。

由于很多项目会使用插件化,热更新等技术,而这些框架会使用各种反射,各种HOOK技术,这是非常耗时的(平均需要几百毫秒),所以应该对这些业务模块进行优化。

线程优化主要是减少CPU调度带来的波动,应该控制线程数,线程太多会互相竞争CPU资源,统一的线程池来管理,根据机器性能控制线程数。另外可以利用systrace检查是否有线程锁,因为主线程会因为有线程锁而等待,出现主线程长时间空转。

启动过程尽量减少GC次数,避免造成主线程长时间卡顿,通过systrace查看整个启动过程GC的时间,如果发现GC同步等待,那就需要使用allocation工具做进一步分析。

启动过程中避免进行大量字符串操作,特别是序列化和反序列化。一些频繁的创建对象,比如在网络库和图片库中byte数组,buffer尽量重复使用。如果一些模块确实需要频繁创建对象,可以考虑移到Native实现。通过systrace的System Service类型可以查看System Server的CPU工作情况,在app启动过程中,尽量不要做系统调用,比如PackageMangerService操作,Binder调用等等。

结语:

开一发一款高质量的应用涉及到的知识和内容比较多,本文基本上归纳总结了大致的方向,和一些实践中的应用总结,把所有的方面都做到了,非常耗费人力和时间,但是我们可以把这个作为一个终极目标,不断打磨产品,争取为用户带来更好的移动体验。


原文转自:http://college.creditease.cn/#/detail/205