Android优化UI篇

学习的过程中善于总结才能快速提升个人的水平,特别作为程序员。项目优化也是很重要的一部分,最近寻思着写一篇文章总结,不论是对以后的开发或者提升自身的水平都有用。在写这篇文章之前,结合自身项目经验网上多篇博客,这里做个总结,特别一些细节的原理,我也会在文末附上相关的链接,虽然有时候不需要了解原理。关于UI方面的优化不能光看一遍就完事了,主要还是开始时主动去培养这样的习惯,本篇的顺序主要是依照UI优化重要等级写的。

过度绘制

大家应该都了解过,简单说一下,接下来主要讲解我们实际开发中怎么去避免这种现象。Overdraw(过度绘制)是指屏幕上的某个像素在同一帧的时间内被绘制了多次。


过度绘制.png

如果当前区域被绘制两次,就是过度绘制一次,以此类推,每绘制一次都会消耗性能CPU、GPU、还有电量等,所以作为开发就是尽量减少同一区域绘制次数。
过度绘制主要成因如下:

1、 由于布局复杂造成嵌套布局
2、布局中的view设置多层背景颜色

布局嵌套
  • 使用merge标签
  • 使用RelativeLayout和ConstraintLayout(加强版RelativeLayout)
  • 自定义Viewgroup,重写layout方法
多层背景

我这里会分为大概三种情况:

  • 单独一个view,如果设置了一个前景图片或者颜色,一定不要设置背景了,这种情况大部分出现在imageview中
  • 对于viewgroup,如果不是特别需要,只设置最底层view的背景色
  • 所有的Activity都有默认的一层背景,默认主题颜色,这也是冷启动出现黑白屏根源,如果是launcher类型Activity,可以设置一张图片或者颜色的drawable或者xml(建议不要这样设置,xml解析会耗时),并且launcher类型Activity启动之后要情况背景,除去内存占用。
    launcher类:
<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@drawable/launch_screens</item>
</style>

或者如下(不建议):

<style name="SplashTheme" parent="@style/Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowDisablePreview">true</item>
</style>

设置加载之后在oncreat()方法中再设置如下除去内存占用

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    //将window的背景图设置为空
    getWindow().setBackgroundDrawable(null);
    super.onCreate(savedInstanceState);
}

如果是普通Activity,则可以:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setBackgroundDrawable(null);
    }

参考链接:https://juejin.im/post/5bf8f90a518825396d71ff2a

常用布局性能比较

几种viewgroup性能对比.png

通过上图总结几点:

  • 相同层级效果,优先级
    FrameLayout>=LinearLayout>RelativeLayout>ConstraintLayout
  • 对于负责UI,首先优化考虑层级嵌套问题,优先使用ConstraintLayout和RelativeLayout
  • 使用LinearLayout时,慎用weight属性和measureWithLargestChild属性,会引起2次渲染
  • 尽量减少ConstraintLayout和RelativeLayout中太多无效依赖,可以减少不必要控件的刷新

自定义view优化

(还有需要总结的地方)

onDraw

自定义View里面最重要,也是优化的最重要部分。

  • ondraw方法里面尽量不要去初始化对象,初始化过程放在构造方法里,在ondraw方法里面初始变量,如果需要invalidate(),则会一直调用ondraw(),会导致内存一直创建和回收,造成内存抖动。

  • 不要频繁的设置可见于不可见方法,这样也会调用,尽量在xml设置的就是默认的状态,减少ondraw方法调用。

  • 尽量在View的内容发生改变的时候才去触发invalidate方法

  • 尽量使用ClipRect等方法来提高绘制的性能(重叠的部分不去绘制)。

  • 减少不必要元素的绘制

  • 不在屏幕的元素尽量使用Canvas.quickReject把他们给剔除

onLayout

任何时刻对View调用requestLayout()方法,都需要遍历整个View树,确定每个视图它们所占用的大小。如果在measure过程中有任何冲突,可能会多次遍历。
如果UI设计师给的效果过于复杂,就需要自定义onlayout 方法,并且可以减少嵌套问题,优化measure过程。

clipRect()

该方法用于裁剪画布,调用clipRect()方法后,只会显示被裁剪的区域,之外的区域将不会显示。
该方法最后有一个参数Region.Op,表示与之前区域的区域间运算种类,如果没有这个参数,则默认为Region.Op.INTERSECT
这几个参数的意义为:

  • DIFFERENCE是第一次不同于第二次的部分显示出来
  • REPLACE是显示第二次的
  • REVERSE_DIFFERENCE 是第二次不同于第一次的部分显示
  • INTERSECT交集显示
  • UNION全部显示
  • XOR补集 就是全集的减去交集生育部分显示
    注意:
    clipxx方法只对设置以后的drawxx起作用,已经画出来的图形,是不会有作用的。

