Android實際開發bug大總結

目錄介紹

  • 1.1 java.lang.UnsatisfiedLinkError找不到so庫異常
  • 1.2 java.lang.IllegalStateException非法狀態異常
  • 1.3 android.content.res.Resources$NotFoundException
  • 1.4 java.lang.IllegalArgumentException參數不匹配異常
  • 1.5 IllegalStateException:Can't compress a recycled bitmap
  • 1.6 java.lang.NullPointerException空指針異常
  • 1.7 android.view.WindowManager$BadTokenException異常
  • 1.8 java.lang.ClassCastException類轉化異常
  • 1.9 Toast運行在子線程問題,handler問題
  • 2.1 java.lang.ClassNotFoundException類找不到異常
  • 2.2 java.util.concurrent.TimeoutException鏈接超時崩潰
  • 2.3 java.lang.NumberFormatException格式轉化錯誤
  • 2.4 java.lang.IllegalStateException: Fragment not attached to Activity
  • 2.5 ArrayIndexOutOfBoundsException 角標越界異常
  • 2.6 IllegalAccessException 方法中構造方法權限異常
  • 2.7 android.view.WindowManager$BadTokenException,dialog彈窗異常
  • 2.8 java.lang.NoClassDefFoundError 找不到類異常
  • 2.9 Android出現:Your project path contains non-ASCII characters.
  • 3.1 OnErrorNotImplementedException【 Can't create handler inside thread that has not called Looper.prepare()】
  • 3.2 adb.exe,start-server' failed -- run manually if necessary
  • 3.3 java.lang.IllegalStateException: ExpectedBEGIN_OBJECT but was STRING at line 1 column 1 path $
  • 3.4 android.content.ActivityNotFoundException: No Activity found to handle Intent
  • 3.5 Package manager has died致使崩潰
  • 3.6 IllegalArgumentException View添加窗口錯誤
  • 3.7 IllegalStateException: Not allowed to start service Intent異常崩潰
  • 3.8 java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState
  • 3.9 在Fragment中經過getActivity找不到上下文,報null致使空指針異常
  • 4.1 IllegalArgumentException致使崩潰【url地址傳入非法參數,轉義字符】
  • 4.2 ClassNotFoundException: Didn't find class "" on path: /data/app/*錯誤
  • 4.3 NoClassDefFoundError異常【該異常表示找不到類定義】
  • 4.4 公司以前項目使用客服udesk,sdk更新後初始化致使崩潰問題
  • 4.5 java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception
  • 4.6 java.util.concurrent.ExecutionException: com.android.ide.common.process.ProcessException
  • 4.7 00768556 /vendor/lib/libllvm-glnext.so [armeabi-v8]沒法加載so庫致使崩潰
  • 4.8 Only the original thread that created a view hierarchy can touch its views
  • 4.9 NoSuchMethodException android.support.v4.app.Fragment$InstantiationException

好消息

  • 博客筆記大彙總【16年3月到至今】,包括Java基礎及深刻知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,固然也在工做之餘收集了大量的面試題,長期更新維護而且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請註明出處,謝謝!
  • 連接地址:https://github.com/yangchong2...
  • 若是以爲好,能夠star一下,謝謝!固然也歡迎提出建議,萬事起於忽微,量變引發質變!

1.1 java.lang.UnsatisfiedLinkError

  • A.詳細崩潰日誌信息php

    # main(1)
    java.lang.UnsatisfiedLinkError
    dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.paidian.hwmc-1/base.apk", dex file "/data/app/com.paidian.hwmc-1/base.apk"],nativeLibraryDirectories=[/data/app/com.paidian.hwmc-1/lib/arm64, /data/app/com.paidian.hwmc-1/base.apk!/lib/arm64-v8a, /vendor/lib64, /system/lib64]]] couldn't find "libijkffmpeg.so"
  • B.查看崩潰類信息java

    • 這個異常類的大意是:若是Java虛擬機找不到聲明爲本機的方法的適當本機語言定義,則引起。
    public class UnsatisfiedLinkError extends LinkageError {
        private static final long serialVersionUID = -4019343241616879428L;
    
        public UnsatisfiedLinkError() {
            super();
        }
    
        public UnsatisfiedLinkError(String s) {
            super(s);
        }
    }
  • C.項目中異常分析react

    • 根據實際項目可知,當準備播放視頻時,找不到libijkffmpeg.so這個庫,致使直接崩潰。
  • D.引起崩潰日誌的流程分析
  • F.解決辦法linux

    • 報這個錯誤一般是so庫加載失敗,或者找不到準備執行的JNI方法:android

      • 1.建議檢查so在安裝的過程當中是否丟失,沒有放入指定的目錄下;
      • 2.調用loadLibrary時檢查是否調用了正確的so文件名,並對其進行捕獲,進行相應的處理,防止程序發生崩潰;
      • 3.檢查下so的架構是否跟設備架構一至(如在64-bit架構下調用32-bit的so)。
    • 代碼展現
    ndk {
        //根據須要 自行選擇添加的對應cpu類型的.so庫。
        //abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'mips'
        abiFilters 'armeabi-v7a'
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //這兩個是必需要加的,其它的可供選擇
        compile 'tv.danmaku.ijk.media:ijkplayer-java:0.8.4'
        compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.4'
        //其餘庫文件
        //compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8'
        //compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
        //compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8'
        //compile 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'
    }
  • G.知識延申git

    • Android 應用開發者應該對 UnsatisfiedLinkError 這種類型的錯誤比較熟悉了,這個問題一直困擾着廣大的開發者,那麼有沒有想過有可能你什麼都沒作錯,也會出現這個問題呢?
    • 咱們在 Android 應用開發測試過程當中曾經碰到過這樣的案例,apk 在某機型上安裝完成以後運行即崩潰,報錯 UnsatisfiedLinkError。
    • java.lang.UnsatisfiedLinkError: Couldn’t load mobsec from loader dalvik.system.PathClassLoader.....findLibrary returned null
    • 首先懷疑是在 apk 中相應的 libsabi 目錄下沒有放置 libmobsec.so,然而檢查發現這個 so 在全部的 libsabi 下都有放置過,繼續排查;
    • 而後的想法是放置的 so 不是對應 abi 的,好比因爲粗心在 armeabi 目錄下放置了 x86 指令集的 so,致使在 armeabi 指令集手機上加載出錯,這個也被排除掉;
    • 就在沒有頭緒的時候,想到 System.loadLibrary 函數加載 so 時,系統是從指定的路徑下加載的,那麼這個路徑下 so 是否存在呢?
    • 咱們知道應用的私有 Native library 目錄 /data/data/packagename/lib 是一個符號連接,連接到 /data/app-lib/<package name> 目錄,System.loadLibrary 是到這個目錄去嘗試加載 so 的。
    • adb shell 到這個路徑下,使用命令 ls 查看,果真這個 libmobsec.so 是不存在的。那麼是什麼緣由致使的呢?
    • 分析 Android 系統源碼的實現,發現 /data/app-lib/<package name> 這個目錄下的 so ,是在系統安裝 apk 時從 apk 的 lib 目錄下去抽取的。

1.2 java.lang.IllegalStateException非法狀態異常

  • A.詳細崩潰日誌信息github

    • onSaveInstanceState方法是在該Activity即將被銷燬前調用,來保存Activity數據的,若是在保存玩狀態後

再給它添加Fragment就會出錯。面試

IllegalStateException: Can not perform this action after onSaveInstanceState:
  • B.查看崩潰類信息sql

    • 在非法或不適當的時間調用方法的信號。換句話說,Java環境或Java應用程序沒有處於請求操做的適當狀態。
    public class IllegalStateException extends RuntimeException {
        public IllegalStateException() {
            super();
        }
    
        public IllegalStateException(String s) {
            super(s);
        }
    
        public IllegalStateException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public IllegalStateException(Throwable cause) {
            super(cause);
        }
    
        static final long serialVersionUID = -1848914673093119416L;
    }
  • C.項目中異常分析shell

    • 分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 解決辦法就是把commit()方法替換成 commitAllowingStateLoss()
  • G.其餘延申

    • 錯誤類型大體爲如下幾種:
    java.lang.IllegalStateException:Can't change tag of fragment d{e183845 #0 d{e183845}}: was d{e183845} now d{e183845 #0 d{e183845}}
    java.lang.IllegalStateException:Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 37 path $.data
    • 第一種:我在顯示fragment的代碼中使用了:fragment.show(getSupportFragmentManager, fragment.toString());而這裏是由於兩次toString()結果不一樣,致使不一樣的tag指向的是同一個fragment。獲取fragment的tag的正確方法應該是使用其提供的fragment.getTag()方法。
    • 第二種:該異常是因爲服務器錯誤返回的JSON字符串和服務器正常下時返回的JSON字符串結構不一樣,致使利用Gson解析的時候報了一個異常:本該去解析集合卻強制去解析對象所致.解決辦法:在使用Gson解析JSON時try cash一下,不報錯按照正常邏輯繼續解析,報異常則處理爲請求失敗邏輯便可.

