作者
大家好,我叫大圣;
自己于2018年5月加入37手游安卓团队,曾经上任于爱拍等互联网公司;
目前是37手游安卓团队的国内担任人,首要担任相关业务开发和一些日常业务统筹等。
布景
最近在处理一个游戏内嵌社区页面卡顿问题的进程中,发现在Manifest的Activity中有将硬件加速封闭的配置,假如将其敞开,那么页面卡顿的问题就处理了。对这一块的知识还不是很了解,也不知道直接打开硬件加速对游戏是否有影响。所以乎在网上查了一波他人的文章,做了一个简单的总结。
CPU与GPU
在了解什么是硬件加速之前,先了解一下什么是CPU和GPU。 CPU(Centr(l Processing Unit,中央处理器)是核算机设备核⼼器材,⽤于执⾏ 程序代码,软件开发者对此都很了解;GPU(Gr(phics Processing Unit,图形处理 器)首要⽤于处理图形运算,一般所说“显卡”的核⼼部件便是GPU。
- 黄色的Control为操控器,用于和谐操控整个CPU的运转,包括取出指令、操控其他模块的运转等;
- 绿色的ALU(Arithmetic Logic Unit)是算术逻辑单元,用于进行数学、逻辑运算;
- 橙色的Cache和DRAM分别为缓存和RAM,用于存储信息。 从上面的结构图能够看出,CPU的操控器较为复杂,拿手各种复杂的逻辑运算;GPU的操控器比较简单,但包括了大量ALU,拿手大量的数学运算。
硬件加速的首要原理,便是通过底层软件代码,将CPU不拿手的图形核算转换成GPU专用指令,由GPU完结。
显卡和GPU不是同一个概念,显卡指的是包括GPU的一个完整的卡,GPU是单指显卡的核心芯片,是一颗芯片;
听说比特币挖矿的机器都是采用GPU运算的,是不是和上面的原理相同,挖矿是大量的数学核算,GPU愈加拿手呢?
Android中的硬件加速
接下来看看Android中怎么使用硬件加速的。
怎么敞开硬件加速
从 Android 3.0(API 等级 11)开端,Android 2D 烘托管道⽀持硬件加速,也 便是说,在 View 的画布上执⾏的一切制作操作都会使⽤ GPU。启⽤硬件加速需求 更多资源,因此应⽤会占⽤更多内存。
假如您的⽬标 API 等级为 14 及更⾼等级,则硬件加速默许处于启⽤状态,但也能够 清晰启⽤该功用。假如您的应⽤仅使⽤规范视图和 Dr(w(ble,则大局启⽤硬件加速 不会形成任何不良制作效果。
您能够在以下等级操控硬件加速:
- 应⽤
- Activity
- 窗⼝
- 视图
应⽤等级
在 Android 清单⽂件中,将以下特点添加到 标记中,为整个应⽤启⽤硬件加速:
<application android:hardwareAccelerated="true" ...>
Activity 等级
假如大局启用硬件加速后,您的使用无法正常运转,则您也能够针对各个 Activity 操控硬件加速。要在 Activity 等级启用或停用硬件加速,您能够使用 元素的 android:hardwareAccelerated 特点。以下示例展现了怎么为整个使用启用硬件加速,但为一个 Activity 停用硬件加速:
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
窗口等级
假如您需求完成更精细的操控,能够使用以下代码为给定窗口启用硬件加速:
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
视图等级
您能够使用以下代码在运转时为单个视图停用硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
制作流程上面的优化
在说完以上CPU和GPU的特性,以及Android怎么操控硬件加速之后,来看看在界面View在改写和制作进程中,详细是哪些要害点来做了硬件加速的操控。
硬件加速情况下Canvas生成的是DisplayListCanvas目标
在View的制作进程中会沿着draw(canvas,parent,drawingTime) –> draw(canvas) –> onDraw –> dispachDraw 这条递归途径往下走,在draw(canvas,parent,drawingTime)中,假如是硬件加速情况下,会走updateDisplayListIfDirty()。
if (drawingWithRenderNode) {
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
// ...
renderNode = null;
drawingWithRenderNode = false;
}
}
在updateDisplayListIfDirty()办法中,生成了DisplayListCanvas目标,然后调用draw(canvas)办法,此刻Canvas已经不是一般的Canvas了,而是DisplayListCanvas。
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);
try {
//...
if (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
draw(canvas);
}
}
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
在调用DisplayListCanvas的drawXXX办法进程中,并没有履行真实的制作,而是用构建DisplayList,并保存在RenderNode中,通过renderNode.end(canvas)进行该操作。
/**
* Ends the recording for this display list. A display list cannot be
* replayed if recording is not finished. Calling this method marks
* the display list valid and {@link #isValid()} will return true.
*
* @see #start(int, int)
* @see #isValid()
*/
public void end(DisplayListCanvas canvas) {
long displayList = canvas.finishRecording();
nSetDisplayList(mNativeRenderNode, displayList);
canvas.recycle();
}
JNI层将DisplayListCanvas的操作转化成DisplayList
一个RenderNode包括若干个DisplayList,一般一个RenderNode对应一个View,包括View本身及其子View的一切DisplayList。
在ViewRootImpl中,假如是支撑硬件加速,会履行mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this)办法。
// ViewRootImpl.java
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
//...
}
ThreadedRenderer的draw进程,会调用到updateRootDisplayList,然后调用updateViewTreeDisplayList,此进程会更新整个View树的DisplayList,终究履行ThreadedRenderer.nSyncAndDrawFrame来发动线程对DisplayList进行终究的制作操作
// ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
...
updateRootDisplayList(view, callbacks);
...
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
}
因为制作流程的不同,硬件加速在界面内容产生重绘的时分制作流程能够得到优化,避免了一些重复操作,然后大幅提高制作功率。在updateDisplayListIfDirty办法从姓名能够看出,更新Displaylist是产生在脏区域。
在硬件加速封闭时,制作内容会被 CPU 转换成实践的像素,然后直接烘托到屏幕。详细来说,这个「实践的像素」,它是由 Bitmap
来承载的。在界面中的某个 View 因为内容产生改动而调用 invalidate()
办法时,假如没有敞开硬件加速,那么为了正确核算 Bitmap
的像素,这个 View
的父 View、父 View 的父 View 乃至一直向上直到最尖端 View,以及一切和它相交的兄弟 View
,都需求被调用 invalidate()
来重绘。一个 View 的改动使得大半个界面甚至整个界面都重绘一遍,这个工作量是非常大的。
而在硬件加速敞开时,前面说过,制作的内容会被转换成 GPU 的操作保存下来(承载的方式称为 display list,对应的类也叫做 DisplayList
),再转交给 GPU。因为一切的制作内容都没有变成终究的像素,所以它们之间是彼此独立的,那么在界面内容产生改动的时分,只要把产生了改动的 View 调用 invalidate()
办法以更新它所对应的 GPU 操作就好,至于它的父 View 和兄弟 View,只需求保持原样。那么这个工作量就很小了。
在默许情况下,View的clipChildren的特点为true,即每个View制作区域不能超出其父View的规模。假如设置一个页面根布局的clipChildren特点为false,则子View能够超出父View的制作区域。
上图中右边的ViewGroup的clipChildren特点即为false,其子View的制作超出了本身规模。
当一个View触发invalidate,且没有播放动画、没有触发layout的情况下:
-
关于全不通明的View,其本身会设置标志位
PFLAG_DIRTY
,其父View会设置标志位PFLAG_DIRTY_OPAQUE
。在draw(canvas)
办法中,只有这个View本身重绘。上图中的TextView布景全不通明,此刻调用TextView的invalidate,自由其本身重制作。
-
关于可能有通明区域的View,其本身和父View都会设置标志位
PFLAG_DIRTY
。-
clipChildren为true时,脏区会被转换成ViewRoot中的Rect,改写时层层向下判断,当View与脏区有堆叠则重绘。假如一个View超出父View规模且与脏区堆叠,但其父View不与脏区堆叠,这个子View不会重绘。
上图中假如TextView布景通明,那么脏区域会从TextView投影至ViewRoot上面的Rect,也便是绿色区域,此刻在这个投影之上的View都会被触发制作(即TextView、左边ViewGroup、ViewRoot)
-
clipChildren为false时,
ViewGroup.invalidateChildInParent()
中会把脏区扩大到本身整个区域,所以与这个区域堆叠的一切View都会重绘。上图中右边ViewGroup的clipChildren为false,那么脏区域即为右边ViewGroup的整个投影的区域,与此堆叠的View都会被重新制作。
-
实践问题处理
布景中说到的游戏内嵌web页面卡顿的问题, 在敞开硬件加速后卡顿的问题处理了。但是因为对游戏引擎不了解,所以不清楚敞开硬件加速是否会影响到游戏的运转,比方游戏界面制作会不会错乱,耗电量会不会添加等等问题。其实从上面第三末节说到的,Android不只仅能够对整个使用敞开硬件加速还支撑对单个Activity进行设置,那么这个问题其实就很好处理了, 直接将显示Web的页面写成一个Activity单独敞开硬件急速即可,这样完全不会影响到游戏。
总结
在硬件加速进程中,充沛利用CPU和GPU各自的特性,将逻辑部分交个CPU处理,将大量的数学核算交个GPU处理,在硬件层面对整个进程功率提高。界面改写进程中,CPU只会更新需求重绘的DisplayList,进一步提高制作功率。
上文的介绍只是在一些要害的代码上面做了讲解,未深化介绍细节;关于DisplayListCanvas怎么生成DisplsyList的进程,属于JNI层的操作,文档并未触及。关于RenderNode是怎么安排DiaplayList,以及View树的各个RenderNode是怎么安排的都未说明,文章充沛学习了一些大神文章的内容,文末有参阅链接,需求深化了解能够自行查阅。
结束语
进程中有问题或者需求沟通的同学,能够添加微信号AndroidAssistant37
,然后进群进行问题和技术的沟通等。
参阅链接
- /post/684490…
- developer.android.com/guide/topic…
- www.codeleading.com/article/337…
- /post/684490…