❤️Androd 性能优化之布局优化❤️

简介: Android的布局管理器本身就是个UI组件,所有的布局管理器都是ViewGroup的子类,而ViewGroup是View的子类,所以布局管理器可以当成普通的UI组件使用,也可以作为容器类使用,可以调用多个重载addView()向布局管理器中添加组件,并且布局管理器可以互相嵌套,当然不推荐过多的嵌套 (如果要兼容低端机型,最好不要超过5层)。

  Android的布局管理器本身就是个UI组件,所有的布局管理器都是ViewGroup的子类,而ViewGroup是View的子类,所以布局管理器可以当成普通的UI组件使用,也可以作为容器类使用,可以调用多个重载addView()向布局管理器中添加组件,并且布局管理器可以互相嵌套,当然不推荐过多的嵌套 (如果要兼容低端机型,最好不要超过5层)。

微信图片_20220523232718.png



🔥 布局层级管理


       让咱们一起了解一下每当系统绘制一个布局时,都会发生一些什么。这一过程由两个步骤完成:


💥 绘制(Measurement)


  • 1:根布局测量自身。


  • 2:根布局要求它内部所有子组件测量自身。


  • 3:所有自布局都需要让它们内部的子组件完成这样的操作,直到遍历完视图层级中所有的View。


💥 摆放(Positioning)


  • 1:当布局中所有的View都完成了测量,根布局则开始将它们摆放到合适的位置。


  • 2:所有子布局都需要做相同的事情,直到遍历完视图层级中所有的View。


💥 背景设置产生的过度绘制


  • 组件背景:每个组件每设置一次背景, 该组件的区域就会增加一层绘制 , 如 LinearLayout 设置背景颜色 , 里面的 TextView 设置背景颜色 , 都会增加该组件区域内的过渡绘制 ;


  • 主题背景:Activity 界面的主题背景,会增加一次 GPU 绘制 ;


       不要随意给布局中的 UI 组件设置背景 。如 ImageView 设置一张图片,会增加一次绘制 ,再给该 ImageView 组件设置背景颜色, 那么又会增加一次绘制, 那么该 ImageView 组件肯定过渡绘制了。


💥 小结


       当某个View的属性发生变化(如:TextView内容变化或ImageView图像发生变化),View自身会调用View.invalidate()方法(必须从 UI 线程调用),自底向上传播该请求,直到根布局(根布局会计算出需要重绘的区域,进而对整个布局层级中需要重绘的部分进行重绘)。布局层级越复杂,UI加载的速度就越慢。因此,在编写布局的时候,尽可能地扁平化是非常重要的。


       FrameLayout和TableLayout有各自的特殊用途,LinearLayout 和 RelativeLayout 是可以互换的,ConstraintLayout和RelativeLayout类似。也就是说,在编写布局时,可以选择其中一种,咱们可以以不同的方式来编写下面这个简单的布局。


🔥 小实验(多种方式实现同一布局)


微信图片_20220523232845.png


💥 LinearLayout


       第一种方式是使用LinearLayout,虽然可读性比较强,但是性能比较差。由于嵌套LinearLayout会加深视图层级,每次摆放子组件时,相对需要消耗更多的计算。


<?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="match_parent"
    android:orientation="vertical">
    <View
        android:id="@+id/view_top_1"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/color_666666"/>
    <View
        android:id="@+id/view_top_2"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:background="@color/teal_200"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <View
            android:id="@+id/view_top_3"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@color/color_FF773D"/>
        <View
            android:id="@+id/view_top_4"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@color/purple_500"/>
    </LinearLayout>
</LinearLayout>


LinearLayout视图层级如下所示:


微信图片_20220523232941.png


💥 使用RelativeLayout


       第二种方法使用RelativeLayout,在这种情况下,你不需要嵌套其他ViewGroup,因为每个子View可以相当于其他View,或相对与父控件进行摆放。


