当前位置:首页 » 安卓系统 » android刷新布局

android刷新布局

发布时间: 2024-10-24 17:33:12

‘壹’ Android invalidate/postInvalidate/requestLayout-彻底厘清

系列文章:
Android Activity创建到View的显示过程
Android Activity 与View 的互动思考
Android invalidate/postInvalidate/requestLayout-彻底厘清
Android 容易遗漏的刷新小细节

前几篇分析了Measure、Layout、Draw 过程,这三个过程在第一次展示View的时候都会调用。那之后更改了View的属性呢?比如更改颜色、更换文字内容、更换图片等,还会走这三个过程吗?循着这个思路,来分析Invalidate/RequestLayout流程。
通过本篇文章,你将了解到:

MyView 默认展示一块红色的矩形区域,暴露给外界的方法:setColor
用以改变绘制的颜色。颜色改变后,需要重新执行onDraw(xx)才能看到改变后的效果,通过invalidate()方法触发onDraw(xx)调用。
接下来看看invalidate()方法是怎么触发onDraw(xx)方法执行的。

invalidate顾名思义:使某个东西无效。在这里表示使当前绘制内容无效,需要重新绘制。当然,一般来说常常简单称作:刷新。
invalidate()是View.java 里的方法。

从上可知,当前要刷新的View确定了刷新区域后即调用了父布局的invalidateChild(xx)方法。该方法为ViewGroup里的final方法。

由上可知,在该方法里区分了硬件加速绘制与软件绘制,分别来看看两者区别:

硬件加速绘制分支
如果该Window支持硬件加速,则走下边流程:

onDescendantInvalidated 方法的目的是不断向上寻找其父布局,并将父布局PFLAG_DRAWING_CACHE_VALID 标记清空,也就是绘制缓存清空。
而我们知道,根View的mParent指向ViewRootImpl对象,因此来看看它里面的onDescendantInvalidated()方法:

做个小结:

用图表示硬件加速绘制的invaldiate流程:

软件绘制分支
如果该Window不支持硬件加速,那么走软件绘制分支:
parent.invalidateChildInParent(location, dirty) 返回mParent,只要mParent不为空那么一直调用invalidateChildInParent(xx),实际上这也是遍历ViewTree过程,来看看关键invalidateChildInParent(xx):

与硬件加速绘制一致,最终调用ViewRootImpl invalidateChildInParent(xx),来看看实现:

做个小结:

用图表示软件绘制invalidate流程:

上述分析了硬件加速绘制与软件绘制时invalidate的不同,它们的最终目的都是为了重走Draw过程。重走Draw过程通过调用scheleTraversals() 触发的,来看看是如何触发的。

想了解更多硬件加速绘制请移步:
Android 自定义View之Draw过程(中)

触发Draw过程
scheleTraversals 详细分析在这篇文章:
Android Activity创建到View的显示过程

三大流程真正开启在ViewRootImpl->performTraversals(),在该方法里根据一定的条件执行了Measure(测量)、Layout(摆放)、Draw(绘制)。
本次着重分析如何触发Draw过程。

可以看出,invalidate 最终触发了Draw过程。

可以看出,启用硬件加速绘制可以避免不必要的绘制。
关于硬件加速绘制与软件绘制详细区别,请移步系列文章:
Android 自定义View之Draw过程(上)

最后,用图表示invalidate流程:

顾名思义,重新请求布局。
来看看View.requestLayout()方法:

可以看出,这个递归调用和invalidate一样的套路,向上寻找其父布局,一直到ViewRootImpl为止,给每个布局设置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED标记。
查看ViewRootImpl requestLayout()

很明显,requestLayout目的很单纯:

和invalidate一样的配方,当刷新信号来到之时,调用doTraversal()->performTraversals(),而在performTraversals()里真正执行三大流程。

由此可见:

之前设置的PFLAG_FORCE_LAYOUT标记有啥用呢?
回忆一下measure 过程:

PFLAG_FORCE_LAYOUT 标记打上之后,会触发onMeasure()测量自身及其子布局。

试想一下,假设View的尺寸改变了,变大了,那么调用了requestLayout后因为走了Measure、Layout 过程,测量、摆放倒是重新设置了,但是不调用Draw出不来效果啊。实际上,View layout时候已经考虑到了。
在View.layout(xx)->setFrame(xx)里

也就是说:

关于measure、layout 过程更深入的分析,请移步:

用图表示requestLayout过程:

结合requestLayout和invalidate与View三大流程关系,有如下图:

总结一下:

上面仅仅说明了单个布局Invalidate/RequestLayout联系,那么如果父布局调用了invalidate,那么子布局会走重绘过程吗?接下来列举这些关系。

子布局Invalidate
如果是软件绘制或者父布局开启了软件缓存绘制,父布局会走重绘过程(前提是WILL_NOT_DRAW标记没设置)。

子布局RequestLayout
父布局会重走Measure、Layout过程。

父布局Invalidate
如果是软件绘制,则子布局会走重绘过程。