特殊标签

include

include标签常用于将布局中的公共部分提取出来,比如页面所有的actionbar那么就可以直接include进去了。
注意事项:

  • 外层可以设置宽高,外层优于内层
  • 外层可以设置id,外层id优先级高于内层id
  • 如果一个布局中引用两个id相同的include,则第一个有效,第二个无效,但是两者都会显示。
merge:

merge标签是作为include标签的一种辅助扩展来使用,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。Android渲染需要消耗时间,布局越复杂,性能就越差。如上述include标签引入了之前的LinearLayout之后导致了界面多了一个层级。
注意事项:

  • merge必须放在布局文件的根节点上。
  • merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
  • merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下。
  • 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。
  • 如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点。
  • 自定义View如果继承LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层结点。
  • 因为merge不是View,所以对merge标签设置的所有属性都是无效的。
  • 如果引用到外层的是LinearLayout ,merge内部方向跟随LinearLayout 设置的方向
ViewStub:

ViewStub 直接继承自View,默认是不可见的,没有measure过程,只有加载的时候才会由加载的xml替换掉(ViewStub只能inflate一次,再次进行inflate的时候会报异常)或者设置为Visibility时才可见。最大特点就是使用才加载。
这里简述一个常用的使用场景,有时候页面没有数据,会显示无数据页面或者网络异常页面,这种情况就很好的利用了使用时才去加载。

注意事项:

  • ViewStub只能被Inflate一次,inflate之后ViewStub对象就会被置为空
  • ViewStub只能用来Inflate一个布局文件,而不是某个具体的View(可以把View写在某个布局文件中)
  • android:id——ViewStub 自身的Id,无论是否被inflate,都可以通过findViewById拿到对应的ViewStub控件本身。
  • android:inflatedId——ViewStub对用的layout里面根节点的id,inflate之后可以通过findViewById获取到对应的被映射的布局对象
  • viewStub.inflate()之后,如果要显示或者隐藏布局跟普通view一样,但是千万不要再一次.inflate()(会报异常)。
  • 在xml 中定义ViewStub 节点时,内部不能包含其他节点,也就是说,ViewStub 是一个自闭合节点,如果一个布局view如果想通过ViewStub显示,只能定义在单独的xml 文件中。
ViewStub于Gone的对比

设置为GONE的View不会占用布局空间,但是会进行类的初始化;如ImageView 将src设置为一个BitmapDrawable,那么该图片将会加载到内中
ViewStub只有在代码中进行inflate之后才会加载进来,不会占用内存。
性能对比:
https://blog.csdn.net/wolinxuebin/article/details/82726517

space

Space 经常用于组件之间的缝隙,其draw()为空,减少了绘制渲染的过程。组件之间的距离使用 Space 会提高了绘制效率,特别是对于动态设置间距会很方便高效。因为draw()为空,对该 view 没有做任务绘制渲染,所以不能对 Space 设置背景色,如果需要的间隔需要设置颜色是明显不合适的。Space 相对于View设置间距的好处是不用draw,缺点是不能设置背景色。

其他
  • 如果初始化View不可见的时候,使用View.GONE代替View.VISIBLE,设置GONE的view会加载的时候标记,不会去measure过程。

  • 减少alpha值对性能的影响
    对于不透明的View,显示它只需要渲染一次即可,可是如果这个View设置了alpha 值,会至少需要渲染两次。原因是包含alpha的view需要事先知道混合View的下一层元素是什么,然后再结合上层的View进行Blend混色处理,并且对于设置。

  • TextView设置文字和图片减少View的解析加载

  • 使用TextView设置换行功能,例如:

<TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="第一行\第二行" />
  • 对于确定宽高的View或者ViewGroup,尽量使用固定的宽高或者match_parent,例如:recyclerview如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源;

附言

UI渲染
https://blog.csdn.net/lmj623565791/article/details/45556391/
UI卡顿优化
https://blog.csdn.net/joye123/article/details/79425398
UI优化工具Lint
https://blog.csdn.net/luzhenyuxfcy/article/details/79398761
UI优化工具Hierarchy Viewer
https://www.jianshu.com/p/e9e05ce5b0c9
https://github.com/romainguy/ViewServer

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

推荐阅读更多精彩内容