Android性能优化 - 卡顿和布局优化

  • 布局可以说是APP最重要的一项了,用户感知极强,无论你的代码写的如何,用户也不知道,用户只能看到和操作APP,更漂亮合理的布局,更流畅的体验才是好APP。
  • 比如微信,操作起来卡,用户只会觉得是手机不行,而不会是微信不行,但其他APP卡,用户就觉得是APP不行,而不是手机不行。┓( ´∀` )┏
  • Android性能优化 - 启动速度优化 也可一起学习。

1.卡顿分析

1.1 刷新率

  • 大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。从设计师的角度,他们希望App能够有更多的动画,图片等时尚元素来实现流畅的用户体验。但是Android系统很有可能无法及时完成那些复杂的界面渲染操作。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
  • 上面那段话就是,APP做的越来越炫酷,动画和视频一大堆,启动就要显示视频广告,这些对于旗舰Android机是无压力的,但对于老手机,或者是入门手机,复杂的页面,计算量大,CPU、GPU处理不过来,也就无法流畅显示了。


    16ms刷新
  • 如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。


    24ms刷新

1.2 PerfDog

腾讯PerfDog,性能监控

  • 下图,在PerfDog,使用华为P30Pro,查看微博的刷新情况,静态的微博内容在不滑动的时候,刷新率就是0fps,快速滑动时,刷新率在60fps左右,还能查看CPU和内存是使用情况。


    微博
  • 下图,而在微博播放视频时,刷新率一直就是60fps左右了。


    微博视频
  • 下图,普通的APP都基本能达到60fps,相机就不是了,相机拍照稳定在30fps,而自拍时,开启美颜,降到24fps了,看来相机加AI美颜是比较吃性能的。

  • 小知识,电影或者是网上看的视频一般是24帧/秒的速度播放的,即可以省性能,效果也不错,索尼A7M3相机可以录制120帧的慢动作,可以做4倍或者5倍升格。


    相机

1.3 CPU Profile

2.布局优化

2.1 过度绘制

  • Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。

  • 蓝色绿色是比较好的情况,红色就是层级较多了,为了实现好看的效果,就会套多层布局,过度绘制的多了消耗性能,对与入门机就会卡顿了。


    过度绘制
  • 手机进入开发人员选项,调试GPU过度绘制,打开显示过度绘制区域。

  • 贝壳APP的布局大多是蓝色绿色的,说明他们APP就没什么过度绘制的情况,非常好。


    贝壳
  • 到下面的列表就会有过度绘制的情况,但区域不大,只有内容的部分。


    在这里插入图片描述
  • 开发人员选项,显示布局边界,可以看到贝壳的布局层级确实不多,也非常的清晰工整。


    贝壳
  • 微博APP的过度绘制区域基本占满整个屏幕了,除了微博还有微信淘宝等列表APP也是大多数红色的,原因可能是列表类的APP,除了父布局,里面还要套RecyclerView,再套itemview,无法避免的过度绘制;但整个item都过度绘制了,贝壳就会比较好一些。


    微博
  • 微博的布局看起来就会复杂一些了。


    在这里插入图片描述

2.2 解决过度绘制

  • 1.上面的微博跟贝壳比较,微博的item是有过度绘制的情况,那么我们在写RecyclerView的时候,如果RecyclerView的父布局、RecyclerView、item三者的背景只要其中一个设置就可以了,没有设置背景就不会渲染,否则就会有过度绘制的情况。
  • 2.父布局套子布局也是尽量只设置其中一个背景,除非没办法都需要背景。
  • 3.子view一般绘制后是会覆盖父view,所以一般选择把背景设置在子view。
  • 4.视图的层级结构能减少就减少,层级越多绘制速度越慢。
  • 5.尽量少设置view的透明度,如果一个view设置了alpha,那他需要知道下面的view是什么内容,再绘制自己,就是过度绘制。如果是文字有透明度,可以在色号里就设置好。

2.3 层级优化

  • Android studiol有布局层级的工具,Layout Inspector,运行起来app后,可以看到每个页面的层级结构。层级太多,肯定就会造成卡顿,启动慢,在启动优化有说,
  • 左边可以看到布局树的具体内容。
    布局树
  • 像ScrollView里面只能放一个ViewGroup,是不可缩减的,但 LinearLayout套LinearLayout 是可以通过ConstraintLayout解决的,约束布局可以说是结合了线性布局跟相对布局的优点,能有效减少层级。
    view tree

2.4 使用merge

  • 我们有一些布局是可以通用的,避免重复代码,就可以使用 include
  • 但是,如果使用 include,但里面的布局又是一个 ViewGroup 的话,就会造成层级过多,这个时候就可以使用 merge 标签了,里面的子view根据会外部include地方的ViewGroup来排列,从而减少层级。
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <include layout="@layout/layout_head" />
</LinearLayout>


<!--  layout_head -->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_head"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="text1"
        android:textSize="16sp" />
 

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="text2"
        android:textSize="16sp" />
</merge>

  • 可以看到使用merge 里面的 view 直接在 LinearLayout 的层级里。

2.5 ViewStub

  • 我们有时根据需求,先把布局画好,然后把 android:visibility 设置成 "invisible" 或者是 "gone"invisiblegone 虽然看不见,但他们还是有初始化,占用这内存和资源,前者还占用着位置。
  • 我们可以使用 ViewStub 来包裹这些需要隐藏显示的 view,它是一个轻量级的view,不可见不占用资源,只有当设置 inflate 时才初始化显示。

<ViewStub
    android:id="@+id/viewStub_title"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:layout="@layout/layout_title"
    app:layout_constraintTop_toTopOf="parent" />


<!-- layout_title -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal">

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/image_back"
        android:padding="12dp"
        android:src="@drawable/ic_back" />

    <TextView
        android:layout_width="0dp"
        android:id="@+id/tv_title"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="title" />
</LinearLayout>
  • 在Activity中 viewStub.inflate() 即可显示,但不可重复调用inflate();否则报异常:
    java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.ViewStub.inflate()' on a null object reference。
viewStub = findViewById(R.id.viewStub_title);
viewStub.inflate();
//之后可以初始化里面的view
ImageView ivBack = findViewById(R.id.image_back);
ivBack.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        finish();
    }
});
TextView tvTitle = findViewById(R.id.tv_title);

3.其他优化

3.1 不要在onDraw里创建对象

  • 我们经常需要自定义view,在 onDraw() 方法里不要创建对象,因为自定义view绘制,会非常频繁的调用 onDraw,虽然方法里的对象创建用完就被回收掉,但频繁的创建销毁对象会导致内存抖动和GC ,而 GC 多了就会卡顿。
    避免ondraw里创建对象

3.2 异步加载布局

Google Asynclayoutinflater库

  • LayoutInflater加载xml布局的过程会在主线程使用IO读取XML布局文件进行XML解析,再根据解析结果利用反射创建布局中的View/ViewGroup对象。这个过程随着布局的复杂度上升,耗时自然也会随之增大。Android为我们提供了 Asynclayoutinflater 把耗时的加载操作在异步线程中完成,最后把加载结果再回调给主线程。
  • Asynclayoutinflater 注意的地方:
  • 1、使用异步 inflate,那么需要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的。
  • 2、所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.myLooper;(因为是在异步线程中加载的,异步线程默认没有调用 Looper.prepare )。
  • 3、AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;。
  • 4、不支持加载包含 Fragment 的 layout。
  • 5、如果 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局。

参考文章

Android性能优化典范

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

推荐阅读更多精彩内容