乐趣区

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. 详细崩溃日志信息

    # 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 虚拟机找不到声明为 本机 的方法的适当本机语言定义,则引发。
    public class UnsatisfiedLinkError extends LinkageError {
        private static final long serialVersionUID = -4019343241616879428L;
    
        public UnsatisfiedLinkError() {super();
        }
    
        public UnsatisfiedLinkError(String s) {super(s);
        }
    }
  • C. 项目中异常分析

    • 根据实际项目可知,当准备播放视频时,找不到 libijkffmpeg.so 这个库,导致直接崩溃。
  • D. 引发崩溃日志的流程分析
  • F. 解决办法

    • 报这个错误通常是 so 库加载失败,或者找不到准备执行的 JNI 方法:

      • 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. 知识延申

    • 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. 详细崩溃日志信息

    • onSaveInstanceState 方法是在该 Activity 即将被销毁前调用,来保存 Activity 数据的,如果在保存玩状态后

再给它添加 Fragment 就会出错。

IllegalStateException: Can not perform this action after onSaveInstanceState:
  • B. 查看崩溃类信息

    • 在非法或不适当的时间调用方法的信号。换句话说,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. 项目中异常分析

    • 分析
  • 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. 详细崩溃日志信息

    • 报错日志,是不是有点眼熟呀?更多可以看我的开源项目:https://github.com/yangchong211
    android.view.WindowManager$BadTokenException
        Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
  • B. 查看崩溃类信息

    • 查询报错日志是从哪里来的
  • 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]()
  • 子线程中吐司的正确做法,代码如下所示

    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 的构造函数如下所示:

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();}
        }
    }
    • 错误打印日志
    • 解决方式:其实只要避免多个线程同时来调用 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. 详细崩溃日志信息

  • 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 发生的变化如下:

      • 1、在前面提到的几种情况下系统回收了 MyActivity
      • 2、通过 onSaveInstanceState 保存 MyFragment 的状态
      • 3、用户再次点击进入 app
      • 4、由于 MyActivity 被回收,系统会重启 MyActivity,根据之前保存的 MyFragment 的状态恢复 fragment
      • 5、MyActivity 的代码逻辑中,会再次创建新的 MyFragment
      • 6、页面出现混乱,覆盖了两层的 fragment。假如恢复的 MyFragment 使用到了 getActivity()方法,会报空指针异常
    • 对于上面的问题,可以考虑下面这两种解决办法:

      • 1、不保存 fragment 的状态:在 MyActivity 中重写 onSaveInstanceState 方法,将 super.onSaveInstanceState(outState); 注释掉,让其不再保存 Fragment 的状态,达到 fragment 随 MyActivity 一起销毁的目的。
      • 2、重建时清除已经保存的 fragment 的状态:在恢复 Fragment 之前把 Bundle 里面的 fragment 状态数据给清除。方法如下:
      if(savedInstanceState!= null){
          String FRAGMENTS_TAG =  "Android:support:fragments";
          savedInstanceState.remove(FRAGMENTS_TAG);
      }

4.1 IllegalArgumentException 导致崩溃【url 地址传入非法参数,转义字符】

  • A. 详细崩溃日志信息

  • 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"}
  • 日志错误截图

  • 解决办法

    • 第一种解决办法:客户端裁剪 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 是啥

    • AAPT 是 Android 资源打包工具
    • https://www.jianshu.com/p/8d6…

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. 关于博客汇总链接

  • 1. 技术博客汇总
  • 2. 开源项目汇总
  • 3. 生活博客汇总
  • 4. 喜马拉雅音频汇总
  • 5. 其他汇总

02. 关于我的博客

  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/…
  • 简书:http://www.jianshu.com/u/b7b2…
  • csdn:http://my.csdn.net/m0_37700275
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo…
  • 开源中国:https://my.oschina.net/zbj161…
  • 泡在网上的日子:http://www.jcodecraeer.com/me…
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
  • segmentfault 头条:https://segmentfault.com/u/xi…
  • 掘金:https://juejin.im/user/593943…

博客开源到 GitHub 上了,更多可以看:https://github.com/yangchong2…

退出移动版