1.3 android.content.res.Resources$NotFoundException

  • A.詳細崩潰日誌信息

    • Android資源不是可繪製的(顏色或路徑)
    Resource is not a Drawable (color or path): TypedValue{t=0x2/d=0x7f040151 a=2}
    android.view.LayoutInflater.createView(LayoutInflater.java:620)
  • B.查看崩潰類信息

    • 當找不到請求的資源時,資源API將引起此異常。
    public static class NotFoundException extends RuntimeException {
        public NotFoundException() {
        }
    
        public NotFoundException(String name) {
            super(name);
        }
    
        public NotFoundException(String name, Exception cause) {
            super(name, cause);
        }
    }
  • C.項目中異常分析

    • 因爲將圖片資源拷貝到了drawable-land-xhdpi目錄下,原本應該拷貝到drawable-xhdpi目錄下。
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 1.引用的資源ID 是否能匹配到R.java文件中定義的資源;
    • 2.是否由於緩存等緣由致使編譯APK時未把資源文件打包進去,能夠把APK反編譯檢查下;
    • 3.是否使用了一個錯誤的類型來引用了某個資源或者配置資源時存在錯誤;
    • 4.是否將Int等整型變量做爲了參數傳給了View.setText調用,這種狀況下該整型變量將被認爲是一個資源ID號去資源列表中查找對應的資源,致使找不到對應資源錯誤;解決方法是作類型轉換View.setText(String.valueOf(Int id))。

1.4 java.lang.IllegalArgumentException參數不匹配異常

  • A.詳細崩潰日誌信息
  • B.查看崩潰類信息

    • 參數不匹配異常,一般因爲傳遞了不正確的參數致使。
    public class IllegalArgumentException extends RuntimeException {
        public IllegalArgumentException() {
            super();
        }
    
        public IllegalArgumentException(String s) {
            super(s);
        }
    
        public IllegalArgumentException(String message, Throwable cause) {
            super(message, cause);
        }
    
    
        public IllegalArgumentException(Throwable cause) {
            super(cause);
        }
    
        private static final long serialVersionUID = -5365630128856068164L;
    }
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法
  • G.常見的出現場景

    • Activity、Service狀態異常;
    • 非法URL;
    • UI線程操做。
    • Fragment中嵌套了子Fragment,Fragment被銷燬,而內部Fragment未被銷燬,因此致使再次加載時重複,在onDestroyView() 中將內部Fragment銷燬便可
    • 在請求網絡的回調中使用了glide.into(view),view已經被銷燬會致使該錯誤

1.5 IllegalStateException:Can't compress a recycled bitmap

  • A.詳細崩潰日誌信息

    • 沒法壓縮回收位圖
    Can't compress a recycled bitmap
    com.paidian.hwmc.utils.i.a(FileUtils.java:75)
  • B.查看崩潰類信息

    • 若是位圖已被回收,則但願拋出異常的方法將調用此值。知道了崩潰的具體位置,就該分析具體的緣由呢!
    public boolean compress(CompressFormat format, int quality, OutputStream stream) {
        checkRecycled("Can't compress a recycled bitmap");
        //省略代碼
        return result;
    }
    
    //若是位圖已被回收,則但願拋出異常的方法將調用此值。
    private void checkRecycled(String errorMessage) {
        if (mRecycled) {
            throw new IllegalStateException(errorMessage);
        }
    }
  • C.項目中異常分析

    • 使用了已經被釋放過內存的對象。對於Bitmap:Bitmap bitmap=一個bitmap對象。使用過程當中調用bitmap.recycle(),以後再使用bitmap就會報錯。
  • D.引起崩潰日誌的流程分析

    • bitmap.recycle()解釋以下所示,釋放與此位圖關聯的本機對象,並清除對像素數據的引用。這將不會同步釋放像素數據;它只容許在沒有其餘引用的狀況下對其進行垃圾收集。位圖被標記爲「死」,這意味着若是調用getPixels()或setPixels(),它將拋出異常,而不會繪製任何內容。此操做不能反轉,所以只有在肯定沒有進一步使用位圖的狀況下才應調用該操做。這是一個高級調用,一般不須要調用,由於當沒有對此位圖的引用時,普通GC進程將釋放此內存。
    Free the native object associated with this bitmap, and clear the reference to the pixel data
  • F.解決辦法

    • 第一種:在使用bitmap前增長判斷,if (mBitmap.isRecycled()) return null;

1.6 java.lang.NullPointerException空指針異常

  • A.詳細崩潰日誌信息

    Please call the AutoSizeConfig#init() first
    com.paidian.hwmc.base.BaseApplication.initAutoSizeConfig(BaseApplication.java:386)
  • B.查看崩潰類信息

    • 空指針異常,也是十分常見的一個異常
    public class NullPointerException extends RuntimeException {
        private static final long serialVersionUID = 5162710183389028792L;
        public NullPointerException() {
            super();
        }
        public NullPointerException(String s) {
            super(s);
        }
    }
  • C.項目中異常分析

    • 空指針發生場景較多,是指某一個對象報null,這個使用去使用它的話就i會報該異常。
  • D.引起崩潰日誌的流程分析

    • 致使出現空指針的緣由: 必須知足兩個條件纔會發生空指針:引用變量指向了空,而且調用了這個引用的方法
    • 空指針問題解決思路:

      • 查看Log信息看第一行致使空指針發生的代碼,直接雙擊打開報空指針的類
      • 查看該行代碼中有幾處調用了方法,則有幾個對象多是空的,找出哪一個對象是空的
      • 查看這些對方在哪裏賦值了
      • 若是沒賦值,則給她賦值,問題解決
      • 若是有地方賦值了,則看這個方法有沒有被調用(Ctrl + Shift + G)
      • 若是沒有調用(可能沒調用或可能調用時機太晚),在使用她前先調用賦值,問題解決
      • 若是有調用,則看是否是有其它地方又給她賦值爲null了,若是沒有設置爲null,則要看賦值的變量和咱們使用時的變量是不是同一個變量。
  • F.解決辦法

    • 空指針最爲常見,也最容易規避,使用的時候必定要進行null check,採起不信任原則:

      • 1.方法形參要判空後才使用;
      • 2.全局變量容易被系統回收或者更改,使用全局變量前建議判空;
      • 3.第三方接口的調用,對返回值進行判空。
      • 4.請注意線程安全

1.7 android.view.WindowManager$BadTokenException異常,Toast報錯Unable to add window

  • A.詳細崩潰日誌信息

    android.view.WindowManager$BadTokenException
        Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
  • B.查看崩潰類信息

    • 查詢報錯日誌是從哪裏來的
    • image
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析

    • 這個異常發生在Toast顯示的時候,緣由是由於token失效。一般狀況下,通常是不會出現這種異常。可是因爲在某些狀況下, Android進程某個UI線程的某個消息阻塞。致使 TN 的 show 方法 post 出來 0 (顯示) 消息位於該消息以後,遲遲沒有執行。這時候,NotificationManager 的超時檢測結束,刪除了 WMS 服務中的 token 記錄。刪除 token 發生在 Android 進程 show 方法以前。這就致使了上面的異常。
    • 測試代碼。模擬一下異常的發生場景,其實很容易,只須要這樣作就能夠出現上面這個問題
    Toast.makeText(this,"瀟湘劍雨-yc",Toast.LENGTH_SHORT).show();
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
  • F.解決辦法

    • 目前見過好幾種,思考一下那種比較好……
    • 第一種,既然是報is your activity running,那能夠不能夠在吐司以前先判斷一下activity是否running呢?
    • 第二種,拋出異常增長try-catch,代碼以下所示,最後仍然沒法解決問題

      • 按照源碼分析,異常是發生在下一個UI線程消息中,所以在上一個ui線程消息中加入try-catch是沒有意義的。並且用到吐司地方這麼多,這樣作也不方便啦!
    • 第三種,那就是自定義相似吐司Toast的view控件。我的建議除非要求很是高,否則不要這樣作。畢竟發生這種異常仍是比較少見的
  • G.哪些狀況會發生該問題?

    • UI 線程執行了一條很是耗時的操做,好比加載圖片等等,就相似上面用 sleep 模擬狀況
    • 進程退後臺或者息屏了,系統爲了減小電量或者某種緣由,分配給進程的cpu時間減小,致使進程內的指令並不能被及時執行,這樣同樣會致使進程看起來」卡頓」的現象
    • 當TN拋出消息的時候,前面有大量的 UI 線程消息等待執行,而每一個 UI 線程消息雖然並不卡頓,可是總和若是超過了 NotificationManager 的超時時間,仍是會出現問題

1.8 java.lang.ClassCastException類轉化異常

  • A.詳細崩潰日誌信息

    android.widget.FrameLayout cannot be cast to android.widget.RelativeLayout
    com.paidian.hwmc.goods.activity.GoodsDetailsActivity.initView(GoodsDetailsActivity.java:712)
  • B.查看崩潰類信息

    • 拋出以指示代碼試圖將對象強制轉換爲它不是實例的子類。
    public class ClassCastException extends RuntimeException {
        private static final long serialVersionUID = -9223365651070458532L;
    
    
        public ClassCastException() {
            super();
        }
    
        public ClassCastException(String s) {
            super(s);
        }
    }
  • C.項目中異常分析

    • 該異常表示類型轉換異常,一般是由於一個類對象轉換爲其餘不兼容類對象拋出的異常,檢查你要轉換的類對象類型。
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 通常在強制類型轉換時出現,例如若是A向B轉換,而A不是B的父類時,將產生java.lang.ClassCastException異常。通常建議作這時要使用instanceof作一下類型判斷,再作轉換。
    • 該案例中,須要把FrameLayout更改爲RelativeLayout就能夠呢

1.9 Toast運行在子線程問題,handler問題

  • A.詳細崩潰日誌信息

    • 先來看看問題代碼,會出現什麼問題呢?
    new Thread(new Runnable() {
        @Override
        public void run() {
            ToastUtils.showRoundRectToast("瀟湘劍雨-楊充");
        }
    }).start();
    • 報錯日誌以下所示:
    • image
  • 而後找找報錯日誌從哪裏來的

    • ![image]()
  • 子線程中吐司的正確作法,代碼以下所示

    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            ToastUtils.showRoundRectToast("瀟湘劍雨-楊充");
            Looper.loop();
        }
    }).start();
  • 得出的結論

    • Toast也能夠在子線程執行,不過須要手動提供Looper環境的。
    • Toast在調用show方法顯示的時候,內部實現是經過Handler執行的,所以天然是不阻塞Binder線程,另外,若是addView的線程不是Loop線程,執行完就結束了,固然就沒機會執行後續的請求,這個是由Hanlder的構造函數保證的。能夠看看handler的構造函數,若是Looper==null就會報錯,而Toast對象在實例化的時候,也會爲本身實例化一個Hanlder,這就是爲何說「必定要在主線程」,其實準確的說應該是 「必定要在Looper非空的線程」。
    • Handler的構造函數以下所示:
    • image
    • image

