一 概述
上篇分析了自定义 View 绘制流程及其常用方法:Android View绘制4 Draw过程(上),本篇将以硬件加速绘制与软件绘制入口为切入点,通过本篇文章,你将了解到:
- 硬件加速
- 硬件加速绘制与软件绘制分道扬镳的地方
- 认识 LayerType
二 硬件加速
Android 3.0 之前,绘制操作通过 CPU 完成,而在 Android 3.0(含)之后,Android 2D 渲染管道支持硬件加速,也就是说 Canvas 绘制操作会使用 GPU 渲染。
2.1 硬件加速的作用
使用软件绘制的时候,绘制操作都是通过 CPU 计算并写入 Bitmap,最终 Bitmap 直接渲染到屏幕上。当某个 View 需要刷新的时候,计算刷新的脏区域,有相交的地方都需要重新绘制,也就是重走 Draw 过程。
使用硬件绘制的时候,绘制操作先将操作记录到 RenderNode 里,当渲染的时候将这些操作集合交给 GPU 处理,GPU 更擅长处理浮点相关的运算。
当某个 View 需要刷新的时候,只需要重新生成与之相关的操作指令集,也就是 Draw 过程。甚至当修改透明度等属性的时候都不需要重走 Draw 过程,大大减少了无效的绘制请求,节约了 CPU 时间,提升程序运行流畅度。
当然,硬件加速需要更多资源,因此应用会占用更多内存。另外有些 Canvas API 并不支持硬件加速,具体请参考官方文档:https://developer.android.google.cn/guide/topics/graphics/hardware-accel
2.2 硬件加速的开启与关闭
2.2.1 硬件加速控制层级
硬件加速分为4个层级来控制,分别为:
- Application
- Activity
- Window
- View
Application
在 Application 标签下,添加如下字段:
//关闭硬件加速
<application
android:hardwareAccelerated="false">
</application>
//开启应将加速
<application
android:hardwareAccelerated="true">
</application>
Android 4.0(含)之后默认开启硬件加速,也就是:默认 android:hardwareAccelerated=“true”
Activity
在 Activity 标签下,添加如下字段:
//关闭硬件加速
<activity android:hardwareAccelerated="false">
</activity>
//开启硬件加速
<activity android:hardwareAccelerated="true">
</activity>
和 Application 一样,Activity 也是默认开启硬件加速。
Window
//开启硬件加速
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
目前来说,无法直接关闭 Window 的硬件加速,后续解释。
View
//禁用硬件加速
View.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
目前来说,无法直接开启 View 的硬件加速,后续解释。
2.2.2 各个层级的关系
按照控制的范围来说:Application>Activity>Window>View。来看看各个层级开关硬件加速之间的相互影响。
当 Application 开启了硬件加速
- Activity/Window/View 也开启了硬件加速
- 当然,Activity/View 可以选择关闭硬件加速
当 Application 关闭了硬件加速
- Activity/Window 也关闭了硬件加速
- 当然,Activity/Window 可以单独开启硬件加速
当 Activity 开启了硬件加速
- Window/View 也开启了硬件加速
- 当然,View 也可以选择关闭硬件加速
当 Activity 关闭了硬件加速
- View 也关闭了硬件加速
也许你还是觉得比较疑惑,尤其是针对 Window 和 View,到底怎么控制这两个层级的硬件加速呢?前面说过,硬件加速用在绘制上,而绘制的核心是 Canvas,Canvas 分为支持硬件加速的 Canvas:RecordingCanvas 和普通 Cavas。因此只需要找到什么时候用 RecordingCanvas,就知道是否支持了硬件加速。循着这个点,接下来分析代码里是如何控制硬件绘制与软件绘制的。
三 硬件加速绘制与软件绘制分道扬镳的地方
我们知道当 View 添加到 Window 后,会调用 ViewRootImpl->setView() 方法。
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs,
View panelParentView) {
synchronized (this) {
if (mView == null) {
......
//mSurfaceHolder 初始为空
if (mSurfaceHolder == null) {
//使能硬件加速
enableHardwareAcceleration(attrs);
}
......
}
}
}
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
//初始标记
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
//判断是否开启硬件加速
final boolean hardwareAccelerated = (attrs.flags &
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
//判断渲染是否可用
if (!ThreadedRenderer.isAvailable()) {
return;
}
//默认没有设置该标记
final boolean fakeHwAccelerated = (attrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
final boolean forceHwAccelerated = (attrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
if (fakeHwAccelerated) {
mAttachInfo.mHardwareAccelerationRequested = true;
} else if (!ThreadedRenderer.sRendererDisabled
|| (ThreadedRenderer.sSystemRendererDisabled &&
forceHwAccelerated)) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.destroy();
}
//创建渲染线程 ThreadedRenderer
//并赋值给mAttachInfo->mThreadedRenderer
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext,
translucent, attrs.getTitle().toString());
if (mAttachInfo.mThreadedRenderer != null) {
//将mAttachInfo->mHardwareAccelerated 标记位true
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
}
}
}
}
以上代码重点关注两个点:
- 硬件加速标记存放在 WindowManager.LayoutParams 的 flags 参数里
- 如果支持硬件加速,则将标记与渲染线程对象记录到 View.AttachInfo 里
我们知道 View 展示的三大流程是在 performTraversals() 里调用的,在该方法里,依次进行 Measure、Layout、Draw 过程,来看看 Draw 过程的开启:
ViewRootImpl.java
private void performTraversals() {
//若有必要初始化渲染线程
hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
mSurface);
//1、Measure
......
//2、Layout
......
//3、Draw
performDraw();
}
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
......
if (mAttachInfo.mThreadedRenderer != null &&
mAttachInfo.mThreadedRenderer.isEnabled()) {
//mAttachInfo.mThreadedRenderer.isEnabled()->true 前边初始化过了
//硬件加速绘制入口
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
//软件绘制入口
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
return useAsyncReport;
}
以上代码重点关注两个点:
- 在 ViewRootImpl->draw() 方法里开始了绘制的分歧
- 分歧的依据是 mAttachInfo.mThreadedRenderer != null
因此现在的问题简化为:WindowManager.LayoutParams 里的 flags 硬件加速标记决定绘制是否走硬件加速流程。WindowManager.LayoutParams 从哪来?回顾一下 View 是如何添加到 Window 的:
- 获取 WindowManager 对象
- 设置 LayoutParams 属性
- 将 View 添加到 Window 里
当调用:
wm.addView(textView, layoutParams);
后续调用如下:
可以看出,layoutParams 经过层层传递最后到达 ViewRootImpl 里的 setView(),也即是上边分析的方法。
通过分析代码,发现只有 WindowManagerGlobal addView() 会给 layoutParams.flags 赋值:
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
final WindowManager.LayoutParams wparams =
(WindowManager.LayoutParams) params;
if (parentWindow != null) {
//该分支是Activity、Dialog addView() 会走
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//该分支是直接WindowManager.addView()
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
//(context.getApplicationInfo().flags 是在Application 层级设置的标记
//如果设置了该标记,那么将该标记存储在wparams
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
......
synchronized (mLock) {
try {
...
//此时,wparams 记录了是否有硬件加速
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
}
继续来看 adjustLayoutParamsForSubWindow:
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
......
//1、mHardwareAccelerated 表示该 Activity 是否支持硬件加速
// 该值是由 Activity 在 xml 里配置的硬件加速标记决定的
//2、mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED)
// 表示之前是否给 Window 设置了硬件加速标记
if (mHardwareAccelerated ||
(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
//如果满足,记录在WindowManager.LayoutParams 里
wp.flags |= FLAG_HARDWARE_ACCELERATED;
}
}
从以上两段代码可以看出:
- Window 与 Activity 关联,如果该 Activity 开启了硬件加速,那么该 Window 也开启了硬件加速。
- Window 不与 Activity 关联,如果 Application 开启了硬件加速,那么该 Window 也开启了硬件加速。
要关闭 Window 层级的硬件加速,只需要 Activity/Application 禁用硬件加速即可。
实际上,不管 Activity/Application/Window 如何设置硬件加速标记,都是反馈到 WindowManager.LayoutParams 上,进而反馈到 View.AttachInfo,最终反馈到 Canvas,用图表示其中的关系:
四 初步认识LayerType
在所有 Android 版本中,视图能够通过以下两种方式渲染到屏幕外缓冲区(离屏缓存):
- Canvas.saveLayer()
- 使用视图的绘制缓存 。
屏幕外缓冲区或层具有多种用途。在为复杂的视图添加动画效果或应用合成效果时,我们可以使用离屏缓存获得更好的效果。
此处介绍第二种:View 是通过 Canvas 绘制的,而 Canvas 既可以硬件加速绘制也可以软件绘制,于是 View 同样就拥有了这两种选择。
通过前面的分析可以看出,只要 Application/Activity/Window 开启了硬件加速,那么View 也就开启了硬件加速,那么如何关闭 View 的硬件加速呢?
View.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
该方法简化如下:
View.java
int mLayerType = LAYER_TYPE_NONE;
public void setLayerType(@LayerType int layerType,
@android.annotation.Nullable Paint paint) {
......
mLayerType = layerType;
......
}
public int getLayerType() {
return mLayerType;
}
View 里用 Int 表示 LayerType,可以理解为绘制离屏缓存。
- LAYER_TYPE_NONE–> 不使用绘制缓存
- LAYER_TYPE_SOFTWARE–> 使用软件绘制缓存
- LAYER_TYPE_HARDWARE–> 使用硬件绘制缓存
当使用 LAYER_TYPE_SOFTWARE 时,就"顺道"禁用了硬件加速,因此View.setLayerType(View.LAYER_TYPE_SOFTWARE, null) 产生了两个作用:
- 启用软件绘制缓存
- 禁用硬件加速
软件绘制、硬件加速绘制、启用离屏缓存三者对于 Draw 流程的影响将会在下篇分析,敬请关注。