天天看點

android開發經常碰到的crash(下)

結合拜讀包建強前輩著作的《App研發錄》,與自己開發過程中遇到的問題,繼續上篇對android開發經常碰到的crash探讨。上篇已經對空指針、數組越界、資料類型轉換、fragment引用資源出錯、dialog關閉報錯、adapter資料改變與清單更新不同步、試圖調用空對象的方法、清單滾動與重新整理沖突報錯、棧的無限遞歸引起棧溢出、多dex分包造成無法找到類定義、手機的CPU架構不同造成無法加載so檔案等問題作了介紹。接下來對xml布局檔案、系統資源碎片化控件使用、資源加載、資料庫操作、記憶體溢出等常見crash進行分析。

1、InflateException:Binary XML file(加載出錯:二進制XML檔案)

此類問題是由于XML檔案引用自定義控件或者本身書寫不符合規範引起的。在報錯地方有指出明确的出錯行數,很容易定位并且修複。

2、NoSuchMethodError(沒有該方法錯誤)

比如由于不同android版本提供方法的參數不同引發報錯:java.lang.NoSuchMethodError:android.os.Bundle.getString。這是因為SharePreference提供的getString有兩種版本參數:getString(String key) 與 getString(String key, int defaultValue)。第一種是android2.3以前的版本,而android2.3以後增加defaultValue這個參數。是以,系統碎片化引發的異常告誡我們多關注不同android版本的差異。

3、Unable to find app for caller android.app.ApplicationThreadProxy when stopping service Intent(Intent傳值太大報錯)

Intent裡面傳遞一般資料不會報此類錯誤,但如果換成Bitmap那麼就成為可能了。因為通常情況下,bundle攜帶超過1M資料,就會抛出該異常,而Bitmap往往會超過1M。是以,在使用Intent傳遞資料時,需要估計資料量大小,注意不要超過1M。

4、IllegalArgumentException:Receiver not registerd(非法參數異常:接受者沒有注冊)

在Activity中使用ViewFlipper控件,進行橫豎屏切換操作時就會發生此異常。一般原因是onDetachedFromWindow()在onAttachedToWindow()之前被調用引起的,因為還沒有關聯到對應窗體,就從窗體解除關聯。我在做垂直滾動公告時,使用ViewFlipper控件就碰到該問題。後來自定義一個控件繼承ViewFlipper重寫onDetachedFromWindow()方法,裡面加上try-catch。

protected void onDetachedFromWindow(){
   try{
     super.onDetachedFromWindow();
   }catch(IllegalArgumentException e){
    stopFlipping();
   }
}
           

5、Package manager has died(包管理器已經不存在)

出現此類異常說明PackageManager所在的程序已經不存在了,或者App本身處于崩潰狀态。解決方法是,每次擷取PackageManager時使用try-catch捕獲異常。

6、IllegalStateException:Can not perform this action after onSaveInstanceState(不合法狀态異常)

commit方法在Activity的onSaveInstanceState()之後被調用就會報錯。因為onSaveInstanceState方法是在Activity即将被銷毀前調用,以儲存Activity資料。其中一種情況是,如果在儲存完狀态後再給它添加Fragment就會報錯。對應解決方法是将commit替換成commitAllowingStateLoss()。

7、SQLException:cannot commit-no transaction is active(資料庫異常:無法送出—事務不處于活動狀态)

在事務中,逐條循環插入(for + insert)大量資料時會導緻此類 崩潰。因為android在SQLite插入資料時預設一條語句就是一個事務。解決方法是采用sql語句加上事務機制,操作完畢設定事務成功,把資料同步給資料庫。

private void insertToDB(SQLiteDatabase db, List<String> sqlList) {
        db.beginTransaction(); // 開始事務
        try {
            for(String sql:sqlList) {//自定義sql語句集合周遊
               db.execSQL(sql);
            }
            db.setTransactionSuccessful();//設定事務成功标志
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            db.endTransaction();//結束事務
        }
    }
           