1.<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:id="@+id/view_top_1"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/color_666666"/>
    <View
        android:id="@+id/view_top_2"
        android:layout_width="200dp"
        android:layout_below="@id/view_top_1"
        android:layout_height="100dp"
        android:background="@color/teal_200"/>
    <View
        android:id="@+id/view_top_3"
        android:layout_width="100dp"
        android:layout_below="@id/view_top_2"
        android:layout_height="100dp"
        android:background="@color/color_FF773D"/>
    <View
        android:id="@+id/view_top_4"
        android:layout_width="100dp"
        android:layout_below="@id/view_top_2"
        android:layout_toRightOf="@id/view_top_3"
        android:layout_height="100dp"
        android:background="@color/purple_500"/>
</RelativeLayout>


RelativeLayout视图层级如下所示:


微信图片_20220523233030.png


       通过两种方式,可以很容易看出,第一种方式LinearLayout需要3个视图层级和6个View,第二种方式RelativeLayout仅需要2个视图层级和5个View。


       当然,虽然RelativeLayout效率更高,但不是所有情况都能通过相对布局的方式来完成控件摆放。所以通常情况下,这两种方式需要配合使用。


     注意:为了保证应用程序的性能,在创建布局时,需要尽量避免重绘,布局层级应尽可能地扁平化,这样当View被重绘时,可以减少系统花费的时间。在条件允许的情况下,尽量的使用RelativeLayout和ConstraintLayout,而非LinearLayout,或者用GridLayoutl来替换LinearLayout。


       咱们最常使用的是ViewGroup是LinearLayout,只是因为它很容易看懂,编写起来简单,所以它就成了Android开发的首选。出于这个原因,Google推出了一个全新的ViewGroup。在适当的时候时候使用它,可以减少冗余,它就是网格布局GridLayout下。


🔥 布局复用(和  )


       Android 提供了一个非常有用的标签。在某些情况下,当你希望在其他布局中用一些已存在的布局时, 标签可通过制定相关引用ID,将一个布局添加到另一个布局。


       比如自定义一个标题栏,那么可以按照下面的方式,创建一个可重复用的布局文件。


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <View
        android:id="@+id/view_top_1"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/color_666666"/>
</RelativeLayout>


接着,将标签放入相应的布局文件中,替换掉对应的 View:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include layout="@layout/include_layout"/>
    ...
</RelativeLayout>


这么一来,当你希望重用某些View时,就不用复制/粘贴的方式来实现,只需要定义一个layout文件,然后通过 引用即可。


       但是这样做,可能会引入一个冗余的ViewGroup(重用的布局文件的根视图)。为此,Android 提供了另一个标签,用来帮我们减少布局冗余,让层级变得更加扁平化。我们只需要将可重用的根视图,替换为 标签即可。如下所示:


<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <View
        android:id="@+id/view_top_1"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/color_666666"/>
</merge>


  如此一来,就没有了冗余的视图控件,因为系统会忽略标签,并将标签中的视图直接放置在相应的布局文件中,替换标签。


       注意:使用此标签时,需要记住它的两个主要限制:


       1、它只能作为布局文件的跟来使用。


       2、每次调用LayoutInflater.inflate()时,必须为布局文件提供一个View,作为它的父容器:LayoutInflater.from(this).inflate(R.layout.merge_layout,parent,true);


🔥 ViewStub


       ViewStub是一个不可见的零大小View,可以作为一个节点被加入布局文件,但他关联的布局,知道运行时通过调用 ViewStub.inflate() 或 View.setVisibility(View.VISIBLE) 方法,才会被绘制。


       先看效果图:


微信图片_20220523233345.gif


💥 ViewStub 设置


        <ViewStub android:id="@+id/viewStub"
            android:inflatedId="@+id/subTree"
            android:layout="@layout/activity_imageview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />


💥 显示


       上方 ViewStub 所关联的布局 activity_imageview 并不会被实例化(不要调用布局内的控件,因为还没加载会报空指针异常),只有程序在运行期间调用了以下方法:


