前言
Draw 過程系列文章
上篇分析了自定義View繪制流程及其常用方法:
Android 自定義View之Draw過程(上)
本篇將以硬件加速繪制與軟件繪制入口為切入點(diǎn),通過本篇文章,你將了解到:
1、什么是硬件加速
2、硬件加速的開啟與關(guān)閉
3、硬件加速繪制與軟件繪制分道揚(yáng)鑣的地方
4、初步認(rèn)識LayerType
什么是硬件加速
Android 3.0 之前,繪制操作通過CPU完成的,而在Android 3.0(含)之后,Android 2D 渲染管道支持硬件加速,也就是說Canvas繪制操作會使用GPU渲染。

硬件加速的作用
使用軟件繪制的時(shí)候,繪制操作都是通過CPU計(jì)算并寫入Bitmap,最終Bitmap直接渲染到屏幕上。當(dāng)某個(gè)View需要需要刷新的時(shí)候,計(jì)算刷新的臟區(qū)域,有相交的地方都需要重新繪制,也就是重走Draw過程。
使用硬件繪制的時(shí)候,繪制操作先將操作記錄到RenderNode里,當(dāng)渲染的時(shí)候?qū)⑦@些操作集合交給GPU處理,GPU更擅長處理浮點(diǎn)相關(guān)的運(yùn)算。
當(dāng)某個(gè)View需要刷新的時(shí)候,只需要重新生成與之相關(guān)的操作指令集,也就是Draw過程。甚至當(dāng)修改透明度等屬性的時(shí)候都不需要重走Draw過程,大大減少了無效的繪制請求,節(jié)約了CPU時(shí)間,提升程序運(yùn)行流暢度。
當(dāng)然,硬件加速需要更多資源,因此應(yīng)用會占用更多內(nèi)存。
另外有些Canvas API并不支持硬件加速,具體請參考官方文檔:https://developer.android.google.cn/guide/topics/graphics/hardware-accel
硬件加速的開啟與關(guān)閉
硬件加速控制層級
硬件加速分為4個(gè)層級來控制,分別為:
1、Application
2、Activity
3、Window
4、View
Application
在Application標(biāo)簽下,添加如下字段:
//關(guān)閉硬件加速
<application
android:hardwareAccelerated="false">
</application>
或
//開啟應(yīng)將加速
<application
android:hardwareAccelerated="true">
</application>
Android 4.0(含)之后默認(rèn)開啟硬件加速,也就是:
默認(rèn) android:hardwareAccelerated="true"
Activity
在Activity 標(biāo)簽下,添加如下字段:
//關(guān)閉硬件加速
<activity android:hardwareAccelerated="false">
</activity>
或
//開啟硬件加速
<activity android:hardwareAccelerated="true">
</activity>
和Application一樣,Activity也是默認(rèn)開啟硬件加速。
Window
//開啟硬件加速
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
目前來說,無法直接關(guān)閉Window的硬件加速。(1)后續(xù)解釋。
View
//禁用硬件加速
View.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
目前來說,無法直接開啟View的硬件加速。(2)后續(xù)解釋。
各個(gè)層級關(guān)系
按照控制的范圍來說:
Application>Activity>Window>View
來看看各個(gè)層級開關(guān)硬件加速之間的相互影響。
1、當(dāng)Application 開啟了硬件加速
Activity/Window/View 也開啟了硬件加速
當(dāng)然,Activity/View 可以選擇關(guān)閉硬件加速
2、當(dāng)Application 關(guān)閉了硬件加速
Activity/Window 也關(guān)閉了硬件加速
當(dāng)然,Activity/Window 可以單獨(dú)開啟硬件加速
3、當(dāng)Activity 開啟了硬件加速
Window/View 也開啟了硬件加速
當(dāng)然,View 也可以選擇關(guān)閉硬件加速
4、當(dāng)Activity 關(guān)閉了硬件加速
View 也關(guān)閉了硬件加速
也許你還是覺得比較疑惑,尤其是針對Window和View,到底怎么控制這兩個(gè)層級的硬件加速呢?
前面說過,硬件加速用在繪制上,而繪制的核心是Canvas,Canvas分為支持硬件加速的Canvas:RecordingCanvas和普通Cavas。因此只需要找到什么時(shí)候用
RecordingCanvas,就知道是否支持了硬件加速。
循著這個(gè)點(diǎn),接下來分析代碼里是如何控制硬件繪制與軟件繪制的。
硬件加速繪制與軟件繪制分道揚(yáng)鑣的地方
當(dāng)View 添加到Window后,會調(diào)用ViewRootImpl->setView(xx)方法。
關(guān)于過程細(xì)節(jié)請移步:Window/WindowManager 不可不知之事
#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) {
//初始標(biāo)記
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
//判斷是否開啟硬件加速
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
//判斷渲染是否可用
if (!ThreadedRenderer.isAvailable()) {
return;
}
//默認(rèn)沒有設(shè)置該標(biāo)記
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();
}
//創(chuàng)建渲染線程 ThreadedRenderer
//并賦值給mAttachInfo->mThreadedRenderer
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
attrs.getTitle().toString());
if (mAttachInfo.mThreadedRenderer != null) {
//將mAttachInfo->mHardwareAccelerated 標(biāo)記位true
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
}
}
}
}
以上代碼重點(diǎn)關(guān)注兩個(gè)點(diǎn):
1、硬件加速標(biāo)記存放在WindowManager.LayoutParams 的flags參數(shù)里
2、如果支持硬件加速,則將標(biāo)記與渲染線程對象記錄到View.AttachInfo里
我們知道View展示三大流程是在 performTraversals(xx)里,在該方法里,依次進(jìn)行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;
}
以上代碼重點(diǎn)關(guān)注兩個(gè)點(diǎn):
1、在ViewRootImpl->draw(xx)方法里開始了繪制的分歧
2、分歧的依據(jù)是View.AttachInfo != null
因此現(xiàn)在的問題簡化為:
WindowManager.LayoutParams里的flags 硬件加速標(biāo)記決定繪制是否走硬件加速流程
WindowManager.LayoutParams 從哪來
回顧一下View是如何添加到Window的:
1、獲取WindowManager對象
2、設(shè)置LayoutParams屬性
3、將View添加到Window里
當(dāng)調(diào)用:
wm.addView(textView, layoutParams);
后續(xù)調(diào)用如下:

