1、View绘制流程
看我这篇长作Android View的绘制原理 - 掘金 (juejin.cn)
2、自定义View注意点
2.1、View需要实现4个构造函数
// java代码中new View时用的
public ChildrenView(Context context) {
super(context);
}
// 布局文件中用到
public ChildrenView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
// 如果View不需要随主题变化而变化,则上面两个就OK
//随主题变化而变化
public ChildrenView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
//当defStyleAttr为0,就使用defStytleRes的样式
public ChildrenView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
context:上下文
AttributeSet attrs 在xml中定义的参数内容
int defStyleAttr 主题中优先级最高的属性
int defStyleRes 优先级次之的内置于View的style(自定义View设置样式的地方),defStyleAttr为0,或者Theme中没有给defStytleAttr属性赋值才起作用
Android中的属性可以在多处赋值,优先级:xml直接定义-》xml中通过style定义-》自定义view所在Activity的Theme中指定的Theme中style的定义->构造函数中defStytleRes指定的默认值
2.2 自定义View的种类不同给,选一种低成本方式,尽可能减少UI层级,View的种类如下
- 继承View重写onDraw
- 继承指定的View,比如TextView
- 继承ViewGroup重写onLayour
- 继承指定的ViewGroup比如Linearlayout
2.3 View需要支持padding
直接结成View的控件要在onDraw中处理padding,直接继承ViewGroup的控件要在onMeasure和onLayout中考虑padding和子元素的margin的影响,不然会失效
2.4 尽量不要在view中使用handler
用view的post方法,这样就会使viewRootImpl度量之后再执行的。
2.5 自定义View的onMeasure、onDraw,onLayout方法尽量少使用局部变量因为可能被频繁调用导致内存抖动
3、自定义View和Group的区别
ViewGroup相当于放置View的容器,同时也是View的派生类。能给子view计算建议的宽高和测量模式,并决定child的位置,将子view布局到这个Layout上面。
子View的职责:根据测量模式和ViewGroup给出的建议宽高,计算自己的宽高。并在ViewGroup为它指定的区域内绘制自己的形状。核心在onDraw上
3.1、事件分发方面
- 主要3个方法,dispatchTouchEvent() onIntercepTouchEvent() onTouchEvent()
- ViewGroup包含这三个方法,而View则只包含dispatchToucher()\onTouchEvent()两个方法
- 触摸事件由Action_Down Action_move Action_up 组成,一次完整的触摸事件,包含down、up和0~n个move
- Action_Down-》ViewGroup.dispatchTouchEvent->
- onInterecptTouchEvent true表示拦截。
- viewGroup的onTouchEvent
- true 表示消费
- false或者调用super表示给父消费
- onInterecptTouchEvent false 表示不拦截
- 给到子View的DispatchTouchEvent 为true,那么则消费,在ViewGoup中记录该子View。接下来的move和up事件都由它处理。
- 给到子VIew的DIspacherTouchEvent为false则调给ViewGroup的onTouchEvent,即传回去。Super的话就给view的onTouchEvent。只要消费了就让谁处理后续
3.2、UI绘制方面
3.2.1
onMeasure onLayout onDraw dispatchDraw drawChild
- ViewGroup上边全包括,view只有onMeasure、onLayout、onDraw、
- 自定义View主要就是重写onDraw。只有改变大小采用onMeasure。改变View在父ViewGroup的位置就重写onLayout。
- 自定义VIewGroup主要是重写onLayout和onMeasure
3.2.2
- 绘制流程都一样 onMeasure(测量)——》onLayout(布局)——》onDraw(绘制)
- 视图绘制会先绘制父再绘制子。如果背景可见,会在onDraw之前先调用drawbackGround。强制重绘可以调用invalidate。
- 视图尺寸变化,会调用requestLayout,请求父控件再次布局。视图外观变化,会调用invalidata,强制重绘。两者有一个被调用,都会丢视图树进行相关的测量、布局、绘制。注意视图树是单线程操作,必须再UI线程中操作。跨线程必须要用Handler
- OnMeasure:计算自己及所有子对象的大小。MeasureSpc包含了测量的模式和测量的大小。MeasureSpec.getMode()获取测量模式。通过MeasureSpec.getSize()获取大小。mode有三种。EXACTLY\AT_MOST\UNSPECFIELD.父的这个mode和自身的设置,固定大小,wrap_content,match_parent组成9宫格。固定大小一行都是EXACTLY.wrap_content一行,EXACTLY一行是EXACTLY。AT_MOST是AT_MOST.UNSPECFIELD.match_parent。match_parent一行,AT_MOST,AT_MOST,UNSPECFIELD.
- onLayout():view的onLayout是空实现。但是ViewGroup onLayout是个抽象方法,必须实现。
- onDraw:drawBackGound绘制背景,onDraw对view的内容进行绘制,dispatchDraw对当前View的所有子View进行绘制,onDrawScrollBar对View的滚动条进行绘制
- dispatchDraw:ViewGroup及派生类,主要控制子View的绘制分发,自定义ViewGroup控件时,重载该方法可以改变子View的绘制。做一些复杂的事
- drawChild 。如果你在父
ViewGroup
中通过drawChild
方法绘制了一个子View
(例如甲View
),并且在这个过程中没有显式地调用甲View
的onDraw
方法,那么甲View
的onDraw
方法在这次绘制过程中不会被自动调用。
4、View的绘制流程是从Activity哪个生命周期方法开始执行
- ActivityThread.handleResumeActivty->ActivityThread.performResumeActivity->activity.performResume->activity.callActivityOnresume->activity.onResume->vm.addView->mGlobal.addView->viewRootImpl.setView-》requestLayout
5、Activity,Window,View三者的联系和区别
- Activity是安卓四大组件,负责界面、交互和业务逻辑处理、
- Window是窗体的抽象,是顶级Window的外观与行为策略,这种外观和行为策略包括窗口的布局、绘制、事件处理等。目前只有PhoneWindow;
- View是放在Window容器内的元素,Window是View的载体,View是Window的具体展示
- 单一职责问题,activity和view都有自己的职责,所以activity管理view就需要独立类来进行,也就是同通过window
5.1 Window和View 的关系
- Window是View的管理者,Android中所有视图都是通过Window来呈现的,比如Activity、Dialog、Toast
- Window的添加、删除时通过WindowManager来实现的,WIndowManager继承与ViewManager
- WindowManager也是一个接口,它的唯一实现类时WIndowManagerImpl,所以管理是都用的WIndowManagerImpl
- WindowManagerImpl都是交给WIndowManagerGlobal来执行
- windowmanagerGlobal通过调用ViewRootImpl来对view操作
- viewRootImpl是视图层次结构的顶部,它实现了View与WindowManager之间 所需要的协议,大部分的WindowManagerGlobal的内哦不实现是通过ViewRootImpl来进行
5.2 Activity与Window的关系
- Activity是向用户展示 一个界面,并可以与用户 进行 交互。Activity内部持有一个Window对象,用于管理view
- activity的onattach()方法会建立一个PhoneWindow对象赋值给mWindow,mWindow相当于Activity的管家,用于管理View的相关事宜,时间分发,也是先交给Window再往下分发。
- dispatchTouchEvent方法中if (getWindow().superDispatchTouchEvent(ev)) { return true; }
- 事件到WIndow的SuperDispatchTouchEvent(ev),其实会传递个DecorView
6、在onResume中是否可以测量宽高
- 如果第一次进入activity,到onResume方法获取不到,因为handlerResumeActivity()里是先performResumeActivity在vm.addView(decor,l)。也就是说onResume在addView-》root.setView-》requestLayout->scheduleTraversals-》同步屏障,TraversalRunnable-》doTraversal-》移除同步屏障,performTraversals-》performMeasure,performLayout,performDraw->触发viewTree之前,那么 肯定拿不到数据啦。
- 如果不是第一次进入onResume是有可能拿到的。
怎么拿到呢?
- 首先 错误答案,handler.post(Runnable)无法正确获取view真实宽高,因为无法确定是跑在onMeasure之后的。
- View.Post(Runnable)可以获取到View的宽高。
- 核心在于getRunQueue().post(action); 能让消息在视图附加后执行
public boolean post(Runnable action) {
//mAttachInfo 是在 ViewRootImpl 的构造函数中初始化的
//而 ViewRootmpl 的初始化是在 addView() 中调用
//所以此处的 mAttachInfo 为空,所以不会执行该 if 语句
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
//保存消息到 RunQueue 中,等到在 performTraversals() 方法中被执行
getRunQueue().post(action);
return true;
}
7、如何更新UI,子线程为什么不能更新UI?
-
其实吧这事也不一定,比如在onCreate里写个子线程里面来个textView.setText没事,但是延迟在setTExt就有问题
-
ViewRootImpl.requestLayout-> ViewRootImpl.checkThread中有个线程检测,不是主线程就抛出异常。
-
但最开始onCreate里为啥对UI的操作没错呢。因为ViewRootIMpl的创建是在onResume回调之后,这时候都没ViewRootIMpl都没创建当然不会报错 。
-
来,让我们看看ViewRootImpl啥时候创建的。
-
看看onResume怎么来的
-
ActivityThread.handleResumeActivity()->ActivityThread.performResumeActivity()->activity.performResume()->mInstrumentation.callActivityOnResume()->activity.onResume()
-
看看viewRootImpl怎么来的
-
ActivityThread.handleResumeActivity()-》activityThread.performResumeActivity(),后会执行wm.addView->mGlobal.addView->root = new ViewRootImpl
-
所以onResume执行完了,才有ViewRootImpl。
7.1 个人理解
必须要在主线程更新UI,实际是为了提高界面的效率和安全性。如果多线程更新UI,但访问Ui没有枷锁,多线程抢占资源,那么会乱套。所以从这方面考虑,必须规定在主线程中更新ui.
如果没有这些考虑,也能在子线程中更新UI,比如,SurfaceView。1、Surface有自己的渲染线程(CANVAS THREAD),避免主线程阻塞。2、SurfaceView是线程安全的(就一个子线程操作,所以安全),可以在子线程中操作
7.2 源码总结
- 子线程能在ViewRootIMple创建之前更新Ui
- 访问UI是没有加对象锁,在子线程更新uI,会有各种位置风险
- 一定要在主线程更新UI
8、DecorView,ViewRootImpl,VIew之间的关系
- Window时View的管理者,Android中所有视图都是通过Window来呈现,
- Window的在activity.attach中实现了phoneWindow,对象叫mWinoow,mWindow内部再setContentiew-》installDecor,生成DecorView。作为view的根布局,
- Window对view的添加删除,都是通过WindowManager的实现类WIndowManagerImpl-》WIndowManagerGlobal-》VIewRootImpl-》WMS(通过binder)
总结
- DecorView是顶级视图,view都在DecorView下。
- Activity对象创建 完毕,DecorView添加到Window,同时创建ViewRootImpl和DecorView建立关联
- ViewRootImpl(通过binder连接)是连接WMS和DecorView的前两,赋值视图树的绘制和时间传递。
9、自定义View执行invalidate方法,为什么有时候不会调onDraw
- 如果 View 被其他 View 遮挡或者其尺寸为 0,系统可能不会认为需要重绘该 View,因此不会调用
onDraw()
。 - 也就是父ViewGroup drawChild就默认不会调用子view的onDraw
invalidate()
方法用于标记 View 需要重绘- 硬件加速,用GPU。软件加速用CPU。ondraw的调用流程有点区别
10、invalidate和postInvalicate的区别。
一个主线程刷新UI,一个是子线程刷新UI。后者是通过handler切换线程的
11、Requestlayout、onlayout、onDraw、DrawChild区别与联系。
- RequestLayout->会导致onMeasure、onLayout 根据标志位判断是否需要onDraw
- onLayout:摆放ViewGroup里面的子控件,自定义ViewGroup需要重写
- onDrawe:绘制视图本身(ViewGroup还需要绘制里面的所有子控件)
- DispatchChild:父控件会回调每个子控件的ondraw
- DrawChild:父控件调了drawchild 子。那么子view的onDraw默认就不会调用了
12、View的滑动方式
1、ScrollTo/ScorllBy 2、Animation 3、改变布局参数 4、继承View,内部用Scroller实现
13、事件分发机制过程
13.1、控件对应的多叉树
每个节点都有一个ft,就是mFirstTouchTarget,对应但连败哦。表示DOWN事件在onTouchEvent方法中返回true,会回溯设置父的ft指针。也就是DOWN事件被这个view消费,后续的MOVE、UP事件都要给它。
事件是深度遍历,vp1(intercept) -> vp4(intercept) -> v9(touch) -> v8(touch) ->v7 (touch) -> vp4(touch)-> vp3 -> v6(touch) -> v5(touch) -> v4(touch) -> vp3(touch)-> vp2 -> v3(touch) -> v2(touch) -> v1(touch) -> vp2(touch)-> vp1(touch)
13.2、手势事件类型
- DOWN\MOVE\UP\CANCEL
13.3、分发
-
dispatchTouchEvent
-
onInterceptTouchEven
-
onTouchEvent
-
Activity通过dispatchTouchEvent分发事件给ViewGroup
-
ViewGroup.dispatchTouchEvent
- true 表示自己消费
- false 表示给Activity的onTouchEvent
- super 传给 ViewGroup 的onInterceptTouchEvent
-
ViewGroup.onInterceptTouchEvent
- true 拦截 给ViewGroup的onTouchEvent
- false/super 传递给view的dispatchTouchEvent
-
ViewGroup.onTouchEvent
- true自己消费 false给到Activity的onTouchEvent
-
View.dispatchTouchEvent
- true 自己消费
- false给到 viewGroup的onTouchEvent
- super 给到view的onTouchEvent
-
View.onTouchEvent
- true 自己消费 false 传给ViewGroup的onTouchEvent
13.4 DOWN事件的分发流程
当子VIew消费了DOWN事件,接下里的MOVE up cancel都是子View来消费。因为有个FT。是一个单链表,链表头是消费DOWN事件的子view。按照深度遍历,同级别是从上到小。
13.5 MOVE、UP事件
VP1下有2个子视图 v1,v2。如果v1消费了DOWN。那么MOVE直接传给v1,如果V1不消费MOVE。那么会回传给VP1。因为VP1沾了V1的光,有ft,所以会执行onInterceptTouchEvent,如果true就传给自己的onTouchEvent。否则传给子view v2.因为v2没有ft,所以就被直接拦截到onTouchevent。
up同理
13.6 cancel
VPP1下有一个子viewgroup vp1,vp1下有一个v1和v2.
假设v1拿了down事件,然后一直move,突然被vpp1截胡了,拦截了move事件,那么vpp1产生一个CANCEL事件给到vp1,同时vpp1的ft变为空,vp1传递这个cancel给到子view,同时vpp2的ft变为空。
VPP1下有一个子viewgroup vp1,vp1下有一个v1和v2. 假设v1拿了down事件,然后一直move,突然被vpp1截胡了,拦截了move事件,那么vpp1产生一个CANCEL事件给到vp1,同时vpp1的ft变为空,vp1传递这个cancel给到子view v1和v2,同时vpp2的ft变为空。
14、事件冲突怎么解决?子View中让父View不拦截
内部拦截法,
- 子view中dispatchTouchEvent
- ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true);不让父亲拦截我,
- 因为可能是view没消费,然后让父view消费了,后续move直接给到它,但是因为不让它拦截我,所以还是会给到子view
- requestDisallowInterceptTouchEvent 传入参数 true 时,disallowIntercept 为true,就会导致 intercepted = false。那么一定分发给子view。
- ACTION_MOVE: 指定条件下,比如滑动 getParent().requestDisallowInterceptTouchEvent(false);让父亲拦截我
- 通过requestDisallowInterceptTouchEvent方法参数disallowIntercept的值,控制了mGroupFlags的值,而这个值是控制父容器是否可以拦截子View的关键代码
- 因为intercepted = true,那么cancelChild = true,那么子View会执行cancel事件。同时把事件交给父来处理。那为什么我们还是要下面这样写
- 父view中 onInterceptTouchEvent
if (event.getAction() == MotionEvent.ACTION_DOWN){
super.onInterceptTouchEvent(event); return false;
}
return true;
- 因为
public boolean dispatchTouchEvent(MotionEvent ev) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
resetTouchState();
}
}
private void resetTouchState() {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
- resetTouchState 会重制 mGroupFlags 的值,而该代码在事件为 MotionEvent.ACTION_DOWN 的时候一定会执行。这就导致 disallowIntercept 在事件为 MotionEvent.ACTION_DOWN 的时候一定为 false,即 onInterceptTouchEvent 一定会执行。而子view requestDisallowInterceptTouchEvent(false),接下来的move事件是会直接onInterceptTouchEvent。到所以父容器只能在接下来的move事件是会直接onInterceptTouchEvent。
外部了拦截法:父view中判断是否拦截
直接在onInterceptTouchEvent返回true,中断下发
private int mLastXIntercept;
private int mLastYIntercept;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastXIntercept = (int) event.getX();
mLastYIntercept = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (needIntercept) {//判断是否需要拦截的条件
return true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(event);
}
15、view分发的反向制约方法?
- View拿到down事件后,调用requestDisallowInterceptTouchEvent反向制约父容器对事件的拦截
- requestDisallowInterceptTouchEvent之中
- 1.通过disallowIntercept的值,给mGroupFlags设置不同值。
- 2.请求传给父类
- 父View通过调 用 onInterceptTouchEvent 方法,来实现对子View的事件拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
}
- disallowIntercept 的值,直接影响到 onInterceptTouchEvent 方法的执行,而 mGroupFlags 的值又可以 影响 disallowIntercept 的值。如果disallowIntercept为false,表示来拦截,就直接走onInterceptTouchEvent方法,不走dispatchTouchEvent。
- 如果是down事件,那么该方法requestDisallowInterceptTouchEvent(false)没有效果的,因为在 上面 onInterceptTouchEvent 这块代码在执行前,会先执行如下代码:
if (actionMasked == MotionEvent.ACTION_DOWN) {
resetTouchState();
}
private void resetTouchState() {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
16 onTouch、onTouchEvent onClick的执行顺序
- 实现先到onTouchEvent,其中会先运行performTouch() 再运行performCLick()
- 分别检查是否设置了onTouchListener和onClickListener
- 如果还是,先ontouch是否消费,再onclick是否消费。最后再在onTouchevent里判断
17、怎么拦截事件?如果onTouchEvent返回false?onclick还会执行吗?
- 内部拦截法
- 子view down事件 的requestDisallowInterceptTouchEvent(true),move事件里,按条件判断requestDisallowInterceptTouchEvent(false)拦截,父控件中onInterecpetTouchEvent中。判断DOWN事件里为super.onInterceptTouchEvent,并发挥false。其它为true,消费。
- 外部拦截法,
- 直接在父viewonInterceptTouchEvent中,MOVE中判断条件,如果是就返回true,其它都返回super.onInterceptTouchEvent
如果onTouchEvent返回false?onclick还会执行吗?
- onTouchEvent进来会执行performCLick,里边就是执行onclick,onclick返回true,事件不会下流,所以,onTouchEvent返回值不影响onCLick执行 如果onTouchEvent返回false?onclick还会执行吗?
18、事件的分发机制,责任链模式的优缺点
- 分发机制上面讲了
- 责任链模式,,前请求对象记住后请求对象的引用,而连成一条链,当有请求发生,可沿着这条链传递,指导有对象处理它为止。
- 如果某个view消费了dowm事件,那么它的ft指向父view,那么如果子view没有消费move事件,那应该是按照ft链去传递给父view,而不是兄弟view。
优点
- 请求解耦
- 请求者只需要关心自己的处理逻辑,不是自己的就转发
- 链式传递,请求者不需要关心链路结构,等待结果就好了
- 容易维护,很灵活增删改
缺点
- 循环引用
- 责任链太长影响性能
19、ScrollView下嵌套一个RecycleView通常会出现什么问题
- ScrollView和Recycleview两者都会滑动给,有时候会滑动冲突
- ScorllView高度显示不正确
- RecycleView内容显示不全
解决方案
- RecycleView套一层linealayout
- recycleview。sethashFixedSize(true)
- recycleview.setNestedScrollingEnabled(false)
20、View.inflater过程 与异步inflater
View.inflater
- view.inflater 最后调用的是LayourInflater.inflate(...,resource,...)
- 首先获取xmlResourceParser
- inflate(parser, root, attachToRoot);解析标签
- 如果没有父布局就报错
- 解析 merge标签下的所有的 View,添加到根布局中。通过反射来创建对象
异步inflater AsyncLayoutInflater
- ASyncLayoutInflater用于布局文件的解析和加载,不阻塞子线程,通常用于复杂布局,提高效率加载速度。
- 使用的时ArrayBlockingqueue来实现此模型,如果大量AsyncLayoutInflater创建布局,可能造成缓冲区阻塞。 缺点:
- 注意调用顺序,因为可能存在setContent还未执行,而先调用了控件,从而空指针
- 不支持fragment layout
- 转换出来的view并没有被加到parentview,要手动添加。
21、inflater创建view效率为什么比new View慢?
前者要解析xml然后通过反射创建,当然慢
22、动画的分类以及区别
帧动画
收集N张照片,依次显示,类似于胶卷放映
补间动画
view的平移旋转、缩放、透明
属性动画
- 不停改变属性产生效果,不仅显示效果发生该百年,view的属性值也发生了改变。
- 属性动画和补间动画一样会声明关键帧。和补间动画不一样的是,属性动画可以声明多个关键帧,但是补间动画固定是开始帧和结束帧。
23、属性动画的原理
最开始设定好都规划的基本属性,属性按照逻辑变化并刷新,接着判断动画是否达到结束条件,如果没有达到额重复数值的变化和视图刷新直到结束。
分5步
- 设置动画基本信息,视图,属性名称、属性开始值、属性结束值
- 设置属性值的变化逻辑,插值器、估值器
- 根据变化的逻辑不断的改变值
- 改完后赋给属性
- 调用invalidate刷新视图,并且判断数值是否为结束值。当前值满足结束条件,则结束,不满足则重复第四步。
重要类
关键帧
- 在创建属性动画时,你需要指定起始和结束状态的值。关键帧的作用就是将这两个状态之间的变化过程定义出来、然后根据插值器、估值器,将中间帧制作出来。也就有了多个关键帧,控制属性在不同时间点的变化。
24、动画插值器与估值器是什么?
- 插值器,控制属性变化速度,定义了随时间流逝百分比,计算档期那属性值改变百分比
- 估值器,通过属性改变百分比来计算改变百分比。
- 说人话就是,前者传入改变动画执行百分比来返回属性应该改变的百分比。后者就是传入动画执行百分比,和开始,结束,你自己计算应该要返回的属性值
25、优化帧动画之SurfaceView逐步解析
帧动画一次加载所有帧,容易OOM,用Surfaceview逐帧解析,逐帧释放
- framesurfaceView继承BaseSurfaceView,所以基于积累的绘制框架算法,定了每一帧的绘制内容给,一张Bitmap
- Btimap资源id通过setBitmaps传递进阿里,绘制一帧解析一张,每一帧绘制完毕后,调用Bitmap.reccle()释放图片native内存并去除java堆中图片像素数据的引用。这样GC时,土拍你像素数据可以及时被回收
- 帧动画每张图片大小是一致的,是不是能复用上一帧Bitmap的内存空间?当然可以啦。
- 采用逐帧解析的方式可以有效的解决原生帧动画一次性加载全部动画图片带来的问题,而且按需加载的方式也更符合实际开发场景,不占用过多没有必要的内存资源。
26、WebView如何做资源缓存?
- WebView自带的H5缓存机制
- 预加载
- 离线包
27、WebView和JS交互的几种方式
Android调js
- WebView loadurl
- webView evaluateJavaScript
js调Android
- Webview的addJavaScriptInterface
- WebViewClient的shouldOverrideUrlLoading方法拦截url
28、WebView拦截请求的方法
Webview的loadUrl方法
- 开启一个网页时,onPageStarted,
- 点击网页内链接时,shouldOverrideUrlLoading先执行再onPageStarted
- 链接点击back返回历史时,onPageStarted执行
所以再onPageStarted方法中拦截,检测url不合规就停止加载并转跳到预制链接
- Webiew的loadUrl方法打开网页、点击网页中的链接、返回网页时,所有的资源加载均会调用shouldInterceptRequest
- 进行资源替换时,可以将网页资源,例如html、css、js、图片等存放在本地,在shouldInterceptRequest对 WebView加载的资源进行拦截,当符合某种策略时,替换为本地的资源,资源的MIME类型可以采用以下方法获取: MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url))
- 每个请求都会过shouldInterceptRequest
29、如何将Activity窗口快速变暗?
- getWindow获取窗口对象,然后,param里有个alpha对象,用属性动画0.5m内控制1降到0.5。就好了
30、RecycleView与ListVIew的对比,缓存策略,优缺点
布局效果
- List只有纵向
- RecycerView 横向 纵向 表格 瀑布流。实在不行自已自定义LayoutManager布局管理器
局部刷新
- ListView只有全局刷新
- RecycelView有局部刷新,notifyItemChanged,因为onCreateViewHolder和绑定数据onBindeViewHolder的逻辑分离。
动画效果
- ListView没有实现动画效果,可以在Adapter自己实现item的动画效果
- RecycelView自带动画效果,通过实现接口(RECYCEvIEW.ITEMAnimator),然后调用RecyclerView.setItemAnimator方法来设置动画
缓存
缓存层级
- ListView只有两层缓存,屏幕内mActivityViews、mScrapViews
- RecycleView有四级缓存:
- 屏幕内有缓存Scrap,包括mAttachedScrap、mChangedScrap,他们是在notify来修改数据时应用的
- mCacheViews缓存,保存离开屏幕项Item
- 有自定义缓存mViewCacheExtension,开发者自己设置
- 有mRecyclePool 终极华安邨,都没找到才来它这里
缓存内容不同
- ListView对最外层的View进行缓存
- RecycellView时对ViewHOlder进行缓存,ViewHolder时对Item的VIew的封装,ViewHolder在构造方法里会通过findViewById将各个需要的View的引用设置好,之后复用就好了。
缓存机制
- 离开屏幕的item立即会被回收至ScrapView,滑入屏幕的itme则会优先从缓存ScrapView中获取,只是listView与REcyclerView的实现细节有差异
- 一级缓存是mAttachedScrap,二级缓存是mScrapView
- RecycleView 四级缓存
- mAttachedScrap 屏幕内itemView快速复用
- mCacheViews 默认上限为2个itemView
- mViewCacheExtension 默认不实现,用户定制
- mRecyclerPool 默认上限为5,技术上实现所有recyclerViewPool共用同一个
- ListView和RecyclerView缓存机制基本一致:
- 前者的mActiveViews和后者的mAttacheScrap功能类似,快速复用屏幕上可见的列表项ItemView,而用重新createView和bindView
- listView额mScrapView和RecyclerView的mCacheViews+mRecyclerViewpool功能类似,米是将即将进入屏幕的item可以从缓存容器中读取数据,减少耗时。
- RecyclerView的优点
- mCachedViews:屏幕外的列表项itemView进入屏幕内时不用binView,快速复用,
- mREcyclerPool可以供多个RecyclerView共同使用
31、ViewHolder为什么要被声明为静态内部类
- 如果是非静态内部类会隐式持有外部类的引用。如果有复杂逻辑,容易内存泄漏,如果是静态内部类的胡啊,不能直接引用外部类,要用的话,传进去,用弱引用。好习惯
- 能够让ViewHolder不仅仅可以在当前类使用,还可以在其他类里面直接复用这个ViewHoler
32、ListView款额原因以及优化策略。
- item没有复用
- 有些item离开屏幕,有些需要进入,离开的一般进入到缓存容器。如果有缓存就优先加载缓存给convertView。如果不做缓存就要反射创建,耗费性能。
- 层级过深
- 减少布局层级,用约束布局
- 数据绑定逻辑过多
- 简单的position找到数据,再设置,不会很耗时。如果打来那个的空间属性设置、数据遍历转化,就会非常耗时,建议减少
- 滑动时有不必要的图片刷新
- 让item在滑动时不加载,停止时再加载
- 频繁的notifyDdataSetChanged
- 减少呗
33、RecyclerView的回收复用机制
- AttachedScrap、ChangedScrap。和Notify数据更新有关,和滑动过程回收无关。无限定大小。当notify更新的过程中。前者存放不需要变化的item。后者存放需要变化的item。两者被称为屏幕内缓存。前者主要用于保存已经添加到REcyclerView中deViewHolder,通常在屏幕内。后者当数据源繁盛变化,需要会从i性能布局的viewholder会被添加到ChangedScrap中。这些viewholder同样主要位于屏幕内。
- CacheViews是用于保存最新被移除的列表项item。一般是滑动过程中离屏的item。先通过position再通过id来匹配当前缓存是否为需要内容,如果没有就去RecycledViewPool里去拿。但是这个里的去拿,但是这里的同类型的item也是需要重新绑定数据的。
- CacheViews一般是2,recycleViewPool是5。当notifyDataChanged建议先设置为屏幕显示的item总数+1。更新完之后再设置回5.
- 在notifyDataSetChanged()方法之前将RecyclerViewPool的大小设置为屏幕上Item的数量加1,是为了预加载和缓存一定数量的ViewHolder对象,以便在数据集发生变化时快速响应并提高性能。这样做可以预先准备好一些ViewHolder对象,减少在数据集变化时重新创建和初始化ViewHolder对象的开销。
34、如何给ListView&RecyclerView加上上拉刷新&下拉刷新更多机制
- listview
- 添加头布局和底布局
- 改变头布局的paddingTop的值,来控制控件的显示和隐藏
- 根据我们的滑动状态,动态修改头部布局和底部布局。
- RecyclerView
- SwipeRefreshLayout来实现下拉刷新
- 而上拉刷新,需要通过监听列表的滚动,当列表滚动到底部时触发事件
35、如何对ListView&RecycleView进行局部刷新的?
- listview通过 id找到view 然后getView(i, view, listView);
- recyclerView
- 局部刷新一般用notifyItemChanged(position),但是依旧会刷新所有的东西,并且不断刷新的时候会执行刷新item的动画,导致滑动的时候会错开以下。可能导致土拍你重新加载或者不必要的消耗
- 将
notifyItemChanged(position)
替换为notifyItemChanged(position,0)
即可。
36、ListView和RecycleView再显示新闻数据的时候,出现图片错位,啥原因?咋解决?瀑布流错位怎么做?
- 原因:复用功能
- listView 图片加载错乱是因为item复用+异步加载,假如item8用了item1,但是item1的图片加载完成时再item8图片加载之后,那么就会出现item1的图片在item8上。
- 解决方法
- 直接设置一个tag,和默认图片,那么异步加载回来发现和item1的url不等于当前tag村的url,我就不设置上去了
- Glide.with(imageView.getContext()).load(url).into(imageView);当imageview不可见时我就加载了。
- 重写getItemId方法,通过重写getItemId方法并为每个item提供一个唯一的标识符,RecyclerView可以更准确地识别和区分不同的item。这样,当新的item需要显示时,RecyclerView可以正确地找到与之关联的ViewHolder,并更新相应的数据和图片。这避免了因item混淆而导致的图片错位问题。
- 瀑布流错位怎么搞
- Adapter.notifyItemInserted(mPosition);
37、如何优化自定义View
- 硬件加速
- 减少过度渲染,Canvas.clipXxx
- 不要再onDraw里创建绘制对象,一般再构造方法,因为onDraw会频繁调用
- 保持view层级扁平化
- 尽量用约束布局
- 利用缓存机制和重用机制
- bitmap缓存
- ondraw中重用对象而不是新建
- 状态存储与恢复
- 如果内存不足,activity在后台,用onSaveInstanceState保存view属性,onRestoreInstanceState恢复属性
- 减少刷新频率
- invalidate有4种,合理使用。有时候不一定要全部刷新
- 避免在onDraw中执行不必要的代码,减少冗余对象的创建
- 优化requestLayout()调用,减少次数,并保持View层级扁平化
- 自定义viewGroup自定义吧,减少遍历层级。