360°无死角Android性能优化总结

前言 Android架构

Android性能优化介绍
image.png
文末有相关技术福利,需要的可以领取。

1. 缘由

Android系统每隔16ms发出VSYNC信号,对UI进行渲染,如果每次渲染都成功,就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成,时间超出16ms越多,丢的帧就越多。

假设我们更新屏幕的背景图片,需要24ms来做这次运算。当系统在第一个16ms时刷新界面,然而我们的运算还没有结束,无法绘出图片。当系统隔16ms再发一次VSYNC信息重绘界面时,用户才会看到更新后的图片。也就是说用户是32ms后看到了这次刷新(注意,并不是24ms),这就是丢帧。

大多数多用感知到卡顿等问题最主要的根源是渲染问题,而导致渲染问题的原因是性能问题,为了保证程序正常的使用,性能方面需要着重注意,本篇针对的性能优化是从一些平时常见的细节入手,

2. 性能优化

丢帧只是用户能感知到的表面现象,严重的会引起程序卡顿甚至ANR,深层次的原因是代码中有比较耗时的操作阻塞到了主线程,也就是性能问题。

2.1 过度绘制

过度绘制(Overdraw)是指屏幕上同一个像素点在同一帧时间内被绘制多次。在层级复杂的UI结构中,如果不可见的UI也被绘制,会导致某些像素区被绘制多次,浪费大量的CPU以及GPU资源。

在手机的开发者选项中,打开显示布局边界即可查看页面的绘制信息。


image.png

有四中颜色,蓝色,淡绿,淡红,深红分别代表着不同的绘制信息。蓝色代表一次绘制,淡绿代表两次绘制,淡红代表三次绘制,深红代表四及以上次绘制。

我们的目的是尽量减少红色的绘制信息,方法有两种。

简化页面UI结构,复杂的UI布局会导致大量View重叠,出现过度绘制的可能性比较大,要避免布局嵌套过多,例如一般情况下,优先使用LinearLayout布局。
复用背景色,例如如果父布局和子View背景色是相同的,只需要父布局设置背景色即可,子View不用设置。

2.2 布局优化

上面提到简化布局可以减少过度绘制的问题,布局优化可以从下面几方面入手。

*布局的选择,能满足需求的情况下优先选择LinearLayout,因为RelativeLayout在measure会比LinearLayout多出一次,
即使LinearLayout使用了weight后,性能依然会比RelativeLayout好
*边距的设置,RelativeLayout的measure过程中,如果出现子View和布局本身高度不同时候,还会触发measure过程,解决方法很简单,使用padding代替marigin
*复用布局,include
*延迟加载,ViewStub
*合并布局层级,merge
关于这块的具体详情,参考下面链接:

2.3 I/O操作(SharedPreferences)

这里I/O操作仅针对SharedPreferences,因为这个基本是在Android中使用最多的储存库了。

2.3.1 读操作

读操作一般不会阻塞到主线程,如果读取的数据比较大而且需要有大量的处理操作,直接开子线程,在子线程处理。

2.3.2 写操作

写操作比读操作复杂一些,向SharedPreferences写入数据时候,有commit和apply两个方法。

commit方法,直接将数据同步写入磁盘
apply方法,先将数据写入内存,再异步写入磁盘
commit和apply方法区别在于同步写入和异步写入,以及是否需要返回值。在不需要返回值的情况下,使用apply方法可以极大的提高性能。

在使用lint静态扫描代码时候,会建议使用apply去替代commit,说明官方也是支持使用apply方法的。

但是!!!

并不是是说apply不会阻塞到主线程,它也有可能会阻塞到主线程。详情请看这篇文章SharedPreferences调用导致的ANR分析。

长话短说。

使用apply方法时候,会向QueuedWork队列中添加一个等待写入操作完成的线程,只有当写入操作完成后,才会从QueuedWork将等待线程线程移除掉。

而主线程中,Service的启动和stop以及Activity的onPause()和onStop()生命周期都会等待其他异步线程完成,才会继续执行。

例如停止Service的时候代码如下(ActivityThread类中):

private void handleStopService(IBinder token) {
 ···
 QueuedWork.waitToFinish(); // 等待其他异步线程完成
 ···
}

可以看到,如果使用apply方法写入大量复杂数据,确实有可能会阻塞到主线程,甚至可能导致ANR。

优化方法:

*复杂数据
*即时性较弱(距离下次使用时间较长),新建子线程使用commit方法,防止阻塞到主线程
*即时性较强,可以考虑直接放在内存中
*简单数据,使用apply方法

2.4 序列化

大部分情况下,与后台交互使用的数据是gson格式。相信很多是直接使用google官方的Gson类来序列化和反序列化的。

我在项目中就遇见了有使用Gson反序列化复杂数据时候,造成的卡顿现象。

Gson序列化和反序列化是可以优化的。

stackoverflow,问题/答案,https://stackoverflow.com/questions/15509544/optimizing-gson-deserialization
序列化方法对比,https://github.com/eishay/jvm-serializers/wiki
Gson序列话优化,https://sites.google.com/site/gson/streaming
使用流配合Gson序列化,性能能提高25%

// 反序列化
public List<Message> readJsonStream(InputStream in) throws IOException {
 JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
 List<Message> messages = new ArrayList<Message>();
 reader.beginArray();
 while (reader.hasNext()) {
 Message message = gson.fromJson(reader, Message.class);
 messages.add(message);
 }
 reader.endArray();
 reader.close();
 return messages;
}
// 序列化
public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
 JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
 writer.setIndent(" ");
 writer.beginArray();
 for (Message message : messages) {
 gson.toJson(message, Message.class, writer);
 }
 writer.endArray();
 writer.close();
}

2.5 反射

大部分框架或者SDK为了解耦,或多或少的会使用到反射,反射本身会性能就会比直接调用差很多。

有时候会大批量使用反射去实例化对象,所以不要在那些会被反射实例化的类的构造函数中做太多的事情。

建议在获取实例化对象后,再使用对象直接调用初始化方法。

2.6 异常

在代码中有时候可能会发生异常,所以一般使用try/catch来做保护,避免程序崩溃。

一旦有异常发生,系统会耗费资源去处理,本身对内存和CPU就会有消耗。

所以不能因为有了try/catch而不顾代码质量,要尽量保证没有大量的异常出现,我在项目中见过大量空指针异常出现,这种异常我们可以在代码层面就直接避免。

2.7 频繁GC

频繁的GC也会对性能造成影响,严重的会导致卡顿或者ANR。

频繁GC原因有两个

内存抖动,大量的对象被创建又在短时间内马上被释放
瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加 Heap的压力,从而触发更多其他类型的GC。
在项目中避免短时间内突然创建大量的对象。

2.8 设备&应用基本信息

获取设备&应用基本信息,比如说包名、IMEI信息等等,是比较耗时的操作,可以进行一些优化。

固定信息获取采用缓存策略,例如版本号、版本名称、手机mac地址、运营商信息等等,一次获取,缓存到内存,下次直接使用
不部分不固定信息,例如网络情况(2G/3G/4G/WIFI),可以采用监听网络变更方式,来即时更新网络情况
其他不固定信息,比如基站信息,只能每次获取

3. 总结

关于性能优化方面知识,官方也出过视频Android Performance Patterns,网上也有很多翻译的文章,写得都比较详细,这篇文章是自身从平时一些常见点入手做的一些总结,希望对大家有帮助。

360°Android app全方位性能调优.png

Android前沿技术.png

职业生涯规划.png

欢迎加入Android高级开发交流群;457848807。进群可免费领取一份最新技术大纲和以上Android进阶资料。请备注简书

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容