最近項目裏面使用到了surfaceView,在子線程中進行頁面繪製,但是出現了一個莫名其妙的bug,該Exception,沒有什麼信息,無從判斷是什麼原因導致的
所以,需要我們去看看爲什麼會導致這樣的問題,我們的代碼是如下
if (mSurfaceHolder != null) {
mCanvas = mSurfaceHolder.lockCanvas();
}
try {
mCanvas.drawBitmap();//將bitmap繪製到畫布上
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mCanvas != null && mSurfaceHolder != null && !isDestroy) {
// 將畫布解鎖並顯示在屏幕上
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
大致流程就是,先鎖定SurfaceView的畫布,然後將bitmap繪製上去,再解鎖畫布,但是在android 4.3的手機上出現了大量上述的錯誤。
查閱資料,發現網上大多都是當手機按下home鍵時,surfaceView已經銷燬了,但是有概率子線程還是在進行繪製操作,這樣就有可能出現錯誤,
所以,方式一:
加上home鍵的back監聽,及時更改surfaceView的狀態標誌位
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 當按返回鍵時,將線程停止,避免surfaceView銷燬了,而線程還在運行而報錯
if (keyCode == KeyEvent.KEYCODE_BACK) {
mIsThreadRunning = false;
isDestroy = true;//將surfaceView標識爲已經銷燬
}
return super.onKeyDown(keyCode, event);
}
雖然,進行了修改,但是在android 4.3的手機上,還是有機率會報出該異常,那麼我們去看下unlockCanvasAndPost的源碼
看看,他到底是爲什麼會拋出上面的異常
該方法的具體實現在Surface類中
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) {
mHwuiContext.unlockAndPost(canvas);
} else {
unlockSwCanvasAndPost(canvas);
}
}
}
然後我們可以看見,要麼調用的是mHwuiContext的方法,要麼是調用的unlocakSwCanvasAndPost方法,而堆棧信息已經說明了,是調用的unlockSwCanvasAndPost方法,這個mHwuiContext其實就是是否開啓硬件加速進行繪製,他的初始化是在下面這個方法裏面的
public Canvas lockHardwareCanvas() {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext == null) {
mHwuiContext = new HwuiContext();
}
return mHwuiContext.lockCanvas(
nativeGetWidth(mNativeObject),
nativeGetHeight(mNativeObject));
}
}
因爲,跟我們要解決的問題無關,所以暫時不去管它。我們繼續看unlockSwCanvasAndPost方法
private void unlockSwCanvasAndPost(Canvas canvas) {
if (canvas != mCanvas) {
throw new IllegalArgumentException("canvas object must be the same instance that "
+ "was previously returned by lockCanvas");
}
if (mNativeObject != mLockedObject) {
Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
Long.toHexString(mLockedObject) +")");
}
if (mLockedObject == 0) {
throw new IllegalStateException("Surface was not locked");
}
try {
nativeUnlockCanvasAndPost(mLockedObject, canvas);
} finally {
nativeRelease(mLockedObject);
mLockedObject = 0;
}
}
我們可以看到,該方法雖然拋出了異常,但是很明顯不是我們拋的那個,因爲我們的那個異常是沒有異常信息的。那麼需要繼續查看nativeUnlockCanvasAndrPost方法了,從命名規則就可以看出,這是個jni的方法。
private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
查看代碼,也確實是這樣,是個native方法。
而且我們發現,這個native方法並沒有拋我們出現的這個異常,這一路的調用也都沒有 拋出異常。這說明,拋異常的地方,是在android的jni代碼裏面了,這可不好辦了。。。
讓我們思索片刻。。。我們好像忘記了萬能的google,所以讓我們google一下
最終,我們找到了下面這個地方
https://code.google.com/p/android/issues/detail?id=58385
坑爹呢這是!!!
看來,衆多的外國同行也遇到了android 4.3出現的這個問題,跟我們發生的情況一模一樣,而且並沒有什麼明確的解決辦法。
兜兜轉轉一大圈。。。原來還是需要android的開發組去解決,所以目前我們能做的就只有如下方式了
方式二:
Surface surface = mSurfaceHolder.getSurface();
if (mCanvas != null && mSurfaceHolder != null && !isDestroy && surface != null && surface.isValid()) {
//說明當前幀的時候,該控件已經銷燬了
try {
mSurfaceHolder.unlockCanvasAndPost(mCanvas);//手動try catch一下這個方法,讓程序在4.3的手機上不至於崩潰
}catch (Exception e){
e.printStackTrace();
}
}
第一個,加載surface.isValid()的判斷,如果surface已經無效了,那麼就不會執行下面這個函數第二個,手動加載try catch,把異常抓一下,至少不至於在4.3的手機上崩潰
最後,如此做法並非萬全之策,實屬無奈之舉,各位如果有好的做法,請一定聯繫我。。。謝謝