2.1 java.lang.ClassNotFoundException類找不到異常

  • A.詳細崩潰日誌信息

    Didn't find class "om.scwang.smartrefresh.layout.SmartRefreshLayout" on path: DexPathList[[zip file "/data/app/com.paidian.hwmc-EsIbVq6e0mFwE0-rPanqdg==/base.apk", zip file "/data/app/com.paidian.hwmc-EsIbVq6e0mFwE0-rPanqdg==/split_lib_dependencies_apk.apk", zip file "/data/app/com.paidian.hwmc-EsIbVq6e0mFwE0-rPanqdg==/split_lib_slice_0_apk.apk", zip file "/data/app/com.paidian.hwmc-EsIbVq6e0mFwE0-rPanqdg==/split_lib_slice_1_apk.apk", zip file "/data/app/com.paidian.hwmc-EsIbVq6e0mFwE0-rPanqdg==/split_lib_s
    com.paidian.hwmc.goods.activity.GoodsDetailsActivity.onCreate(GoodsDetailsActivity.java:209)
  • B.查看崩潰類信息

    • 當應用程序嘗試使用字符串名稱加載類時引起:但沒法找到具備指定名稱的類的定義。從1.4版開始,已對此異常進行了修改,以符合通用的異常連接機制。在構建時提供並經過{@link#getException()}方法訪問的「在加載類時引起的可選異常」如今稱爲緣由,而且能夠經過{@link Throwable#getCace()}方法以及前面提到的「遺留方法」進行訪問。
    public class ClassNotFoundException extends ReflectiveOperationException {
        private static final long serialVersionUID = 9176873029745254542L;
        private Throwable ex;
        public ClassNotFoundException() {
            super((Throwable)null);  // Disallow initCause
        }
        public ClassNotFoundException(String s) {
            super(s, null);  //  Disallow initCause
        }
        public ClassNotFoundException(String s, Throwable ex) {
            super(s, null);  //  Disallow initCause
            this.ex = ex;
        }
        public Throwable getException() {
            return ex;
        }
        public Throwable getCause() {
            return ex;
        }
    }
  • C.項目中異常分析

    • 該異常表示在路徑下,找不到指定類,一般是由於構建路徑問題致使的。
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 類名是以字符串形式標識的,可信度比較低,在調用Class.forName(""),Class.findSystemClass(""),Class.loadClass("")等方法時,找不到類名時將會報錯。若是找不到的Class是系統Class,那麼多是系統版本兼容,廠家Rom兼容的問題,找到對應的設備嘗試重現,解決方法能夠考慮更換Api,或用本身實現的Class替代。
    • 若是找不到的Class是應用自由Class(含第三方SDK的Class),能夠經過反編譯工具查看對應apk中是否真的缺乏該Class,再進行定位,這種每每發生在:

      • 1.要找的Class被混淆了,存在但名字變了;
      • 2.要找的Class未被打入Dex,確實不存在,多是由於本身的疏忽,或編譯環境的衝突;
      • 3.要找的Class確實存在,但你的Classlorder找不到這個Class,每每由於這個Classloder是你自實現的(插件化應用中常見)。
  • G.其餘延申

2.2 java.util.concurrent.TimeoutException鏈接超時崩潰

  • A.詳細崩潰日誌信息

    java.util.concurrent.TimeoutException: android.view.ThreadedRenderer.finalize() timed out after 10 seconds
    at android.view.ThreadedRenderer.nDeleteProxy(Native Method)
    at android.view.ThreadedRenderer.finalize(ThreadedRenderer.java:423)
  • B.查看崩潰類信息

    • 當阻塞操做超時引起的異常。指定超時的阻塞操做須要一種方法來指示已發生超時。對於許多此類操做,能夠返回指示超時的值;若是不可能或不須要,則應聲明並拋出{@code TimeoutException}。
    public class TimeoutException extends Exception {
        private static final long serialVersionUID = 1900926677490660714L;
        public TimeoutException() {}
        public TimeoutException(String message) {
            super(message);
        }
    }
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 通常是系統在gc時,調用對象的finalize超時致使,解決辦法:
    • 1.檢查分析finalize的實現爲何耗時較高,修復它;
    • 2.檢查日誌查看GC是否過於頻繁,致使超時,減小內容開銷,防止內存泄露。
  • G.其餘延申

2.3 java.lang.NumberFormatException格式轉化錯誤

  • A.詳細崩潰日誌信息

    Exception in thread "main" java.lang.NumberFormatException: For input string: "100 "
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
        at java.lang.Integer.parseInt(Integer.java:458)
        at java.lang.Integer.parseInt(Integer.java:499)
  • B.查看崩潰類信息

    • 引起,以指示應用程序試圖將字符串轉換爲數字類型之一,但該字符串沒有適當的格式。
    public class NumberFormatException extends IllegalArgumentException {
        static final long serialVersionUID = -2848938806368998894L;
    
        public NumberFormatException () {
            super();
        }
    
        public NumberFormatException (String s) {
            super (s);
        }
    
        static NumberFormatException forInputString(String s) {
            return new NumberFormatException("For input string: \"" + s + "\"");
        }
    }
  • C.項目中異常分析

    • 錯誤關鍵字 java.lang.NumberFormatException 這句話明確告訴了咱們是數字格式異常,接着後面有 For input string: "100 " 提示,這就告訴咱們,當前想把 "100 " 轉換成數字類型時出錯了,這樣就很確切了。
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 解決辦法很簡單,改爲 Integer.parseInt(str.trim()),注意將字符串轉化成整數數據類型時,注意須要trim一下。
  • G.其餘延申

2.4 java.lang.IllegalStateException: Fragment not attached to Activity

  • A.詳細崩潰日誌信息

    java.lang.IllegalStateException: Fragment not attached to Activity
  • B.查看崩潰類信息
  • C.項目中異常分析

    • 出現該異常,是由於Fragment的尚未Attach到Activity時,調用瞭如getResource()等,須要上下文Content的函數。
    • 出現該異常,是由於Fragment尚未Attach到Activity時,調用瞭如getResource()等,須要上下文Context的函數。解決方法,就是等將調用的代碼寫在OnStart()中。
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 將調用的代碼運行在Fragment Attached的生命週期內。
    • 第一種:在調用須要Context的函數以前,增長一個判斷isAdded()
    if(isAdded()){//isAdded方法是Android系統提供的,只有在Fragment被添加到所屬的Activity後才返回true
        activity.getResourses().getString(...);
    }
    • 第二種:以下所示
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        activity = (MainActivity) context;
    }
    
    @Override
    public void onDetach() {
        super.onDetach();
        if (activity != null) {
            activity = null;
        }
    }
  • G.其餘延申

    • 發生場景:該錯誤常常發生在fragment的線程中執行了一個耗時操做,線程在執行完畢後會調用getResources來更新ui。若是在線程操做沒有完成,就調用getActivity().recreate()從新加載activity或屏幕旋轉,這時就會出現Fragment not attached to Activity的錯誤

2.5 ArrayIndexOutOfBoundsException 角標越界異常

  • A.詳細崩潰日誌信息

    • 該異常表示數組越界
    java.lang.ArrayIndexOutOfBoundsException: 0
        at com.example.mytest.CityAdapter.setDataNotify(CityAdapter.java:183)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  • B.查看崩潰類信息

    • 引起,以指示已使用非法索引訪問數組。索引不是負的,就是大於或等於數組的大小。
    public class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {
        private static final long serialVersionUID = -5116101128118950844L;
        public ArrayIndexOutOfBoundsException() {
            super();
        }
        public ArrayIndexOutOfBoundsException(int index) {
            super("Array index out of range: " + index);
        }
        public ArrayIndexOutOfBoundsException(String s) {
            super(s);
        }
        public ArrayIndexOutOfBoundsException(int sourceLength, int index) {
            super("length=" + sourceLength + "; index=" + index);
        }
        public ArrayIndexOutOfBoundsException(int sourceLength, int offset,
                int count) {
            super("length=" + sourceLength + "; regionStart=" + offset
                    + "; regionLength=" + count);
        }
    }
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 這種狀況通常要在數組循環前作好length判斷,index超出length上限和下限時都會報錯。舉例以下:一個數組int test[N],一共有N個元素分別是test[0]~test[N-1],若是調用test[N],將會報錯。建議讀取時,不要超過數組的長度(array.length)。
    • Android中一種常見情形就是上拉刷新中header也會做爲listview的第0個位置,若是判斷失誤很容易形成越界。
  • G.其餘延申

2.6 IllegalAccessException 方法中構造方法權限異常

  • A.詳細崩潰日誌信息

    Unable to instantiate application com.pedaily.yc.meblurry.App: java.lang.IllegalAccessException
  • B.查看崩潰類信息

    • 當應用程序試圖反射地建立實例(數組除外)、設置或獲取字段或調用方法時,將引起IllegalAccessException,但當前執行的方法沒法訪問指定的類、字段、方法或構造函數的定義。
    public class IllegalAccessException extends ReflectiveOperationException {
        private static final long serialVersionUID = 6616958222490762034L;
        public IllegalAccessException() {
            super();
        }
        public IllegalAccessException(String s) {
            super(s);
        }
    }
  • C.項目中異常分析

    • 錯誤提示是,構造方法的權限不對
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 檢查了整個Application,才發現,原來有一個無參數的構造方法,被設計成private。修改其爲public便可。
  • G.其餘延申

    • android BroadcastReceiver遇到java.lang.IllegalAccessException解決方法,錯誤緣由主要是app中其餘地方調用了默認的構造函數,必須增長默認構造函數且訪問權限爲public

