1、MeasureSpec是什么?
- MeasureSpec是一种“测量规则”或者“测量说明书”,决定了View的测量过程
- View的MeasureSpec会根据自身的LayoutParamse和父容器的MeasureSpec生成。
- 最终根据View的MeasureSpec测量出View的宽/高(测量时数据并非最终宽高)
2、MeasureSpec的组成?
- MeasureSpec代表一个32位int值,高2位是SpecMode,低30位是SpecSize
- SpecMode是指测量模式
- SpecSize是指在某种测量模式下的大小
- 类MesaureSpec提供了用于SpecMode和SpecSize打包和解包的方法
3、测量模式SpecMode的类型和具体含义?
- UNSPECIFIED:父容器不对View有任何限制,一般用于系统内部
- EXACTLY:精准模式,View的最终大小就是SpecSize指定的值(对应于LayoutParams的match_parent和具体的数值)
- AT_MOST:最大值模式,大小不能大于父容器指定的值SpecSize(对应于wrap_content)
4、MeasureSpec和LayoutParams的对应关系?
- View的MeasureSpec是需要通过自身的LayoutParams和父容器的MeasureSpec一起才能决定
- DecorView(顶级View)是例外,其本身MeasureSpec由窗口尺寸和自身LayoutParams共同决定
- MeasureSpec一旦确定,onMeasure中就可以确定View的测量宽/高
5、如何获取View的测量宽/高?
- 在measure完成后,可以通过getMeasuredWidth/Height()方法,就能获得View的测量宽高
- 在一定极端情况下,系统需要多次measure,因此得到的值可能不准确,最好的办法是在onLayout方法中获得测量宽/高或者最终宽/高
6、如何在Activity启动时获得View的宽/高?
- Activity的生命周期与View的measure不是同步运行,因此在onCreate/onStart/onResume均无法正确得到
- 若在View没有测量好时,去获得宽高,会导致最终结果为0
7、Activity中获得View宽高的4种办法?
- onWindowFocusChanged
- View已经初始化完毕,可以获得宽高,Activity得到焦点和失去焦点均会调用一次(频繁onResume和onPause会导致频繁调用)
- view.post(runnable)
- 通过post将一个runnable投递到消息队列尾部,等到Looper调用次runnable时,View已经完成初始化
- ViewTreeObserver
- 使用ViewTreeObserver的接口,可以在View树状态改变或者View树内部View的可见性改变时,onGlobalLayout会被回调,能正确获取View宽/高
- view.measure
8、Activity启动到最终加载ViewRoot(执行三大流程)的流程是什么?
- Activity调用startActivity方法,最终会调用ActivityThread的handleLaunchActivity方法
- handleLaunchActivity会调用performLauchActivity方法(会调用Activity的onCreate,并完成DecorView的创建)和handleResumeActivity方法
- handleResumeActivity方法会做四件事:
- performResumeActivity(调用activity的onResume方法)
- getDecorView(获取DecorView)
- getWindowManager(获取WindowManager)
- WindowManager.addView(decor, 1)
- WindowManager.addView(decor, 1)本质是调用WindowManagerGlobal的addView方法。其中主要做两件事:
- 创建ViewRootImpl实例
- root.setView(decor, ….)将DecorView作为参数添加到ViewRoot中,这样就将DecorView加载到了Window中
- ViewRootImpl还有一个方法performTraveals方法,用于让ViewTree开始View的工作流程:其中会调用performMeasure/Layout/Draw()三个方法,分别对应于View的三大流程。
9、自定义View性能优化有哪些?
- 避免过度绘制
- 像素点能画一次就不要多次绘制,以及绘制看不到的背景。开发者选项里内的工具,只对xml布局有效果,看不到自定义View的过度绘制,仍然需要注意。
- 尽量减少或简化计算
- 不要做无用计算。尽可能的复用计算结果。
- 应该避免在for或while循环中做计算。比如:去计算屏幕宽度等信息。
- 避免创建大量对象造成频繁GC
- 应该避免在for或while循环中new对象。这是减少内存占用量的有效方法。
- 禁止或避免I/O操作
- I/O操作对性能损耗极大,不要在自定义View中做IO操作。
- onDraw中避免冗余代码、避免创建对象
- onDraw中禁止new对象。如:不应该在ondraw中创建Paint对象。Paint类提供了reset方法。可以在初始化View时创建对象。
- 要避免冗余代码,提高效率。
- 复合View,要减少布局层级。
- 复合控件:继承自现有的LinearLayout等ViewGroup,然后组合多个控件来实现效果。这种实现方法要注意减少布局层级,层级越高性能越差。
- 状态和恢复和保存
- Activity还会因为内存不足或者旋转屏幕而导致重建Activity,自定义View也要去进行自我状态的保存和读取。
- 在onSaveInstanceState()保存状态;在onRestoreInstanceState()恢复状态
- 开启硬件加速
- 合理使用invalidate的参数版本。
- 避免任何情况下调用默认参数的invalidate
- 调用有参数的invalidate进行局部和子View刷新,能够提高性能。
- 减少冗余代码
- 不要使用Handler,因为已经有post系列方法,View已经有post系列方法,没有必要重复去写,可以直接使用,最终会投递到主线程的Handler中
- 使用的线程和动画,要在onDetachedFromWindow中进行清理工作。
- View如果有线程或者动画,需要及时停止,View的onDetachedFromWindow会在View被remove时调用,在该方法内进行终止。这样能避免内存泄露
- 要妥善处理滑动冲突。
- View如果有滑动嵌套情形,需要处理好滑动冲突