8、CursorWindowAllocationException:cursor window allocation of 2048KB failed(遊标窗體配置設定異常)

這個異常是因為使用資料庫查詢時,忘記關閉遊标導緻的,記憶體洩露得多了,就導緻崩潰。對應解決方法是手動關閉cursor。

if(cursor != null && !cursor.isClosed())
   cursor.close();
           

9、SQLiteDatabaseLockedException:database is locked(資料庫被鎖異常)

當我們試圖在不同線程中建立多個資料庫連接配接時,就會抛出此異常。對應的解決方法是将資料庫設定為單例模式。如果是多程序,應該考慮使用Contentprovider。下面介紹下資料庫的單例模式寫法:

private static ContactDBManager mInstance = new ContactDBManager(ContactApplication.getInstance());
    public static ContactDBManager getInstance() {
        return mInstance;
    }
           

10、SQLiteDiskIOException:disk I/O error(資料庫磁盤IO操作異常)

有一種情況是webView中使用到資料庫作為緩存,讀寫緩存異常引發崩潰。webView有兩種緩存:網頁資料緩存和HTML5緩存。而且緩存模式有5種:

模式 介紹
LOAD_CACHE_ONLY 隻讀本地緩存資料
LOAD_DEFAULT 根據cache-control決定是否從網絡讀取
LOAD_CACHE_NORMAL 從API 11開始作用通LOAD_DEFAULT模式
LOAD_NO_CACHE 不使用緩存,隻從網絡擷取資料
LOAD_CACHE_ELSE_NETWORK 隻要本地有,無論是否過期,都是用緩存資料

根據以上5種模式,建議緩存政策為:判斷如果有網絡,使用LOAD_DEFAULT模式,否則使用模式LOAD_CACHE_ELSE_NETWORK。

11、SQLiteDiskIOException:disk I/O error(多線程操作資料庫引發磁盤讀寫錯誤)

考慮到多線程同時操作資料庫,建議在操作資料庫的方法加上syncronized關鍵字。

12、OutOfMemoryException(記憶體溢出)

android系統預先給每個app配置設定記憶體,比如80M。在AndroidManifest.xml檔案加上這個語句:<application  android:largeHeap =  true>,這樣可以獲得更大記憶體配置設定額度。但是不可能是無限制無上限。我在一個項目中通訊錄一次性加載20000條資料,就抛出此異常。性能好的手機抛出該異常的時間會延後一點,性能差的手機則會相對快一點。後來是采用分頁加載分頁顯示,才解決此問題。另外,建議大家盡量避免記憶體洩露,因為記憶體洩露多了最終會引發記憶體溢出;不要頻繁建立對象(能夠複用就複用);涉及多線程程式設計,使用線程池管理(任玉剛前輩在《android開發藝術探索》書中推薦)。

13、JSONException:no value for XXX(json解析溢出)

如果在使用getString("name")而不是optString("name"),并且name這個key值在json字元串中不存在,前者會抛出異常,後者則會傳回空值。類似地,還有getJSONArray方法,建議使用optJSONArray。

14、IllegalArgumentException:parameter must be a descendant of this view

這個崩潰是通過ViewGroup的offRectBetweenParentAndChild方法抛出的。該方法用來計算父子的重疊區域。它是通過所給的descendant這個view逐級向上尋找Parent View,同時将Rect轉換為同級坐标系來計算。如果在UI發生改變後,就會改變目前界面所擁有焦點的控件,就會引發此問題。解決方法是,每次都重新設定焦點,保證目前View始終獲得焦點。與此同時,還要清空其他控件搶占的焦點。

在國慶節期間花了一個下午,來補充android開發常見的crash,自己印象更加深刻,以此告誡自己不要犯類似錯誤。希望也可以幫助到讀者解決這些crash或者避免它們發生。

繼續閱讀