findViewById(R.id.viewStub).setVisibility(View.VISIBLE);
((ViewStub)findViewById(R.id.viewStub)).inflate();


在这期间不要调用关联布局内的控件,因为还没呗加载没有


       一旦 ViewStub 变成 visible 或者被 inflate,它便不再可用(Id:viewStub没了),因为它在布局层级中的位置已经实例化出来的布局所替代,因为不能被访问,而应该使用 android:inflatedId 属性中的ID。如下:


    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_view:
                break;
            case R.id.btn_scheme:
                //加载,选择一种即可。
                findViewById(R.id.v_stud).setVisibility(View.VISIBLE);
                //((ViewStub)findViewById(R.id.v_stud)).inflate();
                //加载后layout所用ID
                subTree  = findViewById(R.id.subTree);
                findViewById(R.id.btn_iv_basis).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this,"我是 ViewStud 加载的控件",Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case R.id.btn_invisible:
                subTree.setVisibility(View.INVISIBLE);
                break;
            case R.id.btn_visible:
                subTree.setVisibility(View.VISIBLE);
                break;
            case R.id.btn_init:
                //ViewStub变为可见后再次调用会报空指针,因为id:viewStub 已经不存在了。
                View viewStub  = findViewById(R.id.viewStub);
                viewStub.setVisibility(View.GONE);
                break;
        }
    }


💥 小结


       ViewStub 非常有用,我们可以通过 ViewStub 来延迟部分 View 的加载,缩短首次加载时间,以及减少一些不必要的内存分配。



🔥 自定义组件优化


在自定义View时需要注意,避免犯以下的性能错误:


  • 在非必要时,对View进行重绘。


  • 绘制一些不被用户所看到的的像素,也就是过度绘制。(被覆盖的地方)


  • 在绘制期间做了一些非必要的操作,导致内存资源的消耗。


💥 优化


  • View.invalite()是最最广泛的使用操作,因为在任何时候都是刷新和更新视图最快的方式。


      在自定义View时要小心避免调用非必要的方法,因为这样会导致重复强行绘制整个视图层级,消耗宝贵的帧绘制周期。检查清楚View.invalite()和View.requestLayout()方法调用时间位置,因为这会影响整个UI,导致GPU和它的帧速率变慢。


  • 避免过渡重绘。为了避免过渡重绘,我们可以利用Canvas方法,只绘制控件中所需要的部分。整个一般在重叠部分或控件时特别有用。相应的方法是Canvas.clipRect()(指定要被绘制的区域);


  • 在实现View.onDraw()方法中,不应该在方法内及调用的方法中进行任何的对象分配。在该方法中进行对象分配,对象会被创建和初始化。而当View.onDraw()方法执行完毕时。垃圾回收器会释放内存。如果View带动画,那么View在一秒内会被重绘60次。所以要避免在View.onDraw()方法中分配内存。


      永远不要在View.onDraw()方法中及调用的方法中进行内存分配,避免带来负担。垃圾回收器多次释放内存,会导致卡顿。最好的方式就是在View被首次创建出来时,实例化这些对象。


       布局优化到这里就结束了,还是那句话后面如果有更好的方案会及时的添加进去,如果有老大有其他方案也可以留言哈,感谢!最后别忘记关注我撒。