父布局RequestLayout
如果父布局尺寸发生了改变,则会触发子布局Measure过程、Layout过程。

在Activity onCreate里创建子线程并展示对话框:

答案是可以的,接下来分析为什么可以。

在分析ViewRootImpl里requestLayout/invalidate过程中,发现其内部调用了checkThread()方法:

问题的关键是mThread是什么?从哪里来?

而创建ViewRootImpl对象是在调用WindowManager.addView(xx)过程中创建的。
关于WindowManager/Window 请移步: Window/WindowManager 不可不知之事

现在回过头来看Dialog创建就比较明朗了:

实际上,"子线程不能更新ui" 更合理的表述应为:View只能被构建了ViewTree的线程操作。只是通常来说,Activity 构建ViewTree的线程被称作UI(主)线程,因此才会有上述说法。

既然invalidate()只能主线程调用(硬件加速条件下,不调用checkThread()),那如果想在子线程调用呢?当然想到的是先通过Handler切换到主线程,再执行invalidate(),但是每次这么写有点冗余,幸好,View里提供了postInvalidate:

切到ViewRootImpl.java

发现了真相:

本文基于Android 10.0

‘贰’ Android 网路请求列表返回逻辑处理

我们在网络请求时,总有分页加载等,处理业务逻辑也是比较混乱的,容易出现各种Bug,下面我这种模式用了很久,记录一下,有任何问题,欢迎指正。

采用 SmartRefreshLayout框架,下拉刷新采用autoRefresh(),上拉加载更多是根据addOnScrollListener()自定义写的。
注意:什么时候加载更多,完全可以自定义。 (本文是滑动到数据一半,开始加载更多。)
1、开始请求数据

2、加载更多请求

3、数据请求完成处理:

4、布局的显示和隐藏:

采用 SmartRefreshLayout框架,下拉刷新采用autoRefresh(),上拉加载更多采用setEnableAutoLoadMore()。
注意:setEnableAutoLoadMore()只有滑到底部才会加载第二页。

‘叁’ [Android] DataBinding 布局里include的databinding刷新不生效

xml布局中,通过include嵌入其他绑定ViewModel的databinding布局,如:

通过刷新headerVM中的ObservableField的方式刷新布局,发现更新未生效。

普通的控件,通过绑定ViewModel的ObservableField的方式,可以实时刷新,如:

更新数据后,重新绑定include的布局和ViewModel并执行databinding的executePendingBindings()方法。

‘肆’ Android性能优化总结

常用的Android性能优化方法:

一、布局优化:

1)尽量减少布局文件的层级。

层级少了,绘制的工作量也就少了,性能自然提高。

2)布局重用 <include标签>

3)按需加载:使用ViewStub,它继承自View,一种轻量级控件,本身不参与任何的布局和绘制过程。他的layout参数里添加一个替换的布局文件,当它通过setVisibility或者inflate方法加载后,它就会被内部布局替换掉。

二、绘制优化:

基于onDraw会被调用多次,该方法内要避免两类操作:

1)创建新的局部对象,导致大量垃圾对象的产生,从而导致频繁的gc,降低程序的执行效率。

2)不要做耗时操作,抢CPU时间片,造成绘制很卡不流畅。

三、内存泄漏优化:

1)静态变量导致内存泄漏   比较明显

2)单例模式导致的内存泄漏 单例无法被垃圾回收,它持有的任何对象的引用都会导致该对象不会被gc。

3)属性动画导致内存泄漏  无限循环动画,在activity中播放,但是onDestroy时没有停止的话,动画会一直播放下去,view被动画持有,activity又被view持有,导致activity无法被回收。

四、响应速度优化:

1)避免在主线程做耗时操作 包括四大组件,因为四大组件都是运行在主线程的。

2)把一些创建大量对象等的初始化工作放在页面回到前台之后,而不应该放到创建的时候。

五、ListView的优化:

1)使用convertView,走listView子View回收的一套:RecycleBin 机制

主要是维护了两个数组,一个是mActiveViews,当前可见的view,一个是mScrapViews,当前不可见的view。当触摸ListView并向上滑动时,ListView上部的一些OnScreen的View位置上移,并移除了ListView的屏幕范围,此时这些OnScreen的View就变得不可见了,不可见的View叫做OffScreen的View,即这些View已经不在屏幕可见范围内了,也可以叫做ScrapView,Scrap表示废弃的意思,ScrapView的意思是这些OffScreen的View不再处于可以交互的Active状态了。ListView会把那些ScrapView(即OffScreen的View)删除,这样就不用绘制这些本来就不可见的View了,同时,ListView会把这些删除的ScrapView放入到RecycleBin中存起来,就像把暂时无用的资源放到回收站一样。

当ListView的底部需要显示新的View的时候,会从RecycleBin中取出一个ScrapView,将其作为convertView参数传递给Adapter的getView方法,从而达到View复用的目的,这样就不必在Adapter的getView方法中执行LayoutInflater.inflate()方法了。

