Android大图绘制——硬件加速限制分析与方案

Posted David-Kuper

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android大图绘制——硬件加速限制分析与方案相关的知识,希望对你有一定的参考价值。

最近在做PhotoView图片的效果定制时,在加载展示图片情境下,统一把图片按照屏幕宽度作为固定值,计算宽高的缩放比然后对Bitmap进行伸缩,这样可以避免一般情况下的大图加载产生——OOM和trying to draw too large(xxxbytes) bitmap的问题。
当即便这样,也还是会有加载的图片尺寸超过限制的时候,就经常会看到这个warning,图片显示不出来:

Bitmap too large to be uploaded into a texture (1080x9431, max=8192x8192)
Bitmap too large to be uploaded into a texture (1080x9431, max=8192x8192)

因为指定的比例的按照屏幕宽度进行宽高比缩放,宽度到了合适的范围,但是当图片太长的时候,还是会触发上面的问题,无法显示图片。

方案一:关闭硬件加速

上面的警告是由于OpenGL硬件加速造成的,可以在Activity、Application、Window、甚至View中把硬件加速关闭即刻。关闭的方式如下:

1、硬件加速的级别
Application

<application 
    android:hardwareAccelerated="false" 
...>
</application>
Activity
<application 
    android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>
Window
getWindow().setFlags(
   WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
   WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Note: 你可以关闭View级别的硬件加速,但是你不能在View级别开启硬件加速,因为它还依赖其他的设置

2、两种获取是否支持硬件加速的方式

View.isHardwareAccelerated()   //returns true if the View is attached to a hardware accelerated window.
Canvas.isHardwareAccelerated() //returns true if the Canvas is hardware accelerated

如果必须进行这样的验证,建议你在draw的代码块中使用:Canvas.isHardwareAccelerated(),因为如果一个View被attach到一个硬件加速的Window上,即使没有硬件加速的Canvas,它也是可以被绘制的。比如:将一个View以bitmap的形式进行缓存通过关闭硬件加速可以避免上面的问题,但是会让使用软件变得卡顿。因此这不是一个好的解决方案,直接放弃。


方案二:获取硬件加速的最大限制值,然后再对将Bitmap进行裁剪、缩放

既然是超出了最大限制,那么只要我们将Bitmap缩放到合适的值即可。那么思路就是:
Step1:获取OpenGL硬件加速最大长宽限制
Step2:开启大图模式,阻止PhotoView默认情况下的ScaleType:FIT_CENTER
Step3:获取Bitmap进行缩放、裁剪处理
Step4:重新刷新绘制

获取OpenGL硬件加速最大限制方式如下:

/**
 * 在Lollipop版本之前可以直接获取硬件加速值
 */
private void getGLESTextureLimitBelowLollipop() 
  GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);


/**
 * 在Lollipop版本之后,需要用下面的方式获取
 * 拿到OpenGL硬件加速所允许的最大长宽,用来做二次bitmap压缩
 */
private void getGLESTextureLimitEqualAboveLollipop() 
  EGL10 egl = (EGL10) EGLContext.getEGL();
  EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
  int[] vers = new int[2];
  egl.eglInitialize(dpy, vers);
  int[] configAttr = 
      EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER, EGL10.EGL_LEVEL, 0,
      EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, EGL10.EGL_NONE
  ;
  EGLConfig[] configs = new EGLConfig[1];
  int[] numConfig = new int[1];
  egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
  if (numConfig[0] == 0) // TROUBLE! No config found.

  
  EGLConfig config = configs[0];
  int[] surfAttr = 
      EGL10.EGL_WIDTH, 64, EGL10.EGL_HEIGHT, 64, EGL10.EGL_NONE
  ;
  EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
  final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;  // missing in EGL10
  int[] ctxAttrib = 
      EGL_CONTEXT_CLIENT_VERSION, 1, EGL10.EGL_NONE
  ;
  EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
  egl.eglMakeCurrent(dpy, surf, surf, ctx);
  GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
  egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
  egl.eglDestroySurface(dpy, surf);
  egl.eglDestroyContext(dpy, ctx);
  egl.eglTerminate(dpy);

拿到了硬件加速值之后,就可以对当前的大图进行测量,然后决定是否需要剪裁、缩放。


方案三:分块显示大图Bitmap

对于超大的Bitmap可以进行分块展示。这种方案有两种不同实现:
(1)把Bitmap分块切割到List中,使用ListView或者RecyclerView中展示。
——这样做比较方便,容易实现,但是方法优点投机取巧。
实现步骤:
这种方式的实现步骤主要就在于Bitmap的分割,只要确定了每一个子View的大小,那么分割就完成了。方法很简单,就不在赘述了。

(2)使用BitmapRegionDecoder类,重写ImageView的Touch事件,保证在手指移动内容的时候将对应绘制区域的Bitmap进行加载绘制,也就是说只加载当前绘制区的Bitmap。
——这样做的方式最好,官方推荐,实现方式比第一种稍微复杂一点。
实现步骤:
step1:监听ImageView的onTouch事件。
step2:传入图片的输入流inputStream,解析Bitmap的大小,初始化可见绘制区Rect的坐标。
step3:在滑动过程中确定可见区Rect的坐标,然后通过BitmapRegionDecoder加载对应的内容至Rect中,生成可见区的Bitmap
step4:在onDraw中对可见区Bitmap进行绘制。

以上是关于Android大图绘制——硬件加速限制分析与方案的主要内容,如果未能解决你的问题,请参考以下文章

PhotoView大图绘制——硬件加速限制

PhotoView大图绘制——硬件加速限制

Android ImageView加载超长图片解决方案

4.2.Android 硬件加速补充

Android硬件加速

Android高效加载大图多图解决方案,有效避免程序OOM