SurfaceView的nativeUnlockCanvasAndPost 導致的沒有任何錯誤信息的IllegalArgumentException

最近項目裏面使用到了surfaceView,在子線程中進行頁面繪製,但是出現了一個莫名其妙的bug,該Exception,沒有什麼信息,無從判斷是什麼原因導致的

<Image_1>

所以,需要我們去看看爲什麼會導致這樣的問題,我們的代碼是如下

  		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的手機上崩潰


最後,如此做法並非萬全之策,實屬無奈之舉,各位如果有好的做法,請一定聯繫我。。。謝謝





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章