RecycleBin中有两个重要的View数组,分别是mActiveViews和mScrapViews。这两个数组中所存储的View都是用来复用的,只不过mActiveViews中存储的是OnScreen的View,这些View很有可能被直接复用;而mScrapViews中存储的是OffScreen的View,这些View主要是用来间接复用的。

2)使用ViewHolder避免重复地findViewById

3)快速滑动不适合做大量异步任务,结合滑动监听,等滑动结束之后加载当前显示在屏幕范围的内容。

4)getView中避免做耗时操作,主要针对图片:ImageLoader来处理(原理:三级缓存)

5)对于一个列表,如果刷新数据只是某一个item的数据,可以使用局部刷新,在列表数据量比较大的情况下,节省不少性能开销。

六、Bitmap优化:

1)减少内存开支:图片过大,超过控件需要的大小的情况下,不要直接加载原图,而是对图片进行尺寸压缩,方式是BitmapFactroy.Options 采样,inSampleSize 转成需要的尺寸的图片。

2)减少流量开销:对图片进行质量压缩,再上传服务器。图片有三种存在形式:硬盘上时是file,网络传输时是stream,内存中是stream或bitmap,所谓的质量压缩,它其实只能实现对file的影响,你可以把一个file转成bitmap再转成file,或者直接将一个bitmap转成file时,这个最终的file是被压缩过的,但是中间的bitmap并没有被压缩。bitmap.compress(Bitmap.CompressFormat.PNG,100,bos);

七、线程优化:

使用线程池。为什么要用线程池?

1、从“为每个任务分配一个线程”转换到“在线程池中执行任务”

2、通过重用现有的线程而不是创建新线程,可以处理多个请求在创建销毁过程中产生的巨大开销

3、当使用线程池时,在请求到来时间 ,不用等待系统重新创建新的线程,而是直接复用线程池中的线程,这样可以提高响应性。

4、通过和适当调整线程池的大小 ,可以创建足够多的线程以使处理器能够保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或者失败。

5、一个App里面所有的任务都放在线程池中执行后,可以统一管理 ,当应用退出时,可以把程序中所有的线程统一关闭,避免了内存和CPU的消耗。

6、如果这个任务是一个循环调度任务,你则必须在这个界面onDetach方法把这个任务给cancel掉,如果是一个普通任务则可cancel,可不cancel,但是最好cancel

7、整个APP的总开关会在应用退出的时间把整个线程池全部关闭。

八、一些性能优化建议:

1)避免创建过多对象,造成频繁的gc

2)不要过多使用枚举,枚举占用的空间比整型大很多

3)字符串的拼接使用StringBuffer、StringBuilder来替代直接使用String,因为使用String会创建多个String对象,参考第一条。

4)适当使用软引用,(弱引用就不太推荐了)

5)使用内存缓存和磁盘缓存。

‘伍’ android上拉刷新下拉加载 通用框架怎么用

1. 关于下拉刷新

下拉刷新这种用户交互最早由twitter创始人洛伦•布里切特(Loren Brichter)发明,有理论认为,下拉刷新是一种适用于按照从新到旧的时间顺序排列feeds的应用,在这种应用场景中看完旧的内容时,用户会很自然地下拉查找更新的内容,因此下拉刷新就显得非常合理。大家可以参考这篇文章:有趣的下拉刷新,下面我贴出一个有趣的下拉刷新的案例。

2. 实现原理

上面这些例子,外观做得再好看,他的本质上都一样,那就是一个下拉刷新控件通常由以下几部分组成:

【1】Header

Header通常有下拉箭头,文字,进度条等元素,根据下拉的距离来改变它的状态,从而显示不同的样式

【2】Content

这部分是内容区域,网上有很多例子都是直接在ListView里面添加Header,但这就有局限性,因为好多情况下并不一定是用ListView来显示数据。我们把要显示内容的View放置在我们的一个容器中,如果你想实现一个用ListView显示数据的下拉刷新,你需要创建一个ListView旋转到我的容器中。我们处理这个容器的事件(down, move, up),如果向下拉,则把整个布局向下滑动,从而把header显示出来。

【3】Footer

Footer可以用来显示向上拉的箭头,自动加载更多的进度条等。

热点内容
西数128m缓存硬盘 发布:2024-11-23 20:15:32 浏览:997
群晖搭建l2tp服务器 发布:2024-11-23 20:14:17 浏览:544
cmd可以下载ftp吗 发布:2024-11-23 19:57:50 浏览:48
如何制作第五人格密码机 发布:2024-11-23 19:44:30 浏览:338
数据库系统的核心是 发布:2024-11-23 19:42:16 浏览:350
linux下载中文版 发布:2024-11-23 19:31:38 浏览:729
gcc按ansi编译 发布:2024-11-23 19:21:12 浏览:700
安卓光遇如何下ios 发布:2024-11-23 19:21:09 浏览:115
内容脚本库 发布:2024-11-23 19:14:31 浏览:362
安卓系统的车机怎么投屏 发布:2024-11-23 19:08:20 浏览:728