来到新公司也有一个月了,最近一直都是在跟进公司的项目在友盟和bugly的Crash相关的问题,现在更进的项目叫:追更小说,由于最近的工作主要是修复项目中遇到的Crash问题,所以就此机会好好总结一下Crash相关的知识点,下面就先列出相关的知识点。
相关知识点汇总:
一、Crash产生的原理
二、Crash的保存,上报与统计
三、Crash的治理:分类,常见错误类型和解决思路
四、治理Crash的相关开源框架介绍
五、疑难杂症的分析与解决实战(不断更新)
六、扩展阅读
一、Crash产生的原理
当我们的应用发生Crash的时候,我们一般会看到两个现象:
1、App突然闪退
2、错误的log信息出现。
主线程异常:
子线程异常:
为什么出现Crash的时候会出现错误日志?
com.android.internal.os.RuntimeInit类分析:
RuntimeInit有两个的内部类,LoggingHandler和KillApplicationHandler。
LoggingHandler的作用是打印异常日志,而KillApplicationHandler就是App发生Crash的真正原因,其内部调用了Process.killProcess(Process.myPid())来杀死发生Uncaught异常的进程。 我们发现,这两个内部类都实现了Thread.UncaughtExceptionHandler接口。分别通过Thread.setUncaughtExceptionPreHandler和Thread.setDefaultUncaughtExceptionHandler方法进行注册。
Thread.setUncaughtExceptionPreHandler,覆盖所有线程,会在回调DefaultUncaughtExceptionHandler之前调用,只能在Android Framework内部调用该方法。 Thread.setDefaultUncaughtExceptionHandler,如果在任意线程中调用即可覆盖所有线程的异常,可以在应用层调用,每次调用传入的Thread.UncaughtExceptionHandler都会覆盖上一次的,即我们可以手动覆盖系统实现的KillApplicationHandler。 new Thread().setUncaughtExceptionHandler(),只可以覆盖当前线程的异常,如果某个Thread有定义UncaughtExceptionHandler,则忽略全局DefaultUncaughtExceptionHandler。
LoggingHandler源码:
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;
@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;
if (mCrashing) return;
//打印异常日志
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
StringBuilder message = new StringBuilder();
message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
final String processName = ActivityThread.currentProcessName();
if (processName != null) {
message.append("Process: ").append(processName).append(", ");
}
message.append("PID: ").append(Process.myPid());
Clog_e(TAG, message.toString(), e);
}
}
}
KillApplicationHandler 源码:
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
ensureLogging(t, e);
if (mCrashing) return;
mCrashing = true;
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
}
}
} finally {
//杀死进程
Process.killProcess(Process.myPid());
System.exit(10);
}
}
}
private void ensureLogging(Thread t, Throwable e) {
if (!mLoggingHandler.mTriggered) {
try {
mLoggingHandler.uncaughtException(t, e);
} catch (Throwable loggingThrowable) {
}
}
}
}
protected static final void commonInit() {
//设置异常处理回调
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
....
}
小结:Uncaught异常发生时会终止线程,此时,系统便会通知UncaughtExceptionHandler,告诉它被终止的线程以及对应的异常,然后便会调用uncaughtException函数。
如果该handler没有被显式设置,则会调用对应线程组的默认handler。如果我们要捕获该异常,必须实现我们自己的handler。
二、Crash的保存,上报与统计
从上面的发生crash时可以看出,发生crash时,会出发回调函数public void uncaughtException(Thread t, Throwable e)的执行,而错误的信息就在对象Throwable e中,下面就看看如何获取发生crash时的错误信息,并存储在本地。
Crash日志的本地存储:
private void saveCrashInfo2SD(Throwable e) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// No sdcard
Log.e(TAG, "saveCrashInfo2SD: sdcard unmounted,can not save crash info");
return;
}
File file = new File(crashStorageDir);
if (!file.exists()) {
file.mkdirs();
}
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
try {
//time = time.replace("-", "_").replace(":", "_").replace(" ", "_");
File file1 = new File(crashStorageDir + time + crashStorageFile);
if (!file1.exists()) {
file1.createNewFile();
}
PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(file1)));
writer.print(time);
saveDeviceInfo(writer);
writer.println();//换行
e.printStackTrace(writer);
writer.close();//关闭输出流
Process.killProcess(Process.myPid());//关闭应用进程
} catch (Exception e1) {
Log.e(TAG, "saveCrashInfo2SD: save crash exception");
}
}
存储设备相关信息:
private void saveDeviceInfo(PrintWriter writer) throws Exception{
PackageManager packageManager=mContext.getPackageManager();
PackageInfo packageInfo=packageManager.getPackageInfo(mContext.getPackageName(),
PackageManager.GET_ACTIVITIES);
writer.print("APP Version: ");
writer.print(packageInfo.versionName+"_");
writer.println(packageInfo.versionCode);
//Android OS
writer.print("OS Version: ");
writer.print(Build.VERSION.RELEASE+"_");
writer.println(Build.VERSION.SDK_INT);
//phone maker
writer.print("Vendor: ");
writer.println(Build.MANUFACTURER);
//phone type
writer.print("Model: ");
writer.println(Build.MODEL);
//cpu 架构
writer.print("CPU ABI: ");
writer.println(Build.CPU_ABI);
}
移动应用崩溃日志收集工具介绍:
腾讯 Bugly:
腾讯公司为移动开发者开放的服务之一,面向移动开发者提供专业的 Crash 监控、崩溃分析等质量跟踪服务。 腾讯无线研发部经过了四年多的开发与打磨,目前腾讯所有产品都已经接入了Bugly质量监控平台,开发同学只要登陆 Bugly 网站,就可以清晰的看到每天自己的产品有多少 Crash,影响了多少用户的使用,并可以根据 Bugly 提供的 Crash 日志进行问题修复,极大的提高了工作效率。
1、根据团队的介绍,Bugly 是业内首家能检测卡顿/ANR(应用主线程长时间失去响应时弹出的等待或关闭报错,在iOS平台一般称卡顿,Android平台一般称 ANR )的服务。
2、依托腾讯的服务器,Bugly 对用户在海外发生的应用崩溃也能实时上报。这个功能对于拥有海外发行应用的团队很有吸引力。对于 Android 移动应用的异常监控,除了普通的 Java 类型崩溃,Bugly 还能检测原生崩溃。
3、因此使用 Android NDK 开发 C/C++ 的移动开发团队也能使用 Bugly。
4、Bugly 能够统计应用启动多少秒之后崩溃的用户数,方便开发者直观了解对用户伤害巨大的闪退的情况。
5、Bugly 还能显示应用崩溃多少次以上的用户数,方便开发者了解对忠诚用户的伤害程度。
6、Bugly 还有问题搜索功能,允许开发者输入关键字搜索相关的崩溃。
7、比如开发者需要找到空指针引起的崩溃,只需在搜索框输入 "NullPoint" 即可。根据团队的说明,目前所有 Bugly 用户都能无限制免费使用这项服务,并且短期内没有收费计划。
友盟U-APP:
国内专业的移动应用统计分析平台。帮助移动应用开发商统计和分析流量来源、内容使用、用户属性和行为数据,以便开发商利用数据进行产品、运营、推广策略的决策。
1、应用趋势:清晰展现了应用的新增用户、活跃用户、启动次数、版本分布、行业指标等数据,方便从整体掌控应用的运营情况及增长动态。
2、渠道分析:友盟统计渠道分析功能可以实时查看各渠道的新增用户、活跃用户、次日留存率等用户指标,通过数据对比评估不同渠道的用户质量和活跃程度,从而衡量推广效果。
3、留存分析:您可以掌握每日(周/月)的新增用户在初次使用后一段时间内的留存率,留存率的高低一定程度上反映了产品和用户质量的好坏。
4、行为分析:针对性地进行应用内的数据统计,了解用户的产品使用细节及行为特征。
5、错误分析:收集并归类崩溃日志,提供错误管理及分析工具,帮助开发者更好的解决问题,从而提高应用的稳定性,改善应用质量。
友盟的侧重点在于运营数据的统计,相关的分析非常详尽,而错误分析只是其中一小部分功能,不是很全面。所以如果用来统计运营数据的话,友盟会非常适合,而收集分析应用崩溃信息则并不是很专业。
三、Crash的治理:分类,常见错误类型和解决思路
对于Crash的治理,我们尽量遵守以下三点原则:
1、由点到面:一个Crash发生了,我们不能只针对这个Crash的去解决,而要去考虑这一类Crash怎么去解决和预防。只有这样才能使得这一类Crash真正被解决。
2、异常不能随便吃掉:随意的使用try-catch,只会增加业务的分支和隐蔽真正的问题,要了解Crash的本质原因,根据本质原因去解决。catch的分支,更要根据业务场景去兜底,保证后续的流程正常。
3、预防胜于治理:当Crash发生的时候,损失已经造成了,我们再怎么治理也只是减少损失,尽可能的提前预防Crash的发生,可以将Crash消灭在萌芽阶段。
Crash不同维度的分类:
1、Native Crash 和 Java Crash。
2、应用级Crash,第三方依赖Crash,系统级Crash。
Java Crash常见错误:
一、NullPointerException 空指针
二、ClassCastException 类型转换异常
三、IndexOutOfBoundsException 下标越界异常
四、ActivityNotFoundException Activity未找到异常
五、IllegalStateException 非法状态异常
六、ArrayIndexOutOfBoundsException 数组越界异常
七、SecurityException 安全异常
八、llegalArgumentException: Service not registered 服务未注册异常
九、BadTokenException:
常见的Crash类型分析:
NullPointerException
NullPointerException是我们遇到最频繁的,造成这种Crash一般有两种情况:
1、对象本身没有进行初始化就进行操作。
2、对象已经初始化过,但是被回收或者手动置为null,然后对其进行操作。
针对第一种情况导致的原因有很多,可能是开发人员的失误、API返回数据解析异常、进程被杀死后静态变量没初始化导致,我们可以做的有:
1、对可能为空的对象做判空处理。
2、养成使用@NonNull和@Nullable注解的习惯。
3、尽量不使用静态变量,万不得已使用SharedPreferences来存储。
4、考虑使用Kotlin语言。
针对第二种情况大部分是由于Activity/Fragment销毁或被移除后,在Message、Runnable、网络等回调中执行了一些代码导致的,我们可以做的有:
1、Message、Runnable回调时,判断Activity/Fragment是否销毁或被移除;加try-catch保护;Activity/Fragment销毁时移除所有已发送的Runnable。
2、封装LifecycleMessage/Runnable基础组件,并自定义Lint检查,提示使用封装好的基础组件。
3、在BaseActivity、BaseFragment的onDestory()里把当前Activity所发的所有请求取消掉。
IndexOutOfBoundsException:
这类Crash常见于对ListView的操作和多线程下对容器的操作。
针对ListView中造成的IndexOutOfBoundsException,经常是因为外部也持有了Adapter里数据的引用(如在Adapter的构造函数里直接赋值),这时如果外部引用对数据更改了,但没有及时调用notifyDataSetChanged(),则有可能造成Crash,对此我们封装了一个BaseAdapter,数据统一由Adapter自己维护通知, 同时也极大的避免了The content of the adapter has changed but ListView did not receive a notification,这两类Crash目前得到了统一的解决。
另外,很多容器是线程不安全的,所以如果在多线程下对其操作就容易引发IndexOutOfBoundsException。常用的如JDK里的ArrayList和Android里的SparseArray、ArrayMap,同时也要注意有一些类的内部实现也是用的线程不安全的容器,如Bundle里用的就是ArrayMap。
OOM:
OOM是OutOfMemoryError的简称,在常见的Crash疑难排行榜上,OOM绝对可以名列前茅并且经久不衰。因为它发生时的Crash堆栈信息往往不是导致问题的根本原因,而只是压死骆驼的最后一根稻草。
导致OOM的原因大部分如下:
内存泄漏,大量无用对象没有被及时回收导致后续申请内存失败。
大内存对象过多,最常见的大对象就是Bitmap,几个大图同时加载很容易触发OOM。
内存泄漏:
内存泄漏指系统未能及时释放已经不再使用的内存对象,一般是由错误的程序代码逻辑引起的。在Android平台上,最常见也是最严重的内存泄漏就是Activity对象泄漏。Activity承载了App的整个界面功能,Activity的泄漏同时也意味着它持有的大量资源对象都无法被回收,极其容易造成OOM。
常见的可能会造成Activity泄漏的原因有:
1、匿名内部类实现Handler处理消息,可能导致隐式持有的Activity对象无法回收。
2、Activity和Context对象被混淆和滥用,在许多只需要Application Context而不需要使用Activity对象的地方使用了Activity对象,比如注册各类Receiver、计算屏幕密度等等。
3、View对象处理不当,使用Activity的LayoutInflater创建的View自身持有的Context对象其实就是Activity,这点经常被忽略,在自己实现View重用等场景下也会导致Activity泄漏。
对于Activity泄漏,目前已经有了一个非常好用的检测工具:LeakCanary,它可以自动检测到所有Activity的泄漏情况,并且在发生泄漏时给出十分友好的界面提示,同时为了防止开发人员的疏漏,我们也会将其上报到服务器,统一检查解决。另外我们可以在debug下使用StrictMode来检查Activity的泄露、Closeable对象没有被关闭等问题。
系统级Crash:
众所周知,Android的机型众多,碎片化严重,各个硬件厂商可能会定制自己的ROM,更改系统方法,导致特定机型的崩溃。发现这类Crash,主要靠云测平台配合自动化测试,以及线上监控,这种情况下的Crash堆栈信息很难直接定位问题。下面是常见的解决思路:
1、尝试找到造成Crash的可疑代码,看是否有特异的API或者调用方式不当导致的,尝试修改代码逻辑来进行规避。
2、通过Hook来解决,Hook分为Java Hook和Native Hook。Java Hook主要靠反射或者动态代理来更改相应API的行为,需要尝试找到可以Hook的点,一般Hook的点多为静态变量,同时需要注意Android不同版本的API,类名、方法名和成员变量名都可能不一样,所以要做好兼容工作;Native Hook原理上是用更改后方法把旧方法在内存地址上进行替换,需要考虑到Dalvik和ART的差异;相对来说Native Hook的兼容性更差一点,所以用Native Hook的时候需要配合降级策略。
3、如果通过前两种方式都无法解决的话,我们只能尝试反编译ROM,寻找解决的办法。
四、治理Crash的相关开源框架介绍
4.1、xCrash(应用崩溃捕获工具)
4.2、KOOM(快手自研OOM解决方案)
4.3、Matrix(腾讯APM框架Matrix)
4.4、Cockroach
4.5、Recovery(奔溃恢复框架)
4.1、xCrash
简述:在移动端 APP 的各种质量问题中,最严重的可能就是 APP 崩溃闪退了。
从安卓 APP 开发的角度,Java 崩溃捕获相对比较容易,JVM 给 Java 字节码提供了一个受控的运行环境,同时也提供了完善的 Java 崩溃捕获机制。Native 崩溃的捕获和处理相对比较困难,安卓系统的debuggerd 守护进程会为 native 崩溃自动生成详细的崩溃描述文件(tombstone)。
在开发调试阶段,可以通过系统提供的 bugreport 工具获取 tombstone 文件(或者将设备 root 后也可以拿到)。但是对于发布到线上的安卓 APP,如何获取 tombstone 文件,安卓操作系统本身并没有提供这样的功能。这个问题一直是安卓 native 崩溃分析和移动端 APM 系统的痛点之一。
2019 年,爱奇艺在 GitHub 上开源了 xCrash。这是一个比较完整的安卓 APP 崩溃捕获 SDK,它能在 App 进程崩溃时,在你指定的目录中生成 tombstone 文件(格式与系统的 tombstone 文件类似)。它支持捕获 native 崩溃和 Java 崩溃;支持安卓 4.0 - 9.0;支持 armeabi,armeabi-v7a,arm64-v8a,x86 和 x86_64。
项目地址:https://github.com/iqiyi/xCrash
4.2、KOOM(目前仅支持AndroidX)
简述:OOM是当前Android开发中的常见疑难问题,尤其是线上发生的OOM问题极难定位。业界当前最知名的方案LeakCanary,通过监控Activity/Fragment泄漏优化Java OOM问题,多年来一直为广大app保驾护航,解决了OOM治理从0到1的问题。但面对行业不断复杂的业务环境和庞大用户流量,LeakCanary仍有优化空间:受限于性能,无法在线上大规模部署,仅支持线下使用;只能定位Activity&Fragment泄漏,无法定位大对象、频繁分配等问题;需要人工一一分析,无法对问题聚类量化……为了彻底解决OOM问题,行业尝试了多种解决方案,通常是基于LeakCanary做优化,但至今没有能完全解决监控过程中的性能问题,普遍解决方法是通过采样的办法牺牲一小部分用户的体验来定位问题。
快手OOM Killer沿用行业的研究思路,针对LeakCanary无法解决的难题进行自研改造,充分发挥LeakCanary原有优势的同时补足短板,打造了一套可以线上部署、兼顾线下、配置灵活、适用范围广泛、高度自动化,埋点、监控、解析、上报、分发、跟进、报警一站式服务的闭环监控系统,将绝大多数OOM问题拦截在灰度阶段,彻底解决了OOM问题。
项目地址:https://github.com/KwaiAppTeam/KOOM
4.3、Matrix
简述:
Matrix 是一款微信研发并日常使用的应用性能接入框架,支持iOS, macOS和Android。 Matrix 通过接入各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。
Matrix for Android:
一:Matrix-android 当前监控范围包括:应用安装包大小,帧率变化,启动耗时,卡顿,慢方法,SQLite 操作优化,文件读写,内存泄漏等等。
二:APK Checker: 针对 APK 安装包的分析检测工具,根据一系列设定好的规则,检测 APK 是否存在特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪。
三:Resource Canary: 基于 WeakReference 的特性和 Square Haha 库开发的 Activity 泄漏和 Bitmap 重复创建检测工具。
四:Trace Canary: 监控界面流畅性、启动耗时、页面切换耗时、慢函数及卡顿等问题。
五:SQLite Lint: 按官方最佳实践自动化检测 SQLite 语句的使用质量。
六:IO Canary: 检测文件 IO 问题,包括:文件 IO 监控和 Closeable Leak 监控。
项目地址:https://github.com/Tencent/matrix
4.4、Cockroach
简述:很多时候由于一些微不足道的bug导致app崩溃很可惜,android默认的异常杀进程机制简单粗暴,但很多时候让app崩溃其实也并不能解决问题。
有些bug可能是系统bug,对于这些难以预料的系统bug我们不好绕过,还有一些bug是我们自己编码造成的,对于有些bug来说直接忽略掉的话可能只是导致部分不重要的功能没法使用而已,又或者对用户来说完全没有影响,这种情况总比每次都崩溃要好很多。
Cockroach可以避免应用因为一些小bug导致app崩溃,例如:某次热修复发布,增加埋点日志,出现空指针问题,大量不影响用户操作的异常如果没有进行捕获,可能会导致严重故障。
下面介绍几个真实案例来说明这个库的优势:
1、有一款特殊的手机,每次开启某个Activity时都报错,提示没有在清单中声明,但其他几百万机型都没问题,这种情况很可能就是系统bug了,由于是在onclick回调里直接使用startActivity来开启Activity,onclick里没有其他逻辑,对于这种情况的话直接忽略掉是最好的选择,因为onclick回调是在一个单独的message中的,执行完了该message就接着执行下一个message,该message执行不完也不会影响下一个message的执行,调用startactivity后会同步等待ams返回的错误码,结果这款特殊的机型返回了没有声明这个Activity,所以对于这种情况可以直接忽略掉,唯一的影响就是这个Activity不会显示,就跟没有调用onClick一样。
2、我们在app中集成了个三方的数据统计库,这个库是在Application的onCreate的最后初始化的,但上线后执行初始化时却崩溃了,对于这种情况直接忽略掉也是最好的选择。根据app的启动流程来分析,Application的创建以及onCreate方法的调用都是在同一个message中执行的,该message执行的最后调用了Application的onCreate方法,又由于这个数据统计库是在onCreate的最后才初始化的,所以直接忽略的话也没有影响,就跟没有初始化过一样。
3、我们做了个检查app是否需要升级的功能,若需要升级,则使用context开启一个dialog风格的Activity提示是否需要升级,测试阶段没有任何问题,但一上线就崩溃了,提示没有设置FLAG_ACTIVITY_NEW_TASK,由于启动Activity的context是Application,但在高版本android中,可以使用Application启动Activity并且不设置这个FLAG,但在低版本中必须要设置这个FLAG,对于这种问题也可以直接忽略。
项目地址:https://github.com/android-notes/Cockroach
4.5、Recovery
简述:一般的Android应用只是对Crash进行捕捉。Recovery框架能做到对Crash进行捕捉并自动恢复,大大地提高APP的用户体验。
ActivityStack的恢复:对于恢复界面,默认是恢复整个Activity的堆栈,以便保护用户之前的数据。
当应用在前台时崩溃无非就三种:
1、界面一创建就崩溃,可能在onCreate等方法中读取数据造成的Crash
2、界面创建且绘制完成正常显示,在用户执行某个操作,如点击按钮执行某个操作等造成的Crash
3、其它异步线程、服务等在后台执行任务时导致的Crash
上面的情况都应恢复绘制完成后的界面,也就是栈顶Activity是在Crash之前用户所看到的界面,而之前创建且未销毁的Activity也应该进行恢复。
当应用在后台时:
1、进程未挂,无非就是异步线程、server等后台任务发生异常时导致的Crash
2、进程已挂,进程被360等工具杀死了,常见的是push过来了然后唤起App进程,在解析push信息时候导致Crash
上面的情况App在后台时导致的Crash,Recovery提供了一个参数(recoverInBackgroud),用来设置是否在后台Crash时进行恢复。
ActivityStack恢复的操作,都是先恢复栈中的Activity,无Activity时则重启应用。
项目地址:https://github.com/Sunzxyong/Recovery
五、疑难杂症的分析与解决实战
案例一:只发生在vivo V3Max的Crash
我们发现原生系统上对应系统版本的AbsListView里并没有UpdateBottomFlagTask类,因此可以断定是vivo该版本定制的ROM修改了系统的实现。我们在定位这个Crash的可疑点无果后决定通过Hook的方式解决,通过源码发现AsyncTask$SerialExecutor是静态变量,是一个很好的Hook的点,通过反射添加try-catch解决。因为修改的是final对象所以需要先反射修改accessFlags,需要注意ART和Dalvik下对应的Class不同,代码如下:
美团外卖App用上述方法解决了对应的Crash:
案例二:DialogFragment出现IllegalStateException: Can not perform this action after * onSaveInstanceState异常。
解析:在dialog中执行show函数时,概率性出现上面错误信息,查看源码看此异常的出现逻辑。
重写show()函数:
@Override
public void show(FragmentManager manager, String tag) {
if (manager.isStateSaved()) {
Logger.d(TAG, "show: isStateSaved = true");
return;
}
try {
super.show();
} catch (Exception e) {
Logger.w(TAG, "show error"+e);
}
}
案例三:IllegalArgumentException: Service not registered 服务未注册异常
错误信息:
W System.err: java.lang.IllegalArgumentException: Service not registered:com.programandroid.Exception.ExceptionActivity$1@5f3161e
W System.err: at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:1363)
W System.err: at android.app.ContextImpl.unbindService(ContextImpl.java:1499)
W System.err: at android.content.ContextWrapper.unbindService(ContextWrapper.java:648)
W System.err: at com.programandroid.Exception.ExceptionActivity.ServiceNotRegisteredCrash(ExceptionActivity.java:276)
W System.err: at java.lang.reflect.Method.invoke(Native Method)
W System.err: at android.view.View$DeclaredOnClickListener.onClick(View.java:4744)
W System.err: at android.view.View.performClick(View.java:5675)
log分析:
此异常经常发生在错误的解除绑定服务造成的,解决方法:
1.解除绑定服务之前,先判断是否绑定过,只有绑定过后才可以解绑。
2.使用try-catch 抓取住异常。
代码实现:
案例四:BadTokenException异常处理
错误log信息:
FATAL EXCEPTION: main
Process: com.android.fmradio, PID: 5564
java.lang.RuntimeException: Error receiving broadcast Intent { act=android.intent.action.HEADSET_PLUG flg=0x40000010 (has extras) } in com.android.fmradio.FmService$FmServiceBroadcastReceiver@b3d2a03
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1401)
at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6702)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@f652dba -- permission denied for window type 2003
at android.view.ViewRootImpl.setView(ViewRootImpl.java:851)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.app.Dialog.show(Dialog.java:329)
at com.android.fmradio.FmService$FmServiceBroadcastReceiver.onReceive(FmService.java:322)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1391)
... 8 more
解决方案:
系统弹窗,请用TYPE_APPLICATION_OVERLAY 替换之前的Windows Type。
Dialog mFMDialog = new AlertDialog.Builder(context)
.setTitle(R.string.airplane_title).setMessage(R.string.airplane_message)
.setPositiveButton(R.string.close_FM,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}
).setCancelable(false).create();
// Android 8.0 之后弹出系统弹窗,需要使用 TYPE_APPLICATION_OVERLAY
// <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
// 一下两个 之前常用的系统的Dialog 会报
// BadTokenException: Unable to add window android.view.ViewRootImpl$W@f652dba -- permission denied for window type 2003
//mFMDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
//mFMDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
mFMDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
mFMDialog.show();
六、扩展阅读:
1、https://tech.meituan.com/2018/06/14/waimai-android-crash.html(美团外卖Android Crash治理之路)
2、https://www.jianshu.com/p/0ff8646871f9(Android 微信APM工具 Matrix使用)
3、https://www.jianshu.com/p/01b69d91a3a8(Android开发之打造永不崩溃的APP——Crash防护)
4、https://blog.csdn.net/leeo1010/article/details/50522892(Android平台的崩溃捕获机制及实现)
5、https://my.oschina.net/bugly/blog/1354954(Android 平台 Native 代码的崩溃捕获机制及实现)
6、https://www.jianshu.com/p/c7a6cef6e2dc(Android Crash 解决方案)
7、http://blog.itpub.net/69945252/viewspace-2674668/(安卓APP崩溃捕获方案——xCrash)
8、https://developer.51cto.com/art/202008/623474.htm(快手自研OOM解决方案KOOM今日宣布开源)
9、https://www.jianshu.com/p/f2d3899758f8(Android OOM 解决方案)
10、https://www.jianshu.com/p/83b96506a449(你知道Android为什么会Crash吗)
11、https://blog.csdn.net/csdn_aiyang/article/details/105054241(Crash治理之路——AndroidCrashX开源库)
12、https://www.jianshu.com/p/fc0f6e38e2f3(Android应用崩溃(Crash)日志报告)
13、https://kymjs.com/code/2018/08/22/01/(Android Native Crash 收集)
14、https://blog.csdn.net/u010144805/article/details/80763956(使用objdump进行Android crash 日志 分析)
15、https://blog.csdn.net/hfut_why/article/details/85221069(Crash信息本地存储)