复杂UI卡顿问题没想到还能这么优化?
点击上方 "程序员小乐"关注, 星标或置顶一起成长
关注订阅号「程序员小乐」,收看更多精彩内容
每日英文
Be yourself, don\'t change for anyone. If they don\'t like you at your worst, then they don\'t deserve you at your best.
勇敢的做自己,不要为任何人而改变。如果他们不能接受最差的你,也不配拥有最好的你。
每日掏心话
没有人可以带走时间,时间却可以带走任何人。春有春的温暖,夏有夏的火热,秋有秋的收获,冬有冬的寒冷,学会享受生活!
作者:普通的程序员 | 责编:乐乐
链接:jianshu.com/u/38b409cda5af
程序员小乐(ID:study_tech)第 1017 次推文
往日回顾:真脸红了!羞羞的报告:2020年轻人性爱数据报告
正文
/ 背景及目标 /
最近在做性能优化工作,代码实际上已经经过几代人的优化,已经做了大量的 按需加载(懒加载),布局ViewStub优化,层级优化,代码质量也很高,检查了没有多余的耗时操作(业务上的无法避免的数据请求带来的耗时操作已尽可能减少)。
但是因为涉及的行业特殊,整个业务很复杂,界面里充满大量的自定义view。现在就是通过工具查到了其中某个方法,该方法会解析某个布局(布局内有n个自定义view),测试会卡住主线程800+ms,现在就需要对这个方法进行优化。
/ 理论基础 /
需要至少对以下三点基础理论有了解。
切换线程的基础技能;
官方提供的AsyncLayoutInflater
ViewStub,merge操作及View初始化操作
/ 流程分析 /
大家都知道在Android中,只能在主线程操作UI原因是:
具体这个mThread是什么时候传入的,这个checkTread方法什么时候调用的,大家去看ViewRootImpl的源码,一步一步跟,这里不赘述。
那博主今天在这里吹什么牛比呢?
『只能在主线程操作UI』 这句话你细品,假如我不操作UI,只inflate View行不行?答案当然是可以的,官方提供的AsyncLayoutInflater 就是这样的操作。内部实现很简单,把需要加载的 Layout.xml的包装成一个任务,内部线程inflate解析,解析完毕再通过handler通知到主线程。
换句话说,操作UI的行为,是指要内部调用到checkThread的行为。由于AsyncLayoutInflater只在高于API 24的版本有用,那么我们就借助这个思路,仿照它来完成性能优化工作。
搜索公众号程序员小乐回复关键字“Java”,获取Java面试题和答案。
/ 具体操作 /
我们以一个简单的demo来模拟这个需求,顺便看看源码。
就一个MainActivty, 主界面布局一个button,按一下把功能view加载到主界面。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><Buttonandroid:id="@+id/btn"android:layout_width="100dp"android:layout_height="50dp"android:gravity="center"android:text="Hello World!" /><ViewStubandroid:id="@+id/viewStub"android:layout="@layout/realview"android:layout_width="match_parent"android:layout_height="200dp" />
</LinearLayout>
我们假设这个realview是一个巨复杂的view(或者里面的自定义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"><Viewandroid:layout_width="200dp"android:layout_height="200dp"android:background="#fcc" />
</RelativeLayout>
就这么一个例子,点击按钮加载viewStub。
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {addViewInNormalWay();}});}public void addViewInNormalWay(){ViewStub viewStub = findViewById(R.id.viewStub);viewStub.inflate();}
}
假如我们直接开子线程把这个viewStub,100%抛出异常。
Schedulers.io().scheduleDirect(new Runnable() {@Overridepublic void run() {addViewInNormalWay();}});
原因是ViewStub的inflate()方法内部,replaceSelfWithView()调用了requestLayout,这部分checkThread。
我们借助AsyncLayoutInflater的思想,把ViewStub.inflate()内部拆开,inflateViewNoAdd()放到子线程解析,解析完毕再回到主线程替换目标view。但是这样需要对布局进行更改:
原始的ViewStub占位需要替换成View。
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {addView();}});}private void addView() {Disposable d = Single.create(new SingleOnSubscribe<View>() {@Overridepublic void subscribe(SingleEmitter<View> emitter) throws Exception {View view = getLayoutInflater().inflate(R.layout.realview, null);emitter.onSuccess(view);//只做inflated 解析xml的操作}}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<View>() {@Overridepublic void accept(View view) throws Exception {View stub = findViewById(R.id.viewStub);if (stub == null) return;ViewGroup parent = (ViewGroup) stub.getParent();int index = parent.indexOfChild(stub);//找到原始占位viewViewGroup.LayoutParams vlp = stub.getLayoutParams();//拿到lpview.setLayoutParams(vlp);//把lp给到新viewparent.removeViewAt(index);//从树里删除parent.addView(view, index);//替换上去}});}
}
这里有个细节,LayoutInflater.inflate()一共有4种。
建议root传null,不传null的话,attach一定要传false。因为realview的顶层layout 宽高属性会丢失,补救策略就是再套一层layout,或者在外部view就指定宽高属性。
搜索公众号程序员小乐回复关键字“offer”,获取算法面试题和答案。
/ 风险提示 /
原来的ViewStub要替换成一个占位View,这样就会破坏原有的布局优化策略;被inflate的View,根标签不能用merge,原因去看inflate源码;不保证你的功能view里面有奇奇怪怪的操作,这些都会导致子线程解析失败。
这些奇奇怪怪的操作有(包括但不限于):
在异步inflated的布局,其 parent 的 generateLayoutParams 函数必须要是线程安全的;
所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.loop, 因为子线程默认没有 Looper.prepare()。补救措施,找到出错的自定义view,初始化handler请加上 Looper.getMainLooper() 参数;
实现了诸如GestureDetector组件,这些组件内部会初始化handler,报错原因见第二条,补救措施,在自定义view里,对这些组件的初始化请切换到主线程。
/ 最后 /
谷歌不推荐在子线程操作UI的原因有很多,比如多线程多次inflate ,风险还是有的。现在找到卡顿的原因就是 inflate自定义view耗时,采用这套方案后该方法耗时120ms。风险跟收益,各位自己评估。
欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。
猜你还想看
阿里、腾讯、百度、华为、京东最新面试题汇集
百度网盘突然大调整,网友炸了!!
Redis 6.0 除了多线程,别忘了这个牛逼特性!
如何设计一个安全的对外接口?
嘿,你在看吗?
复杂UI卡顿问题没想到还能这么优化?相关推荐
- iOS性能优化-UI卡顿检测
前言 在实现需求的同时,能写出既优雅性能又高效的代码是每个开发者都在追求的目标,但是在实际开发中,随着每个版本需求的迭代,功能变得越来越复杂,加上开发者的意识不够或者一时疏忽,日渐复杂的工程很容易产生 ...
- 【iOS高级资深工程师面试篇】①、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 UI部分2/3 -图像显示原理-UI卡顿掉帧
iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...
- Android UI性能优化 检测应用中的UI卡顿
本文已在我的公众号hongyangAndroid首发. 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/58626355 本文出自 ...
- Android的UI卡顿
这篇文章我们主要从3个方面分析: 1.UI卡顿的原理 2.UI卡顿的原因分析 3.卡顿的总结 来看第一部分,UI卡顿的原理: 先来看下这样一个数字; 60fps -> 16ms 其实用户所感受到 ...
- Android面试系列文章2018之内存管理之UI卡顿篇
Android面试系列文章2018之内存管理之UI卡顿篇 1.UI卡顿的原理 60ftp –> 16ms: Android系统每隔16ms都会对界面进行渲染一次,造成卡顿的原因就是Andro ...
- c# 多线程界面卡顿_多线程同步等候 导致主界面UI卡顿,求解~
多线程同步等待 导致主界面UI卡顿,求解~~~ 描述如下,有N个用户,我执行如下操作逻辑, FOR第一个循环,开 N个线程执行登陆操作,执行完毕后 (线程同步后),执行拨号操作,由于我使用线程同步,导 ...
- android gridview滑动卡,ViewPager嵌套GridView引发的一系列UI卡顿不顺畅的问题
由于项目要求,需要做一个日历的页面,做法采用ViewPgaer+GridView来做,初步过程很顺利,成型以后,出现UI现象有(1)Acitivity加载时间过长 (2)滑动Viewpager出现卡顿 ...
- Android UI优化—从Android渲染原理理解UI卡顿
Android渲染机制 1.Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染 2.渲染的过程是由CPU与GPU协作完成 如下图: 为什么是16ms? 1.人眼与大脑之间的协作无法感 ...
- 鸿蒙排队领钱是什么项目,腾讯工程师:鸿蒙OS这个骗钱的老套路没想到还能行得通,真不愧是华为...
原标题:腾讯工程师:鸿蒙OS这个骗钱的老套路没想到还能行得通,真不愧是华为 国产手机这五六年来发展确实很快,虽然有很多品牌已经倒下,但挤进全球前十的品牌大多数是国产手机.现在国产手机拥有越来越多自己的 ...
- 即便学会了Markdown,也没想到还可以这么干 —> 学会==提高效率
前言: 不知不觉,已踏上水文之路:为了满足读者的观感,决心学习一下推文排版技巧,这篇文章就总结一下; 文章目录如下 文章目录 写作前期 trello 写作中期 typora+iPic+坚果云 iPic ...
最新文章
- 优秀的Java程序员应具备哪些编程技术?
- linux下测试磁盘的读写IO速度-简易方法
- 天冷了,大家如果有往年的不穿的衣服别扔,寄给需要的人好吗?
- Syn Bot /OSCOVA 上下文(8)
- 计算机专业论文关于天气预报的,关于天气预报论文范文写作 天气预报相关论文写作资料...
- 操作系统欢乐笔记-01-带你推开操作系统的大门(雾)
- linux c 子线程sleep,linux c之sleep的多种实现
- java nodefilter类,Java Servlet Filter过滤器概述
- sql server权限_保护SQL Server审核的访问权限
- 数论19——高斯消元
- Java8 - Stream API快速入门
- java invokevirtual_Java | 深入理解方法调用的本质(含重载与重写区别)
- linux 虚拟机采用NAT方式共享上网
- CAN LIN CANOE DB9 接口定义
- 【实习之T100开发】帆软报表笔记,Java经典入门教程
- EPUB电子书阅读必备
- 使用百度地图API在页面添加百度地图应用
- UltraLAB台式图形工作站(图形+超算+私有云通吃~图灵超算工作站介绍)
- Ubuntu18.04 sqlite3安装及其应用
- 2.6-2.7 向量运算的基本性质零向量
热门文章
- css加号图标_excel单元格加号展开折叠
- 美通社企业新闻汇总 | 2019.1.21 | 春节访日可享受更多免税优惠;勃林格殷格翰国产化猪疫苗上市...
- LeetCode题解(LCP03):机器人大冒险(Python)
- QNX dumper生成的core文件无法拿到堆栈信息
- 【Python游戏】Python版本的雷霆战机游戏,最新最简单版本来了
- 20200608模拟赛 Endless
- 达梦数据库DM8安装配置和使用
- DDoS攻击器Linux集群【用于网络安全学习】
- html 自动获取屏幕分辨率,HTML5教程 如何获取手机屏幕分辨率和浏览器分辨率
- 简单做份西红柿炒蛋778