富文本编辑器关于Android硬件加速的问题
本文所指的富文本编辑器是基于(EditText+Span)实现的
参见:https://www.cnblogs.com/WideMouth/p/13049581.html
硬件加速(Google官方文档:https://developer.android.google.cn/guide/topics/graphics/hardware-accel)
从 Android 3.0(API 级别 11)开始,Android 2D 渲染管道支持硬件加速,也就是说,在
View
的画布上执行的所有绘制操作都会使用 GPU。启用硬件加速需要更多资源,因此应用会占用更多内存。如果您的目标 API 级别为 14 及更高级别,则硬件加速默认处于启用状态,但也可以明确启用该功能。如果您的应用仅使用标准视图和
Drawable
,则全局启用硬件加速不会造成任何不良绘制效果。不过,并非所有 2D 绘制操作都支持硬件加速,因此启用硬件加速可能会影响您的部分自定义视图或绘制调用。具体问题通常以不可见的元素、异常或错误渲染的像素显现。为了解决此问题,Android 允许您在多个级别选择是启用还是停用硬件加速。请参阅控制硬件加速。如果您的应用执行自定义绘制,请在启用硬件加速的实际硬件设备上测试应用,以检查是否存在任何问题。不受支持的绘制操作部分介绍了已知硬件加速问题和相应的解决方案。
问题
显然,硬件加速并不是完美的,他不能适配所有的绘制操作,以下为Google官方列出的绘制操作支持的API级别。
不受支持的绘制操作
经过硬件加速后,2D 渲染管道支持最常用的 Canvas 绘制操作以及很多不太常用的操作。用于渲染 Android 系统内置应用、默认微件和布局以及常见的高级视觉效果(例如反射和平铺纹理)的所有绘制操作均受到支持。
下表介绍了各种操作在各个 API 级别的支持级别:
第一个支持的 API 级别 Canvas drawBitmapMesh()(颜色数组) 18 drawPicture() 23 drawPosText() 16 drawTextOnPath() 16 drawVertices() ✗ setDrawFilter() 16 clipPath() 18 clipRegion() 18 clipRect(Region.Op.XOR) 18 clipRect(Region.Op.Difference) 18 clipRect(Region.Op.ReverseDifference) 18 clipRect()(通过旋转/透视) 18 Paint setAntiAlias()(适用于文本) 18 setAntiAlias()(适用于线条) 16 setFilterBitmap() 17 setLinearText() ✗ setMaskFilter() ✗ setPathEffect()(适用于线条) 28 setShadowLayer()(除文本之外) 28 setStrokeCap()(适用于线条) 18 setStrokeCap()(适用于点) 19 setSubpixelText() 28 Xfermode PorterDuff.Mode.DARKEN(帧缓冲区) 28 PorterDuff.Mode.LIGHTEN(帧缓冲区) 28 PorterDuff.Mode.OVERLAY(帧缓冲区) 28 Shader ComposeShader 内的 ComposeShader 28 ComposeShader 内相同类型的着色器 28 ComposeShader 上的本地矩阵 18 画布缩放
硬件加速 2D 渲染管道最初是为了支持不可缩放的绘制构建的,其中一些绘制操作会以较高的缩放值显著降低质量。这些操作实现为按 1.0 的缩放值绘制的纹理,由 GPU 进行转换。从 API 级别 28 开始,所有绘制操作都可以顺利缩放。
下表列出了何时更改实现以正确处理大规模缩放:
要缩放的绘制操作 第一个支持的 API 级别 drawText() 18 drawPosText() 28 drawTextOnPath() 28 简单的形状* 17 复杂的形状* 28 drawPath() 28 阴影层 28 注意:“简单”形状指的是使用 Paint 发出的 drawRect()、drawCircle()、drawOval()、drawRoundRect() 和 drawArc()(其中 useCenter=false)命令,该 Paint 不包含 PathEffect,也不包含非默认联接(通过 setStrokeJoin()/setStrokeMiter())。这些绘制命令的其他实例都属于上表中的“复杂”形状。
BUG
由此可见,我们自定义绘制实现的富文本编辑器是不能应用硬件加速的(附bug,见底文),原因是一些基本的绘制操作需要更高的API,如drawPicture()、setPathEffect()、drawTextOnPath(),而如果目标 API 级别为 14 及更高级别,则硬件加速默认处于启用状态,所以我们需要手动关闭相应的硬件加速,参见。
控制硬件加速
您可以在以下级别控制硬件加速:
- 应用
- Activity
- 窗口
- 视图
应用级别
在 Android 清单文件中,将以下属性添加到
<application>
标记中,为整个应用启用硬件加速:
<application android:hardwareAccelerated="true" ...>
Activity 级别
如果全局启用硬件加速后,您的应用无法正常运行,则您也可以针对各个 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);
注意:您目前无法在视图级别启用硬件加速。除了停用硬件加速之外,视图层还具有其他功能。如需详细了解视图层的具体用途,请参阅视图层。
确定视图是否经过硬件加速
有时,应用有必要了解当前是否经过硬件加速,尤其是对于自定义视图等内容。如果您的应用执行大量自定义绘制,但并非所有操作都得到新渲染管道的正确支持,这就会特别有用。
您可以通过以下两种不同的方式检查应用是否经过硬件加速。
- 如果
View
已附加到硬件加速窗口,则View.isHardwareAccelerated()
会返回true
。- 如果
Canvas
经过硬件加速,则Canvas.isHardwareAccelerated()
会返回true
如果您必须在绘制代码中执行这项检查,请尽可能使用
Canvas.isHardwareAccelerated()
,而不是View.isHardwareAccelerated()
。如果某个视图已附加到硬件加速窗口,则仍可以使用未经过硬件加速的画布进行绘制。例如,将视图绘制为位图以进行缓存就会发生这种情况。
总结
经在实际智能手机设备上测试检查,发现在Activity层面关闭硬件加速效果最佳,建议关闭。
附bug:
2020-04-10 10:32:15.166 10570-10570/com.widemouth.wmrichtexteditor E/AndroidRuntime: FATAL EXCEPTION: main Process: com.widemouth.wmrichtexteditor, PID: 10570 java.lang.ArrayIndexOutOfBoundsException: length=3; index=-1 at android.text.DynamicLayout.getBlockIndex(DynamicLayout.java:648) at android.widget.Editor.drawHardwareAccelerated(Editor.java:1703) at android.widget.Editor.onDraw(Editor.java:1672) at android.widget.TextView.onDraw(TextView.java:6882) at android.view.View.draw(View.java:19208) at android.view.View.updateDisplayListIfDirty(View.java:18158) at android.view.View.draw(View.java:18936) at android.view.ViewGroup.drawChild(ViewGroup.java:4236) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022) at android.view.View.draw(View.java:19211) at android.widget.ScrollView.draw(ScrollView.java:1739) at android.view.View.updateDisplayListIfDirty(View.java:18158) at android.view.View.draw(View.java:18936) at android.view.ViewGroup.drawChild(ViewGroup.java:4236) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022) at android.view.View.updateDisplayListIfDirty(View.java:18149) at android.view.View.draw(View.java:18936) at android.view.ViewGroup.drawChild(ViewGroup.java:4236) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022) at android.view.View.updateDisplayListIfDirty(View.java:18149) at android.view.View.draw(View.java:18936) at android.view.ViewGroup.drawChild(ViewGroup.java:4236) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022) at android.view.View.updateDisplayListIfDirty(View.java:18149) at android.view.View.draw(View.java:18936) at android.view.ViewGroup.drawChild(ViewGroup.java:4236) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022) at android.view.View.updateDisplayListIfDirty(View.java:18149) at android.view.View.draw(View.java:18936) at android.view.ViewGroup.drawChild(ViewGroup.java:4236) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022) at android.view.View.updateDisplayListIfDirty(View.java:18149) at android.view.View.draw(View.java:18936) at android.view.ViewGroup.drawChild(ViewGroup.java:4236) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022) at android.view.View.draw(View.java:19211) at com.android.internal.policy.DecorView.draw(DecorView.java:855) at android.view.View.updateDisplayListIfDirty(View.java:18158) at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:676) at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:682) at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:790) at android.view.ViewRootImpl.draw(ViewRootImpl.java:3084) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2888) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2441) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1430) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6885) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966) at android.view.Choreographer.doCallbacks(Choreographer.java:778) at android.view.Choreographer.doFrame(Choreographer.java:713) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952) at android.os.Handler.handleCallback(Handler.java:790) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6618) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:810)