相关文章
|
5月前
|
SQL 缓存 安全
《LangChain 智能体从浅入门到深入门:模型配置、中间件体系、装饰器钩子与 invoke 调用模式全解析部分内容指南分享》(如有错误欢迎指正!)
《LangChain 智能体从浅入门到深入门:模型配置、中间件体系、装饰器钩子与 invoke 调用模式全解析部分内容指南分享》
553 11
|
8月前
|
人工智能 安全 数据可视化
面向业务落地的AI产品评测体系设计与平台实现
在AI技术驱动下,淘宝闪购推进大模型应用落地,构建覆盖“评什么、怎么评、如何度量”的全链路评测体系。面对研发模式变革与Agent复杂性挑战,平台以端到端评测为主、分层测评为辅,打造可回放环境、多裁判机制及变更分级策略,实现质量与效率平衡。已支撑10+部门、90+AI产品,沉淀千余评测集,问题解决率超80%。未来将拓展多模态评测、可视化标注与插件市场,推动评测生态化发展。
|
9月前
|
传感器 人工智能 编解码
2025年11月,全球数字人技术竞技场与数字化应用技术指南
2025年,全球数字人技术进入多维竞技时代。中美中东等地在技术深度、场景广度与生态厚度上全面比拼,推动数字人从“形似”到“神似”、从营销工具到产业赋能、从技术单打独斗到价值共生的跃迁,掀起一场重塑产业与人文交互的创新浪潮。
|
9月前
|
人工智能 JSON 供应链
1688评论接口实战:B端视角下的竞品(供应商)数据拆解指南
本文详解1688评论接口(alibaba.trade.rate.get)在B2B电商中的核心应用,涵盖权限获取、技术调用、数据解析及实战场景,助力企业通过真实采购评论实现供应商评估、风险预警与供应链优化,推动B端决策从经验驱动迈向数据驱动。
|
11月前
|
人工智能 搜索推荐 测试技术
人们如何使用ChatGPT
OpenAI发布《人们如何使用ChatGPT》论文,揭示截至2025年7月,周活用户超7亿,70%对话为非工作用途。三大主要场景为实用指导、信息查询与写作,其中写作是工作核心,占工作相关消息42%。用户互动以“询问”为主,占比49%,增长最快且满意度高。年轻用户(18-25岁)贡献近半消息,性别差距已缩小,女性用户过半。低收入国家增速快,高学历者更用于工作。编程仅占4.2%,情感类交流占1.9%。报告指出,ChatGPT正从生产力工具转向日常生活助手,创造广泛社会价值。
780 2
|
前端开发 JavaScript 安全
剖析跨域问题始末及其解决方案——前端必备交叉知识(一)
跨域问题是前端开发中的常见挑战,了解并掌握不同的跨域解决方案能帮助你更高效地进行开发工作。本文对同源策略、跨域以及解决跨域的三种方案: CORS、JSONP、代理等跨域技术进行了介绍。选择合适的跨域解决方案非常重要。 在实际开发中,推荐优先考虑使用 CORS,因为它是现代浏览器支持的标准,且安全性较高。如果服务器无法修改,则可以考虑使用代理。如果是特殊情况,可以使用 JSONP,但要注意安全性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错
|
编解码 人工智能 缓存
通义万相重磅升级,成功登顶VBench,阿里云百炼邀您第一时间体验
阿里云通义万相推出2.1视频生成模型,大幅提升复杂运动、物理规律遵循及艺术表现,在权威评测VBench中夺冠。新模型采用自研VAE和DiT架构,增强时空上下文建模,实现更稳定的大幅度肢体运动和多对象生成。通义万相支持中英文文字特效生成,满足广告设计、短视频等创作需求,并在阿里云百炼平台开放API调用,提供免费试用资源。
2357 0
|
分布式计算 监控 Hadoop
监控Hadoop集群实战篇
介绍了监控Hadoop集群的方法,包括监控Linux服务器、Hadoop指标、使用Ganglia监控Hadoop集群、Hadoop日志记录、通过Hadoop的Web UI进行监控以及其他Hadoop组件的监控,并提供了相关监控工具和资源的推荐阅读链接。
459 2
|
机器学习/深度学习 算法 大数据
神经网络之超参数
超参数在神经网络的设计和训练中起着至关重要的作用。它们是在开始训练之前设置的参数,与网络的结构、训练过程和优化算法有关。正确的超参数选择对于达到最优模型性能至关重要。
825 1
|
存储 Linux 数据安全/隐私保护