点击上方 "程序员小乐"关注, 星标或置顶一起成长

关注订阅号「程序员小乐」,收看更多精彩内容

每日英文

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,现在就需要对这个方法进行优化。

/   理论基础   /

需要至少对以下三点基础理论有了解。

  1. 切换线程的基础技能;

  2. 官方提供的AsyncLayoutInflater

  3. 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里面有奇奇怪怪的操作,这些都会导致子线程解析失败。

这些奇奇怪怪的操作有(包括但不限于):

  1. 在异步inflated的布局,其 parent 的 generateLayoutParams 函数必须要是线程安全的;

  2. 所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.loop, 因为子线程默认没有 Looper.prepare()。补救措施,找到出错的自定义view,初始化handler请加上 Looper.getMainLooper() 参数;

  3. 实现了诸如GestureDetector组件,这些组件内部会初始化handler,报错原因见第二条,补救措施,在自定义view里,对这些组件的初始化请切换到主线程。

/   最后   /

谷歌不推荐在子线程操作UI的原因有很多,比如多线程多次inflate ,风险还是有的。现在找到卡顿的原因就是 inflate自定义view耗时,采用这套方案后该方法耗时120ms。风险跟收益,各位自己评估。

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

百度网盘突然大调整,网友炸了!!

Redis 6.0 除了多线程,别忘了这个牛逼特性!

如何设计一个安全的对外接口?

嘿,你在看吗

复杂UI卡顿问题没想到还能这么优化?相关推荐

  1. iOS性能优化-UI卡顿检测

    前言 在实现需求的同时,能写出既优雅性能又高效的代码是每个开发者都在追求的目标,但是在实际开发中,随着每个版本需求的迭代,功能变得越来越复杂,加上开发者的意识不够或者一时疏忽,日渐复杂的工程很容易产生 ...

  2. 【iOS高级资深工程师面试篇】①、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 UI部分2/3 -图像显示原理-UI卡顿掉帧

    iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...

  3. Android UI性能优化 检测应用中的UI卡顿

    本文已在我的公众号hongyangAndroid首发. 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/58626355 本文出自 ...

  4. Android的UI卡顿

    这篇文章我们主要从3个方面分析: 1.UI卡顿的原理 2.UI卡顿的原因分析 3.卡顿的总结 来看第一部分,UI卡顿的原理: 先来看下这样一个数字; 60fps -> 16ms 其实用户所感受到 ...

  5. Android面试系列文章2018之内存管理之UI卡顿篇

    Android面试系列文章2018之内存管理之UI卡顿篇 1.UI卡顿的原理   60ftp –> 16ms: Android系统每隔16ms都会对界面进行渲染一次,造成卡顿的原因就是Andro ...

  6. c# 多线程界面卡顿_多线程同步等候 导致主界面UI卡顿,求解~

    多线程同步等待 导致主界面UI卡顿,求解~~~ 描述如下,有N个用户,我执行如下操作逻辑, FOR第一个循环,开 N个线程执行登陆操作,执行完毕后 (线程同步后),执行拨号操作,由于我使用线程同步,导 ...

  7. android gridview滑动卡,ViewPager嵌套GridView引发的一系列UI卡顿不顺畅的问题

    由于项目要求,需要做一个日历的页面,做法采用ViewPgaer+GridView来做,初步过程很顺利,成型以后,出现UI现象有(1)Acitivity加载时间过长 (2)滑动Viewpager出现卡顿 ...

  8. Android UI优化—从Android渲染原理理解UI卡顿

    Android渲染机制 1.Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染 2.渲染的过程是由CPU与GPU协作完成 如下图: 为什么是16ms? 1.人眼与大脑之间的协作无法感 ...

  9. 鸿蒙排队领钱是什么项目,腾讯工程师:鸿蒙OS这个骗钱的老套路没想到还能行得通,真不愧是华为...

    原标题:腾讯工程师:鸿蒙OS这个骗钱的老套路没想到还能行得通,真不愧是华为 国产手机这五六年来发展确实很快,虽然有很多品牌已经倒下,但挤进全球前十的品牌大多数是国产手机.现在国产手机拥有越来越多自己的 ...

  10. 即便学会了Markdown,也没想到还可以这么干 —> 学会==提高效率

    前言: 不知不觉,已踏上水文之路:为了满足读者的观感,决心学习一下推文排版技巧,这篇文章就总结一下; 文章目录如下 文章目录 写作前期 trello 写作中期 typora+iPic+坚果云 iPic ...

最新文章

  1. 优秀的Java程序员应具备哪些编程技术?
  2. linux下测试磁盘的读写IO速度-简易方法
  3. 天冷了,大家如果有往年的不穿的衣服别扔,寄给需要的人好吗?
  4. Syn Bot /OSCOVA 上下文(8)
  5. 计算机专业论文关于天气预报的,关于天气预报论文范文写作 天气预报相关论文写作资料...
  6. 操作系统欢乐笔记-01-带你推开操作系统的大门(雾)
  7. linux c 子线程sleep,linux c之sleep的多种实现
  8. java nodefilter类,Java Servlet Filter过滤器概述
  9. sql server权限_保护SQL Server审核的访问权限
  10. 数论19——高斯消元
  11. Java8 - Stream API快速入门
  12. java invokevirtual_Java | 深入理解方法调用的本质(含重载与重写区别)
  13. linux 虚拟机采用NAT方式共享上网
  14. CAN LIN CANOE DB9 接口定义
  15. 【实习之T100开发】帆软报表笔记,Java经典入门教程
  16. EPUB电子书阅读必备
  17. 使用百度地图API在页面添加百度地图应用
  18. UltraLAB台式图形工作站(图形+超算+私有云通吃~图灵超算工作站介绍)
  19. Ubuntu18.04 sqlite3安装及其应用
  20. 2.6-2.7 向量运算的基本性质零向量

热门文章

  1. css加号图标_excel单元格加号展开折叠
  2. 美通社企业新闻汇总 | 2019.1.21 | 春节访日可享受更多免税优惠;勃林格殷格翰国产化猪疫苗上市...
  3. LeetCode题解(LCP03):机器人大冒险(Python)
  4. QNX dumper生成的core文件无法拿到堆栈信息
  5. 【Python游戏】Python版本的雷霆战机游戏,最新最简单版本来了
  6. 20200608模拟赛 Endless
  7. 达梦数据库DM8安装配置和使用
  8. DDoS攻击器Linux集群【用于网络安全学习】
  9. html 自动获取屏幕分辨率,HTML5教程 如何获取手机屏幕分辨率和浏览器分辨率
  10. 简单做份西红柿炒蛋778