2.7 android.view.WindowManager$BadTokenException,dialog彈窗異常

  • A.詳細崩潰日誌信息

    Unable to add window -- token android.os.BinderProxy@9a57804 is not valid; is your activity running?
    android.view.ViewRootImpl.setView(ViewRootImpl.java:907)
  • B.查看崩潰類信息

    • 在WindowManager中能夠找到這個異常類,主要發生在嘗試添加視圖時引起的
    public static class BadTokenException extends RuntimeException {
        public BadTokenException() {
        }
    
        public BadTokenException(String name) {
            super(name);
        }
    }
  • C.項目中異常分析

    • 該異常表示不能添加窗口,一般是所要依附的view已經不存在致使的。
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 以前項目中有一個自定義彈窗,偶爾會報這個錯。解決辦法以下代碼所示
    • 主要邏輯是在彈窗show或者dismiss的時候,都增長了邏輯判斷,判斷宿主activity存在。
    /**
     * 展現加載窗
     * @param context               上下文
     * @param isCancel              是否能夠取消
     */
    public static void show(Context context,  boolean isCancel) {
        if(context == null){
            return;
        }
        if (context instanceof Activity) {
            if (((Activity) context).isFinishing()) {
                return;
            }
        }
        if (loadDialog != null && loadDialog.isShowing()) {
            return;
        }
        loadDialog = new LoadLayoutDialog(context, isCancel);
        loadDialog.show();
    }
    
    /**
     * 銷燬加載窗
     * @param context               上下文
     */
    public static void dismiss(Context context) {
        if(context == null){
            return;
        }
        try {
            if (context instanceof Activity) {
                if (((Activity) context).isFinishing()) {
                    loadDialog = null;
                    return;
                }
            }
            if (loadDialog != null && loadDialog.isShowing()) {
                Context loadContext = loadDialog.getContext();
                if (loadContext instanceof Activity) {
                    if (((Activity) loadContext).isFinishing()) {
                        loadDialog = null;
                        return;
                    }
                }
                loadDialog.dismiss();
                loadDialog = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            loadDialog = null;
        }
    }
  • G.其餘延申

    • Dialog&AlertDialog,Toast,WindowManager不能正確使用時,常常會報出該異常,緣由比較多,幾個常見的場景以下:

      • 1.上一個頁面沒有destroy的時候,以前的Activity已經接收到了廣播。若是此時以前的Activity進行UI層面的操做處理,就會形成crash。UI層面的刷新,必定要注意時機,建議使用set_result來代替廣播的形式進行刷新操做,避免使用廣播的方式,代碼不直觀且容易出錯。
      • 2.Dialog在Actitivty退出後彈出。在Dialog調用show方法進行顯示時,必需要有一個Activity做爲窗口的載體,若是Activity被銷燬,那麼致使Dialog的窗口載體找不到。建議在Dialog調用show方法以前先判斷Activity是否已經被銷燬。
      • 3.Service&Application彈出對話框或WindowManager添加view時,沒有設置window type爲TYPE_SYSTEM_ALERT。須要在調用dialog.show()方法前添加dialog.getWindow().SetType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)。
      • 4.6.0的系統上, (非定製 rom 行爲)若沒有給予懸浮窗權限, 會彈出該問題, 能夠經過Settings.canDrawOverlays來判斷是否有該權限.
      • 5.某些不穩定的MIUI系統bug引發的權限問題,系統把Toast也當成了系統級彈窗,android6.0的系統Dialog彈窗須要用戶手動受權,若果app沒有加入SYSTEM_ALERT_WINDOW權限就會報這個錯。須要加入給app加系統Dialog彈窗權限,並動態申請權限,不知足第一條會出現沒權限閃退,不知足第二條會出現沒有Toast的狀況。
  • H.其餘建議

    • 1.不要在非UI線程中使用對話框建立,顯示和取消對話框;
    • 2.儘可能少用單獨線程,出發是真正的耗時操做採用線程,線程也不要直接用Java式的匿名線程,除非是那種單純的操做,操做完成不須要作其餘事情的。
    • 3.若是是在fragment中發起異步網絡的回調中進行dialog的操做,那麼在操做以前,須要判斷 isAdd( ),避免fragment被回收了可是還要求dialog去dismiss
    • 4.在Activity onDestroy中對Dialog提早進行關閉

2.8 java.lang.NoClassDefFoundError 找不到類異常

  • A.詳細崩潰日誌信息
  • B.查看崩潰類信息

    • 若是Java虛擬機或ClassLoader實例試圖加載類的定義(做爲普通方法調用的一部分或使用新的表達式建立新實例的一部分),則拋出該類的定義。編譯當前執行的類時存在搜索類定義,但沒法再找到該定義。
    public class NoClassDefFoundError extends LinkageError {
        private static final long serialVersionUID = 9095859863287012458L;
        public NoClassDefFoundError() {
            super();
        }
        public NoClassDefFoundError(String s) {
            super(s);
        }
        private NoClassDefFoundError(String detailMessage, Throwable throwable) {
            super(detailMessage, throwable);
        }
    }
  • C.項目中異常分析

    • 問題的主要緣由:方法數超65536限制。因爲實際開發當中的需求不斷變動,開源框架愈來愈多,大多都用第三方SDK,致使方法數很容易超出65536限制。出現錯誤Java.lang.NoClassDefFoundError
  • D.引起崩潰日誌的流程分析

    • 這個錯誤是Android應用的方法總數限制形成的。android平臺的Java虛擬機Dalvik在執行DEX格式的Java應用程序時,使用原生類型short來索引DEX文件中的方法。這意味着單個DEX文件可被引用的方法總數被限制爲65536。一般APK包含一個classes.dex文件,所以Android應用的方法總數不能超過這個數量,這包括Android框架、類庫和你本身開發的代碼。而Android 5.0和更高版本使用名爲ART的運行時,它原生支持從APK文件加載多個DEX文件。在應用安裝時,它會執行預編譯,掃描classes(..N).dex文件而後將其編譯成單個.oat文件用於執行. 通熟的講,就是分包。
  • F.解決辦法

    • 64k解決辦法
  • G.其餘延申

    • 該異常表示找不到類定義,當JVM或者ClassLoader實例嘗試裝載該類的定義(這一般是一個方法調用或者new表達式建立一個實例過程的一部分)而這個類定義並無找時所拋出的錯誤。
    • [解決方案]:NoClassDefFoundError異常通常出如今編譯環境和運行環境不一致的狀況下,就是說有可能在編譯事後更改了Classpath或者jar包因此致使在運行的過程當中JVM或者ClassLoader沒法找到這個類的定義。

      • 1.分dex包編程,若是依賴的dex包刪除了指定的類,執行初始化方法時將會報錯;
      • 2.使用第三方SDK或插件化編程時,動態加載或實例化類失敗將會報錯;
      • 3.系統資源緊張時,當大量class須要加載到內存的時候,處於競爭關係,部分calss競爭失敗,致使加載不成功;
      • 4.裝載並初始化一個類時失敗(好比靜態塊拋 java.lang.ExceptionInInitializerError 異常),而後再次引用此類也會提示NoClassDefFoundErr 錯誤;
      • 5.手機系統版本或硬件設備不匹配(如ble設備只支持18以上SDK),程序引用的class在低版本中不存在,致使NoClassDefFoundErr 錯誤。
      • 6.so文件找不到,設備平臺armeabi-v7a,可是個人so庫是放在armeabi中的,解決方法新建一個armeabi-v7a包,而且把armeabi的文件拷貝過來.

2.9 Android出現:Your project path contains non-ASCII characters.

  • A.詳細崩潰日誌信息
  • B.查看崩潰類信息
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 很好解決啦,就是你的工程項目路徑或者項目名稱包含了中文,修改相關的名稱就好
  • G.其餘延申

