1. 程式人生 > >Android大圖繪製——硬體加速限制分析與方案

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進行繪製。