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