3.1 OnErrorNotImplementedException【 Can't create handler inside thread that has not called Looper.prepare()】

  • A.詳細崩潰日誌信息

    Can't create handler inside thread that has not called Looper.prepare()
  • B.查看崩潰類信息
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析

    • 這是由於Handler對象與其調用者在同一線程中,若是在Handler中設置了延時操做,則調用線程也會堵塞。每一個Handler對象都會綁定一個Looper對象,每一個Looper對象對應一個消息隊列(MessageQueue)。若是在建立Handler時不指定與其綁定的Looper對象,系統默認會將當前線程的Looper綁定到該Handler上。
    • 在主線程中,能夠直接使用new Handler()建立Handler對象,其將自動與主線程的Looper對象綁定;在非主線程中直接這樣建立Handler則會報錯,由於Android系統默認狀況下非主線程中沒有開啓Looper,而Handler對象必須綁定Looper對象。
    • 若是在主線程中建立handler時,系統會自動建立Looper,可是在子線程中建立handler時,是不會自動建立Looper的,此時若是不手動建立Looper,系統就會崩潰
  • F.解決辦法

    • 不要在子線程中作UI操做,好比更改界面,吐司等等……
    • 方法1:需先在該線程中手動開啓Looper(Looper.prepare()-->Looper.loop()),而後將其綁定到Handler對象上;
    final Runnable runnable = new Runnable() {
      @Override
      public void run() {
        //執行耗時操做
        try {
    
          Log.e("bm", "runnable線程: " + Thread.currentThread().getId()+ " name:" + Thread.currentThread().getName());
    
          Thread.sleep(2000);
          Log.e("bm", "執行完耗時操做了~");
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
      }
    };
    new Thread() {
      public void run() {
        Looper.prepare();
        new Handler().post(runnable);//在子線程中直接去new 一個handler
        Looper.loop();    //這種狀況下,Runnable對象是運行在子線程中的,能夠進行聯網操做,可是不能更新UI
      }
    }.start();
    • 方法2:經過Looper.getMainLooper(),得到主線程的Looper,將其綁定到此Handler對象上。
    final Runnable runnable = new Runnable() {
      @Override
      public void run() {
        //執行耗時操做
        try {
          Log.e("bm", "runnable線程: " + Thread.currentThread().getId()+ " name:" + Thread.currentThread().getName());
          Thread.sleep(2000);
          Log.e("bm", "執行完耗時操做了~");
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
      }
    };
    new Thread() {
      public void run() {
          //在子線程中直接去new 一個handler
        new Handler(Looper.getMainLooper()).post(runnable);
        //這種狀況下,Runnable對象是運行在主線程中的,不能夠進行聯網操做,可是能夠更新UI
      }
    }.start();
  • G.其餘延申

3.2 platform-toolsadb.exe,start-server' failed -- run manually if necessary

  • A.詳細崩潰日誌信息
  • B.查看崩潰類信息
  • C.項目中異常分析

    • adb啓動失敗,端口被佔用
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    百度google你們多說的是任務管理器 kill掉adb 或者重啓adb server,但我任務管理器就沒有adb ,猜想是某個程序佔用了adb端口。因而按此思路查找。
    5037爲adb默認端口 查看該端口狀況以下:
    netstat -aon|findstr "5037"
    TCP 127.0.0.1:5037 0.0.0.0:0 LISTENING 6540
    發現6540佔用了 5037端口,繼續查看6540的task,發現是wandoujia .以下所示
    tasklist|findstr "6540"
    wandoujia_daemon.exe 6540 Console 1 4,276 K
    
    接下來問題就好解決了,在任務管理器kill掉wandoujia_daemon.exe ,運行android程序,ok .
    
    1.關閉xx莢進程
    2.adb kill-server
    3.adb start-server
  • G.其餘延申

3.3 java.lang.IllegalStateException: ExpectedBEGIN_OBJECT but was STRING at line 1 column 1 path $

  • A.詳細崩潰日誌信息

    • 非法參數,開始讀取時應該是{}括號,因此須要處理String字符串,它有可能不是標準的json數據
    java.lang.IllegalStateException: ExpectedBEGIN_OBJECT but was STRING at line 1 column 1 path $
  • B.查看崩潰類信息
  • C.項目中異常分析

    • Gson解析數據出現問題,緣由服務器返回數據不嚴謹
  • D.引起崩潰日誌的流程分析

    • 可能的錯誤:

      • bean類字段類型和字段名稱不一致。
      • 服務器訪問獲得的字符串不是純json前面有空格和回車等字符(難發現)。
      • 若是訪問的json字符串不是utf-8編碼時,用Gson解析會出這種問題,在日誌中打印會發現json的{}前面有亂碼字符,也須要注意一下。這是由於不一樣的編碼的緣由致使的,所以必須訪問utf-8的json字符串,纔會減小這種問題。
    • 問題多是:字符串並非純json字符串,開頭可能會帶有空字符或者回車符,這屬於服務器問題,但咱們也能夠解決。
    • 最重要緣由的咱們網絡請求後結果是字符串,而不是json,所以須要處理。
  • F.解決辦法

    /**
*/
public static boolean isJson(String value) {
    try {
        new JSONObject(value);
    } catch (JSONException e) {
        return false;
    }
    return true;
}

/**
* 判斷是不是json結構
*/
public static boolean isGoodJson(String json) {
    try {
        new JsonParser().parse(json);
        return true;
    } catch (JsonParseException e) {
        System.out.println("bad json: " + json);
        return false;
    }
}
```
  • G.其餘延申,補充說明

    • 解決辦法:後臺輸出穩定的Gson格式。此方法工程量太大
    • 真正的問題是個人數據結構有問題
    • 例以下面Json字符串:
    • {"code":1,"info":"success","results":{"id":"1","name":"hehe"}}
    • results對應的應該是一個實體類,若是這個時候想把他解析爲String或者List就會出現異常
    • 若是參考使用GsonForm處理後的數據模型,幾乎不會出現問題;加入result後面的內容可能在請求時會由於某些緣由會存在格式上的變化,這個時候就有出現該異常的風險。Gson中,關鍵字後面出現""引發來的內容將會被只認爲是STRING,「{}」只被認爲是類,「[]」只被認爲是List,這個幾乎是強制性的。
    • 就是說若是你的實體預計是獲取String的變量,可是關鍵字後面對應的卻出現了「{」或「[」,那麼這個轉換將被認爲是錯誤的,拋出異常。

3.4 android.content.ActivityNotFoundException: No Activity found to handle Intent

  • A.詳細崩潰日誌信息

    android.content.ActivityNotFoundException: No Activity found to handle Intent
  • B.查看崩潰類信息

    • 當調用{@link Context#startActivity}或其變體之一失敗時,會引起此異常,由於沒法找到執行給定意圖的活動。
    public class ActivityNotFoundException extends RuntimeException
    {
        public ActivityNotFoundException()
        {
        }
    
        public ActivityNotFoundException(String name)
        {
            super(name);
        }
    };
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 第一種辦法:作一個try catch
    Intent intent = new Intent(Intent.ACTION_SENDTO,url);
    try {
        context.startActivity(intent);
    } catch(ActivityNotFoundException exception) {
        Toast.makeText(this, "no activity", Toast.LENGTH_SHORT).show();
    }
    • 第二種辦法:判斷是否有應用寶客戶端
    //避免安裝了應用寶的用戶點擊其餘外部連接走此方法致使崩潰
    //判斷是否用應用寶客戶端
    if(AppUtils.isPkgInstalled(AdDetailActivity.this,"com.tencent.android.qqdownloader")){
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        startActivity( intent);
    }

3.5 Package manager has died致使崩潰

  • A.詳細崩潰日誌信息

    出錯代碼位置
    public static String softVersionName(Context context) {
        PackageInfo info = null;
        try {
            info = context.getPackageManager().getPackageInfo( context.getPackageName(), 0);     //在這裏
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return info.versionName;
    }
  • B.查看崩潰類信息
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析

    • 緣由分析(Binder形成)
    • 若是一個進程中使用的Binder內容超過了1M,就會crash.
    • 若是Binder的使用超出了一個進程的限制就會拋出TransactionTooLargeException這個異常。
    • 若是是其餘緣由形成Binder crash的話就會拋出RuntimeException。
  • F.解決辦法

    public static String softVersionName(Context context) {
        PackageInfo info = null;
        try {//增長同步塊
            synchronized (context) {
                info =context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            }
            return info.versionName;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
  • G.其餘延申

    private void test() {
            //這個Demo就是同時建立兩個線程來進行Binder調用.
            for (int i = 0; i < 2; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        int count = 0;
                        List<PackageInfo> list = getPackageManager().getInstalledPackages(0);
                        for (PackageInfo info : list) {
                            if(count >=1000){
                                break;
                            }
                            try {
                                PackageInfo pi = getPackageManager().getPackageInfo(info.packageName, PackageManager.GET_ACTIVITIES);
                            } catch (PackageManager.NameNotFoundException e) {
    
                            }
                        }
                    }
                }.start();
            }
        }
    }
    • 錯誤打印日誌
    • image
    • 解決方式:其實只要避免多個線程同時來調用Binder就能夠了,畢竟一個線程用了會釋放,因此理論上是很難發生的。
    synchronized(MainActivity.class){ 
        PackageInfo pi = getPackageManager() .getPackageInfo(info.packageName, PackageManager.GET_ACTIVITIES); 
    }

3.6 IllegalArgumentException View添加窗口錯誤

  • A.詳細崩潰日誌信息

    View=com.android.internal.policy.impl.PhoneWindow$DecorView{22a4fb16 V.E..... R.....ID 0,0-1080,1020} not attached to window manager
    com.flyco.dialog.widget.base.BaseDialog.superDismiss(BaseDialog.java)
  • B.查看崩潰類信息
  • C.項目中異常分析

    • 該異常表示view沒有添加到窗口管理器,一般是咱們dismiss對話框的時候,activity已經不存在了,建議不要在非UI線程操做對話框。
  • D.引起崩潰日誌的流程分析

    • 常發生這類Exception的情形都是,有一個費時的線程操做,須要顯示一個Dialog,在任務開始的時候顯示一個對話框,而後當任務完成了在Dismiss對話框,若是在此期間若是Activity由於某種緣由被殺掉且又從新啓動了,那麼當dialog調用dismiss的時候WindowManager檢查發現Dialog所屬的Activity已經不存在,因此會報錯。要避免此類Exception,就要正確的使用對話框,也要正確的使用線程
  • F.解決辦法

    • 能夠參考崩潰bug日誌總結1中的1.7
  • G.其餘延申,建議

    • 不要在非UI線程中使用對話框建立,顯示和取消對話框;
    • 儘可能少用單獨線程,出發是真正的耗時操做採用線程,線程也不要直接用Java式的匿名線程,除非是那種單純的操做,操做完成不須要作其餘事情的。
    • 若是是在fragment中發起異步網絡的回調中進行dialog的操做,那麼在操做以前,須要判斷 isAdd( ),避免fragment被回收了可是還要求dialog去dismiss
    • 在Activity onDestroy中對Dialog提早進行關閉

3.7 IllegalStateException: Not allowed to start service Intent異常崩潰

  • A.詳細崩潰日誌信息

    Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { act=initApplication cmp=com.paidian.hwmc/.service.InitializeService }: app is in background uid UidRecord{a37d28d u0a386 TRNB bg:+5m30s482ms idle procs:3 seq(0,0,0)}
  • B.查看崩潰類信息
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法
  • G.其餘延申

3.8 java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState

  • A.詳細崩潰日誌信息

    • image
  • B.查看崩潰類信息
  • C.項目中異常分析

    • 經過下面的源碼分析,咱們能夠知道,出現以上崩潰日誌的緣由,是由於咱們在按下頁面返回鍵的時候,當前Activity以及在執行銷燬操做(也就是說咱們之前在其餘地方調用了finish方法)。
  • D.引起崩潰日誌的流程分析

    • 問題所在是Activity#onBackPressed()方法。查看源代碼:點擊onBackPressed方法中的super
    • 在FragmentActivity中
    @Override
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
        }
    }
    • 接着再次點擊super,在Activity中
    public void onBackPressed() {
        if (mActionBar != null && mActionBar.collapseActionView()) {
            return;
        }
    
        if (!mFragments.getFragmentManager().popBackStackImmediate()) {
            finishAfterTransition();
        }
    }
    public void finishAfterTransition() {
        if (!mActivityTransitionState.startExitBackTransition(this)) {
            finish();
        }
    }
    • 咱們看到onBackPressed()方法執行了兩個操做,第一個是獲取當前的FragmentManager,而且執行退棧操做,第二個是在退棧完成以後,執行finish方法。繼續查看源碼,關鍵是FragmentManager實現類的popBackStackImmediate方法
    @Override
    public boolean popBackStackImmediate() {
        checkStateLoss();
        executePendingTransactions();
        return popBackStackState(mHost.getHandler(), null, -1, 0);
    }
    • 咱們看到,在執行退棧動做以前,這裏還有一步檢查操做
    private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        if (mNoTransactionsBecause != null) {
            throw new IllegalStateException(
                    "Can not perform this action inside of " + mNoTransactionsBecause);
        }
    }
    • 從這裏,咱們終於找到了崩潰日誌上的異常文案:Can not perform this action after onSaveInstanceState
  • F.解決辦法

    • 方案1,在調用super.onBackPressed的時候,咱們須要判斷當前Activity是否正在執行銷燬操做。
    if (!isFinishing()) {
        super.onBackPressed();
    }
    • 方案2,經過上面的源碼分析,咱們也知道了,super.onBackPressed最後也是調用finish()方法,所以咱們能夠重寫onBackPressed,直接調用finish方法。
  • G.其餘延申

3.9 在Fragment中經過getActivity找不到上下文,報null致使空指針異常

  • A.詳細崩潰日誌信息
  • B.查看崩潰類信息
  • C.項目中異常分析

    • 使用ViewPager+Fragment進行視圖滑動,在某些部分邏輯也許咱們須要利用上下文Context(例如基本的Toast),可是因爲Fragment只是衣服在Activity容器的一個試圖,若是須要拿到當前的Activity的上下文Context就必須經過getActivity()獲取。
    • 遇過出現getActivity()出現null的時候致使程序報出空指針異常。其實緣由能夠歸結於由於咱們在

      • 切換fragment的時候,會頻繁被crash
      • 系統內存不足
      • 橫豎屏幕切換的時候
      • 以上狀況都會致使Activity被系統回收,可是因爲fragment的生命週期不會隨着Actiivty被回收而被回收,所以纔會致使getActivity()出現null的問題。
    • 不少人都曾被這個問題所困擾,若是app長時間在後臺運行,再次進入app的時候可能會出現crash,並且fragment會有重疊現象。若是系統內存不足、切換橫豎屏、app長時間在後臺運行,Activity均可能會被系統回收而後重建,但Fragment並不會隨着Activity的回收而被回收,建立的全部Fragment會被保存到Bundle裏面,從而致使Fragment丟失對應的Activity。
  • D.引起崩潰日誌的流程分析

    • 當遇到getActivity()爲null,或getContext()時,先冷靜想一想如下3點:

      • 1.是否是放在了第三方的回調中
      • 2.是否是在其餘進程中調用了(其實第一點就是在其餘進程中調用了)
      • 3.是否是調用時不在指定生命週期範圍內(onAttach與onDetach之間)
  • F.解決辦法

    在Fragment中直接調用
    private MActivity mActivity; 
    @Override 
    public void onAttach(Activity activity) { 
        super.onAttach(activity); 
        mActivity = (MActivity) activity; 
    }
    @Override
    public void onDetach() {
        super.onDetach();
        mActivity = null;
    }
  • G.其餘延申

    • 源碼解讀:在FragmentActivity中
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        ……
    }
    • 若是從最近使用的應用裏面點擊咱們的應用,系統會恢復以前被回收的Activity,這個時候FragmentActivity在oncreate裏面也會作Fragment的恢復
    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);
        super.onCreate(savedInstanceState);
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mFragments.restoreLoaderNonConfig(nc.loaders);
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
      ……
        }
        if (mPendingFragmentActivityResults == null) {
            mPendingFragmentActivityResults = new SparseArrayCompat<>();
            mNextCandidateRequestIndex = 0;
        }
        mFragments.dispatchCreate();
    }
    • 假設咱們的頁面叫MyActivity(繼承自FragmentActivity),其中用到的Fragment叫MyFragment。出現上面這種狀況時,app發生的變化以下:

      • 一、在前面提到的幾種狀況下系統回收了MyActivity
      • 二、經過onSaveInstanceState保存MyFragment的狀態
      • 三、用戶再次點擊進入app
      • 四、因爲MyActivity被回收,系統會重啓MyActivity,根據以前保存的MyFragment的狀態恢復fragment
      • 五、MyActivity的代碼邏輯中,會再次建立新的MyFragment
      • 六、頁面出現混亂,覆蓋了兩層的fragment。假如恢復的MyFragment使用到了getActivity()方法,會報空指針異常
    • 對於上面的問題,能夠考慮下面這兩種解決辦法:

      • 一、不保存fragment的狀態:在MyActivity中重寫onSaveInstanceState方法,將super.onSaveInstanceState(outState);註釋掉,讓其再也不保存Fragment的狀態,達到fragment隨MyActivity一塊兒銷燬的目的。
      • 二、重建時清除已經保存的fragment的狀態:在恢復Fragment以前把Bundle裏面的fragment狀態數據給清除。方法以下:
      if(savedInstanceState!= null){
          String FRAGMENTS_TAG =  "Android:support:fragments";
          savedInstanceState.remove(FRAGMENTS_TAG);
      }

4.1 IllegalArgumentException致使崩潰【url地址傳入非法參數,轉義字符】

  • A.詳細崩潰日誌信息

    • image
  • B.查看崩潰類信息
  • C.項目中異常分析

    • 只有不多一部分傳入非法參數致使崩潰,不能直接用常規方法。須要過濾
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • Java調用 URLDecoder.decode(str,"UTF-8");拋出以上的異常,其主要緣由是%在URL中是特殊字符,須要特殊轉義一下
    public static String replacer(String data) {
        try {
            //使用%25替換字符串中的%號
            data = data.replaceAll("%(?![0-9a-fA-F]{2})", "%25");      
            data = URLDecoder.decode(data, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

4.2 ClassNotFoundException: Didn't find class "" on path: /data/app/*錯誤

  • A.詳細崩潰日誌信息

    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{*****Activity}: java.lang.ClassNotFoundException: Didn't find class "*****Activity" on path: /data/app/*******.apk
  • B.查看崩潰類信息

    • 當應用程序嘗試使用字符串名稱加載類時引起:但沒法找到具備指定名稱的類的定義。從1.4版開始,已對此異常進行了修改,以符合通用的異常連接機制。在構建時提供並經過{@link#getException()}方法訪問的「在加載類時引起的可選異常」如今稱爲緣由,而且能夠經過{@link Throwable#getCace()}方法以及前面提到的「遺留方法」進行訪問。
    public class ClassNotFoundException extends ReflectiveOperationException {
        private static final long serialVersionUID = 9176873029745254542L;
        private Throwable ex;
        public ClassNotFoundException() {
            super((Throwable)null);  // Disallow initCause
        }
        public ClassNotFoundException(String s) {
            super(s, null);  //  Disallow initCause
        }
        public ClassNotFoundException(String s, Throwable ex) {
            super(s, null);  //  Disallow initCause
            this.ex = ex;
        }
        public Throwable getException() {
            return ex;
        }
        public Throwable getCause() {
            return ex;
        }
    }
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 1。Manifest文件中註冊的Activity的名稱,有沒有寫錯,包名有沒有搞錯,有些網友,可能只寫一個類名,前面用點號代替,可是這個類不在默認的包內,因此報這個錯,那麼只要寫上類的全名,便可。
    • 2.你的使用的class,是一個外部的JAR包,當在工程中編譯使用時,發佈成APK並無包含JAR文件,因此APK在執行的時候就找不到JAR文件,會報錯。有些android,須要一些第三方的包,直接將其引入,在之前是能夠的,可是在最新的adt中不行,必須在程序中新建一個libs文件夾,將第三方的jar文件copy到libs文件夾中,才行,不少人由於這樣才報錯,特別是之前的項目,默認並無這個libs文件夾,可是新版本的adt,默認就建了libs這個文件夾。
    • 3。有一點也很重要,在Java Build Path面板下的Order and Export中,必定要把你引入的jar文件,勾上,不然,跟沒引用同樣,切記。

4.3 NoClassDefFoundError異常【該異常表示找不到類定義】

  • A.詳細崩潰日誌信息

    • 常常碰到java.lang.NoClassDefFoundError這樣的錯誤,須要花費不少時間去找錯誤的緣由,具體是哪一個類不見了?類明明還在,爲何找不到?並且咱們很容易把java.lang.NoClassDefFoundError和java.lang.ClassNotfoundException這兩個錯誤搞混,事實上這兩個錯誤是徹底不一樣的。
  • B.查看崩潰類信息
  • C.項目中異常分析

    • 該異常表示找不到類定義,當JVM或者ClassLoader實例嘗試裝載該類的定義(這一般是一個方法調用或者new表達式建立一個實例過程的一部分)而這個類定義並無找時所拋出的錯誤。
    • NoClassDefFoundError錯誤的發生,是由於Java虛擬機在編譯時能找到合適的類,而在運行時不能找到合適的類致使的錯誤。
    • 例如在運行時咱們想調用某個類的方法或者訪問這個類的靜態成員的時候,發現這個類不可用,此時Java虛擬機就會拋出NoClassDefFoundError錯誤。
    • 總結:這個錯誤,是編譯器可用,運行期不可用
  • D.引起崩潰日誌的流程分析

    • 對應的Class在java的classpath中不可用
    • 你可能用jar命令運行你的程序,但類並無在jar文件的manifest文件中的classpath屬性中定義
    • 可能程序的啓動腳本覆蓋了原來的classpath環境變量
    • 由於NoClassDefFoundError是java.lang.LinkageError的一個子類,因此可能因爲程序依賴的原生的類庫不可用而致使
    • 檢查日誌文件中是否有java.lang.ExceptionInInitializerError這樣的錯誤,NoClassDefFoundError有多是因爲靜態初始化失敗致使的
  • F.解決辦法

    • 當發生因爲缺乏jar文件,或者jar文件沒有添加到classpath,或者jar的文件名發生變動會致使java.lang.NoClassDefFoundError的錯誤
    • 因爲NoClassDefFoundError是LinkageError的子類,而LinkageError的錯誤在依賴其餘的類時會發生,因此若是你的程序依賴原生的類庫和須要的dll不存在時,有可能出現java.lang.NoClassDefFoundError。這種錯誤也可能拋出java.lang.UnsatisfiedLinkError: no dll in java.library.path Exception Java這樣的異常。解決的辦法是把依賴的類庫和dll跟你的jar包放在一塊兒。
    • 若是你使用Ant構建腳原本生成jar文件和manifest文件,要確保Ant腳本獲取的是正確的classpath值寫入到manifest.mf文件
    • Jar文件的權限問題也可能致使NoClassDefFoundError,若是你的程序運行在像linux這樣多用戶的操做系統種,你須要把你應用相關的資源文件,如Jar文件,類庫文件,配置文件的權限單獨分配給程序所屬用戶組,若是你使用了多個用戶不一樣程序共享的jar包時,很容易出現權限問題。好比其餘用戶應用所屬權限的jar包你的程序沒有權限訪問,會致使java.lang.NoClassDefFoundError的錯誤。
    • 基於XML配置的程序也可能致使NoClassDefFoundError的錯誤。好比大多數Java的框架像Spring,Struts使用xml配置獲取對應的bean信息,若是你輸入了錯誤的名稱,程序可能會加載其餘錯誤的類而致使NoClassDefFoundError異常。咱們在使用Spring MVC框架或者Apache Struts框架,在部署War文件或者EAR文件時就常常會出現Exception in thread 「main」 java.lang.NoClassDefFoundError。
  • G.其餘延申,常見場景

    • 1.分dex包編程,若是依賴的dex包刪除了指定的類,執行初始化方法時將會報錯;
    • 2.使用第三方SDK或插件化編程時,動態加載或實例化類失敗將會報錯;
    • 3.系統資源緊張時,當大量class須要加載到內存的時候,處於競爭關係,部分calss競爭失敗,致使加載不成功;
    • 4.裝載並初始化一個類時失敗(好比靜態塊拋 java.lang.ExceptionInInitializerError 異常),而後再次引用此類也會提示NoClassDefFoundErr 錯誤;
    • 5.手機系統版本或硬件設備不匹配(如ble設備只支持18以上SDK),程序引用的class在低版本中不存在,致使NoClassDefFoundErr 錯誤。
    • 6.so文件找不到,設備平臺armeabi-v7a,可是個人so庫是放在armeabi中的,解決方法新建一個armeabi-v7a包,而且把armeabi的文件拷貝過來.
  • H.NoClassDefFoundError和ClassNotFoundException區別

    • NoClassDefFoundError發生在JVM在動態運行時,根據你提供的類名,在classpath中找到對應的類進行加載,但當它找不到這個類時,就發生了java.lang.NoClassDefFoundError的錯誤
    • ClassNotFoundException是在編譯的時候在classpath中找不到對應的類而發生的錯誤

4.4 公司以前項目使用客服udesk,sdk更新後初始化致使崩潰問題

  • 出錯緣由:初始化udesk客服sdk時,須要傳入token,name等信息。token是做爲客戶的惟一標識,而客服第三方sdk是以token命名建立sqlite數據庫,形成數據庫名稱過長……
  • 報錯信息:

    Could not open database, (OS error - 36:File name too long)
    {"code":"4444","message":"未知錯誤","exception":"Data too long for column 'sdk_token' at row 1"}
  • 日誌錯誤截圖

    • image
    • image
  • 解決辦法

    • 第一種解決辦法:客戶端裁剪token前10位[或者15位也行,就是不要長度過長]字符串做爲客戶惟一標識,通過測試能夠解決問題,可是須要發版,而且沒法改正以前版本的問題。
    • 第二種解決辦法:第三方平臺解決,由於以前使用沒有問題,後來出現了該問題。多是他們改代碼引發該問題!已經跟第三方發過信息,週一看……

4.5 java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception

  • A.詳細崩潰日誌信息

    Caused by: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
    Caused by: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
  • B.查看崩潰類信息

    public class ExecutionException extends Exception {
        protected ExecutionException() {
            throw new RuntimeException("Stub!");
        }
    
        protected ExecutionException(String message) {
            throw new RuntimeException("Stub!");
        }
    
        public ExecutionException(String message, Throwable cause) {
            throw new RuntimeException("Stub!");
        }
    
        public ExecutionException(Throwable cause) {
            throw new RuntimeException("Stub!");
        }
    }
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 在項目的中,gradle.properties文件中添加一行代碼就行,即gradle.properties(Project properties)
    android.enableAapt2=false
  • G.其餘延申

    • gradle.properties 裏面配置的東西,在gradle 文件裏面能夠直接引用
    • 在項目根目錄的gradle.properties文件配置:
    # 應用版本名稱
    VERSION_NAME=1.0.0
    # 應用版本號
    VERSION_CODE=100
    # 支持庫版本
    SUPPORT_LIBRARY=24.2.1
    # MIN_SDK_VERSION
    ANDROID_BUILD_MIN_SDK_VERSION=14
    # TARGET_SDK_VERSION
    ANDROID_BUILD_TARGET_SDK_VERSION=24
    # BUILD_SDK_VERSION
    ANDROID_BUILD_SDK_VERSION=24
    # BUILD_TOOLS_VERSION
    ANDROID_BUILD_TOOLS_VERSION=24.0.3
    • 這時候配置app和lib的build.gradle能夠這樣寫:
    android {
        compileSdkVersion project.ANDROID_BUILD_SDK_VERSION as int
        buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
     
        defaultConfig {
            applicationId project.APPLICATION_ID // lib項目不須要配置這一項
            versionCode project.VERSION_CODE as int
            versionName project.VERSION_NAME
            minSdkVersion project.ANDROID_BUILD_MIN_SDK_VERSION as int
            targetSdkVersion project.ANDROID_BUILD_TARGET_SDK_VERSION as int
        }
    }
     
    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        //這裏注意是雙引號
        compile "com.android.support:appcompat-v7:${SUPPORT_LIBRARY}"
        compile "com.android.support:design:${SUPPORT_LIBRARY}"
        compile "com.android.support:recyclerview-v7:${SUPPORT_LIBRARY}"
        compile "com.android.support:support-annotations:${SUPPORT_LIBRARY}"
        compile "com.android.support:cardview-v7:${SUPPORT_LIBRARY}"
        compile "com.android.support:support-v4:${SUPPORT_LIBRARY}"
    }

4.6 java.util.concurrent.ExecutionException: com.android.ide.common.process.ProcessException

  • A.詳細崩潰日誌信息

    • 也就是執行異常,進程異常。
    Caused by: java.util.concurrent.ExecutionException: com.android.ide.common.process.ProcessException: Error while executing process /Users/duanzheng/WorkSpace/AndroidSdk/build-tools/27.0.3/aapt with arguments {package -f --no-crunch -I /Users/duanzheng/WorkSpace/AndroidSdk/platforms/android-27/android.jar -M /Users/duanzheng/.jenkins/workspace/android_hwmc_project/UdeskSDKUI/build/intermediates/manifests/aapt/release/AndroidManifest.xml -S /Users/duanzheng/.jenkins/workspace/android_hwmc_project/UdeskSDKUI/build/intermediates/res/merged/release --non-constant-id -0 apk --no-version-vectors}
    
    Caused by: org.gradle.process.internal.ExecException: Process 'command '/Users/duanzheng/WorkSpace/AndroidSdk/build-tools/27.0.3/aapt'' finished with non-zero exit value 1
    
    
    //注意這裏看重點:aapt'' finished with non-zero exit value 1
  • B.查看崩潰類信息
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法

    • 網上搜索第一種方法:初步猜想可能由同步下來不須要的build文件形成,clean一下項目看是否能解決。
  • H.aapt是啥

4.7 00768556 /vendor/lib/libllvm-glnext.so [armeabi-v8]沒法加載so庫致使崩潰

  • A.詳細崩潰日誌信息

    • 魅族手機,一打開就崩潰。思考[armeabi-v8]是啥?
    1 #00 pc 00768556 /vendor/lib/libllvm-glnext.so [armeabi-v8]
    2 #01 pc 000011e0 <unknown>
    3 java:
    4 [Failed to get java stack]
  • B.查看崩潰類信息
  • C.項目中異常分析
  • D.引起崩潰日誌的流程分析
  • F.解決辦法
  • G.其餘延申

4.8 Only the original thread that created a view hierarchy can touch its views

  • A.詳細崩潰日誌信息

    UncaughtException detected: io.reactivex.exceptions.OnErrorNotImplementedException: Only the original thread that created a view hierarchy can touch its views.
    Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: 
    Only the original thread that created a view hierarchy can touch its views.
  • B.查看崩潰類信息

    • 異常的意思是說只有建立這個view的線程才能操做這個view,普通會認爲是將view建立在非UI線程中才會出現這個錯誤。
  • C.項目中異常分析

    • Android中相關的view和控件操做都不是線程安全的,因此Android纔會禁止在非UI線程更新UI,對於顯式的非法操做,好比說直接在Activity裏建立子線程,而後直接在子線程中操做UI等,Android會直接異常退出,並提示should run on UIThread之類的錯誤日誌信息。
    • 對於隱式非法操做,App不會直接簡單粗暴地異常退出,只是出現奇怪結果,Only the original thread that created a view hierarchy can touch its views即是一個例子,字面意思是隻有建立視圖層次結構的原始線程才能操做它的View,明顯是線程安全相關的。
  • D.引起崩潰日誌的流程分析

    • 在intentService中發送通知更新購物車頁面數據刷新,致使出現該問題。
  • F.解決辦法
  • G.其餘延申

4.9 NoSuchMethodException android.support.v4.app.Fragment$InstantiationException

  • 詳細日誌錯誤

    vity.baojia.ChoiceResultPsActivity}: android.support.v4.app.Fragment$InstantiationException: 
    Unable to instantiate fragment com.cheoo.app.fragment.choiceresultps.QuotationDaQuanFragment: 
    could not find Fragment constructor
  • 可能致使的出現該bug的緣由分析

    • 問題主要跟Activity的數據恢復有關,其可能產生的Exception:android.support.v4.app.Fragment$InstantiationException
    • 每一個Fragment必需要有一個無參構造方法,這樣該Fragment在Activity恢復狀態的時候才能夠被實例化。
    • 強烈建議,Fragment的子類不要有其餘含參構造方法,由於這些構造方法在Fragment從新實例化時不會被調用。取而代之的方式是,經過setArguments(Bundle)設置參數,而後經過getArguments得到參數。若是的Fragment沒有無參構造方法,app在恢復Activity時(例如旋轉設備),會出現crash。
  • 問題代碼以下所示,解決辦法就是添加一個無參構造方法便可。

    public static BusinessHotCarFragment newInstance(Map<String,String> map) {
        BusinessHotCarFragment fragment = new BusinessHotCarFragment(map);
        return fragment;
    }
    
    public BusinessHotCarFragment(Map<String,String> map) {
        this.pMap =map;
    }
    
    //解決問題辦法,添加一個無參構造方法
    public BusinessHotCarFragment(){}
  • 深刻分析爲什麼Fragment須要無參構造函數才能夠實例化

    • 既然報的找不到構造方法的錯誤,先來看一下Fragment的構造函數:

      • 谷歌翻譯一下可知。構造函數上有一段註釋:默認構造器。每個Fragment必須有一個無參的構造函數,以便當Activity恢復狀態時fragment能夠實例化。強烈建議fragment的子類不要有其餘的有參構造函數,由於當fragment從新實例化時不會調用這些有參構造函數;若是要傳值應該使用setArguments方法,在須要獲取這些值時調用getArguments方法。
      /**
       * Default constructor.  <strong>Every</strong> fragment must have an
       * empty constructor, so it can be instantiated when restoring its
       * activity's state.  It is strongly recommended that subclasses do not
       * have other constructors with parameters, since these constructors
       * will not be called when the fragment is re-instantiated; instead,
       * arguments can be supplied by the caller with {@link #setArguments}
       * and later retrieved by the Fragment with {@link #getArguments}.
       *
       * <p>Applications should generally not implement a constructor. Prefer
       * {@link #onAttach(Context)} instead. It is the first place application code can run where
       * the fragment is ready to be used - the point where the fragment is actually associated with
       * its context. Some applications may also want to implement {@link #onInflate} to retrieve
       * attributes from a layout resource, although note this happens when the fragment is attached.
       */
      public Fragment() {
      }
    • 這個異常是從哪裏來的?

      • 這一段註釋明確的告訴咱們使用有參構造函數會出問題,建議使用無參構造函數,可是並無告訴咱們具體是哪裏的問題。咱們在Fragment源碼中搜索could not find Fragment constructor這個異常,發現是在instantiate方法中拋出的。
      • 看上面的代碼咱們能夠知道,Fragment的實例化是經過調用類對象的getConstructor()方法獲取構造器對象並調用其newInstance()方法建立對象的。此時還會將args參數設置給Fragment。
      public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
          try {
              Class<?> clazz = sClassMap.get(fname);
              if (clazz == null) {
                  // Class not found in the cache, see if it's real, and try to add it
                  clazz = context.getClassLoader().loadClass(fname);
                  sClassMap.put(fname, clazz);
              }
              Fragment f = (Fragment) clazz.getConstructor().newInstance();
              if (args != null) {
                  args.setClassLoader(f.getClass().getClassLoader());
                  f.setArguments(args);
              }
              return f;
          } catch (ClassNotFoundException e) {
              throw new InstantiationException("Unable to instantiate fragment " + fname
                      + ": make sure class name exists, is public, and has an"
                      + " empty constructor that is public", e);
          } catch (java.lang.InstantiationException e) {
              throw new InstantiationException("Unable to instantiate fragment " + fname
                      + ": make sure class name exists, is public, and has an"
                      + " empty constructor that is public", e);
          } catch (IllegalAccessException e) {
              throw new InstantiationException("Unable to instantiate fragment " + fname
                      + ": make sure class name exists, is public, and has an"
                      + " empty constructor that is public", e);
          } catch (NoSuchMethodException e) {
              throw new InstantiationException("Unable to instantiate fragment " + fname
                      + ": could not find Fragment constructor", e);
          } catch (InvocationTargetException e) {
              throw new InstantiationException("Unable to instantiate fragment " + fname
                      + ": calling Fragment constructor caused an exception", e);
          }
      }
    • 這個異常是哪裏觸發的呢?

      • 找到了具體報錯的地方,可是這個方法是在哪裏調用觸發的呢?在Fragment沒有找到調用的地方,因爲Fragment是由FragmentManager管理的,在該類發現是在restoreAllState方法中調用的
      • 這方法名意爲恢復全部的狀態,而其中註釋爲建立激活Fragment的列表,並將他們從保存的狀態中實例化。這個方法應該是Fragment從新實例化時調用的方法。該方法在Fragment的restoreChildFragmentState被調用。
      void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
          // Build the full list of active fragments, instantiating them from
          // their saved state.
          mActive = new SparseArray<>(fms.mActive.length);
          for (int i=0; i<fms.mActive.length; i++) {
              FragmentState fs = fms.mActive[i];
              if (fs != null) {
                  FragmentManagerNonConfig childNonConfig = null;
                  if (childNonConfigs != null && i < childNonConfigs.size()) {
                      childNonConfig = childNonConfigs.get(i);
                  }
                  ViewModelStore viewModelStore = null;
                  if (viewModelStores != null && i < viewModelStores.size()) {
                      viewModelStore = viewModelStores.get(i);
                  }
                  Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig,
                          viewModelStore);
                  if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
                  mActive.put(f.mIndex, f);
                  // Now that the fragment is instantiated (or came from being
                  // retained above), clear mInstance in case we end up re-restoring
                  // from this FragmentState again.
                  fs.mInstance = null;
              }
          }
      ...   
      }
    • 而後看一下在Fragment的restoreChildFragmentState方法源碼。

      • restoreChildFragmentState方法又在Fragment的onCreate方法中調用,這裏將保存的savedInstanceState狀態又傳遞給了restoreChildFragmentState以完成Fragment的從新實例化。
      void restoreChildFragmentState(@Nullable Bundle savedInstanceState) {
          if (savedInstanceState != null) {
              Parcelable p = savedInstanceState.getParcelable(
                      FragmentActivity.FRAGMENTS_TAG);
              if (p != null) {
                  if (mChildFragmentManager == null) {
                      instantiateChildFragmentManager();
                  }
                  mChildFragmentManager.restoreAllState(p, mChildNonConfig);
                  mChildNonConfig = null;
                  mChildFragmentManager.dispatchCreate();
              }
          }
      }
    • 接着看看Fragment中的onCreate方法

      @CallSuper
      public void onCreate(@Nullable Bundle savedInstanceState) {
          mCalled = true;
          restoreChildFragmentState(savedInstanceState);
          if (mChildFragmentManager != null
                  && !mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) {
              mChildFragmentManager.dispatchCreate();
          }
      }
    • 得出結論

      • 通過以上的分析,咱們就知道了爲何這個錯誤出在了Fragment的有參構造函數上。由於當Fragment由於某種緣由從新建立時,會調用到onCreate方法傳入以前保存的狀態,在instantiate方法中經過反射無參構造函數建立一個Fragment,而且爲Arguments初始化爲原來保存的值,而此時若是沒有無參構造函數就會拋出異常,形成程序崩潰。

其餘介紹

01.關於博客彙總連接

02.關於個人博客

博客開源到GitHub上了,更多能夠看:https://github.com/yangchong2...

相關文章
相關標籤/搜索