可以看出,layoutParams 經(jīng)過層層傳遞最后到達(dá)ViewRootImpl里的setView(xx),也即是上邊分析的方法。
因此,我們有理由相信,layoutParams.flags 有關(guān)硬件加速的標(biāo)記一定在某個(gè)步驟被賦值了。
首先第一個(gè)可能賦值的地方:
layoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
wm.addView(textView, layoutParams);
此過程即為為Window 開啟硬件加速。
再來看看剩下的步驟,發(fā)現(xiàn)只有WindowManagerGlobal addView(xx)會給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(xx) 會走
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//該分支是直接WindowManager.addView(xx)
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
//(context.getApplicationInfo().flags 是在Application 層級設(shè)置的標(biāo)記
//如果設(shè)置了該標(biāo)記,那么將該標(biāo)記存儲在wparams
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
...
synchronized (mLock) {
try {
...
//此時(shí),wparams 記錄了是否有硬件加速
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
}
繼續(xù)來看看adjustLayoutParamsForSubWindow:
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
...
//1、mHardwareAccelerated 表示該Window是否支持硬件加速,該值是由Activity 在xml里配置的硬件加速標(biāo)記決定的
//2、mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) 表示之前是否給Window 設(shè)置了硬件加速標(biāo)記
if (mHardwareAccelerated ||
(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
//如果滿足,記錄在WindowManager.LayoutParams 里
wp.flags |= FLAG_HARDWARE_ACCELERATED;
}
}
從以上兩段代碼可以看出:
1、Window與Activity關(guān)聯(lián),如果該Activity開啟了硬件加速,那么該Window也開啟了硬件加速。
2、Window不與Activity 關(guān)聯(lián),如果Application 開啟了硬件加速,那么該Window也開啟了硬件加速。
要關(guān)閉Window層級的硬件加速,只需要Activity/Application 禁用硬件加速即可。
實(shí)際上,不管Activity/Application/Window 如何設(shè)置硬件加速標(biāo)記,都是反饋到WindowManager.LayoutParams 上,進(jìn)而反饋到View.AttachInfo,最終反饋到Canvas
用圖表示其中的關(guān)系:

初步認(rèn)識LayerType
在所有 Android 版本中,視圖能夠通過以下兩種方式渲染到屏幕外緩沖區(qū)(離屏緩存):
1、Canvas.saveLayer()
2、使用視圖的繪制緩存 。
屏幕外緩沖區(qū)或?qū)泳哂卸喾N用途。在為復(fù)雜的視圖添加動畫效果或應(yīng)用合成效果時(shí),我們可以使用離屏緩存獲得更好的效果。
此處介紹第二種:
View是通過Canvas繪制的,而Canvas既可以硬件加速繪制也可以軟件繪制,于是View同樣就擁有了這兩種選擇。
通過前面的分析可以看出,只要Application/Activity/Window 開啟了硬件加速,那么View 也就開啟了硬件加速,那么如何關(guān)閉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-->使用硬件繪制緩存
當(dāng)使用LAYER_TYPE_SOFTWARE時(shí),就"順道"禁用了硬件加速,因此View.setLayerType(View.LAYER_TYPE_SOFTWARE, null) 產(chǎn)生了兩個(gè)作用:
1、啟用軟件繪制緩存
2、禁用硬件加速
軟件繪制、硬件加速繪制、啟用離屏緩存 三者對于Draw流程的影響將會在下篇分析,敬請關(guān)注。
本文基于 Android 10.0