关于ANR异常捕获与分析,你所需要知道的一切

2017年11月27日 19:48:05 大_熊_ 阅读数:1796 标签: AndroidANRAMSBugly 更多

个人分类: Android 应用

背景

最近项目组需要实现捕获ANR并上传到公司服务器相关的功能,因此花了点时间来整理相关的知识,并从AMS源码与腾讯Bugly-SDK中逆向找到相关思路,在此分享给大家。

ANR是什么?

Application Not Responding的缩写,即应用程序无响应。简单来说,就是应用跑着跑着,突然duang~,界面卡住了,无法响应用户的操作如触摸事件等。

触发ANR的原因

  • 应用进程自身引起 
    例如: 
    1.主线程阻塞、挂起、死循环 
    2.应用进程的其他线程的CPU占用率高,使得主线程无法抢占到CPU时间片

  • 其他进程间接引起(误伤) 
    例如: 
    1.当前应用进程进行进程间通信请求其他进程,其他进程的操作长时间没有反馈 
    2.其他进程的CPU占用率极高,使得当前应用进程无法抢占到CPU时间片

ANR的分类:

  1. 应用在5秒内未响应用户的输入事件,如按键或触摸事件
  2. BroadcastReceiver未在10秒内完成相关的处理
  3. Service的各个生命周期函数时20秒内没有执行完毕

主要还是以上3种情况,其根本原因都是主线程被阻塞导致的。

Tips: 
其中2,3属于Background ANR,实际上已经发生了ANR,但不会进行对话框弹出。可以在Android开发者选项—>高级—>显示所有”应用程序无响应“,勾选后即可对后台ANR也进行弹框显示。

问题分解

要实现捕获ANR功能,简单来说,需要解决以下问题: 
1. 获取什么信息? 
2. 去哪里获取? 
3. 什么时候去获取?

对于上述问题,我们一步一步来,逐个解决。

一、获取什么信息?

1. Cause reason: 
当ANR产生的时候,logcat会打印出一段log,会输出类似下面的信息。 
1) 首先可以得到ANR所在进程的进程名、进程号、及出错的组件; 
2) 其中Reason主要描述了ANR产生的具体原因/分类; 
3) CPU usage...ago则主要记录了ANR发生前CPU的使用状况; 
4) CPU usage...later则记录了ANR发生之后CPU的使用状况。

E/ActivityManager: ANR in com.example.testapp (com.example.testapp/.CrashTestActivity)PID: 2480Reason: Input dispatching timed out (Waiting because the touched window has not finished processing the input events that were previously delivered to it.)Load: 0.06 / 0.08 / 0.05CPU usage from 9865ms to 0ms ago:0.7% 1558/system_server: 0% user + 0.7% kernel / faults: 39 minor0.4% 1143/adbd: 0% user + 0.4% kernel / faults: 117 minor0.4% 1796/com.estrongs.android.pop: 0.2% user + 0.2% kernel / faults: 95 minor0.2% 1132/surfaceflinger: 0% user + 0.2% kernel0.1% 1114/kworker/0:1H: 0% user + 0.1% kernel0.1% 1131/rild: 0% user + 0.1% kernel0.1% 1682/com.android.phone: 0% user + 0.1% kernel+0% 2510/logcat: 0% user + 0% kernel0.8% TOTAL: 0.1% user + 0.7% kernel + 0% iowaitCPU usage from 1098ms to 1603ms later:2% 1558/system_server: 2% user + 0% kernel2% 1573/ActivityManager: 2% user + 0% kernel0% TOTAL: 0% user + 0% kernel

结合此部分信息进行分析可以初步得出产生ANR的基本原因,以及排除是否属于误伤。什么意思呢?也就是说如果发现ANR前后有某个进程在占用大量CPU,那么ANR的产生很可能是误伤-_-|||,具体可以回看上面关于ANR触发原因一节。

2. ANR traces 
在知道了Cause reason之后,需要进一步的信息来确定ANR在代码中的位置,这个时候就要去查看traces文件了。每次产生ANR之后,系统都会向/data/anr/traces.txt中写入新的数据。内容大概如下: 
1)介于----- pid 0000 xxx ---------- end 0000 -----之间的为进程0000的所有线程堆栈信息。一般来说,发生ANR的进程信息会在文件头部,下面AMS源码分析的时候会说明为什么。 
2) "main" prio=5 tid=1 TIMED_WAIT分为为线程名、线程优先级(默认值5)、线程ID、线程状态;主线程之后会接着打印进程中其他线程的信息,此处不再贴出。

----- pid 2480 at 2017-04-06 08:48:58 -----
Cmd line: com.example.testappJNI: CheckJNI is on; workarounds are off; pins=0; globals=263DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)"main" prio=5 tid=1 TIMED_WAIT| group="main" sCount=1 dsCount=0 obj=0xaccdcbd8 self=0xb80204a0| sysTid=2480 nice=0 sched=0/0 cgrp=[fopen-error:2] handle=-1217093536| state=S schedstat=( 0 0 0 ) utm=2 stm=1 core=1at java.lang.VMThread.sleep(Native Method)at java.lang.Thread.sleep(Thread.java:1013)at java.lang.Thread.sleep(Thread.java:995)at com.tencent.bugly.proguard.ag.a(BUGLY:897)at com.tencent.bugly.crashreport.crash.anr.BuglyTestANR_Reciver.onReceive(BUGLY:30)at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:768)at android.os.Handler.handleCallback(Handler.java:733)at android.os.Handler.dispatchMessage(Handler.java:95)at android.os.Looper.loop(Looper.java:136)at android.app.ActivityThread.main(ActivityThread.java:5017)at java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:515)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)at dalvik.system.NativeStart.main(Native Method)"AnotherThread1" xxx
xxx
xxx
xxx"AnotherThread2" xxx
xxx
xxx
xxx----- end 2480 ---------- pid 1558 at 2017-04-06 08:48:58 -----
Cmd line: another_process_name
...
...
...
----- end 2480 -----...
...
...

Tips: 
关于ANR traces的保存时长: 
traces.txt:只保留最近一次发生ANR时的信息,位置:/data/anr/traces.txt 
DropBox: Android 2.2 开始增加, 会保留历史上发生的所有ANR的logs,位置:/data/system/dropbox,保存时长3天。详见:ActivityManagerService.addErrorToDropBox()

相关源码: 
ActivityManagerService.java 
DropBoxManagerService.java 
SystemServer.java

3. 关于线程状态 
了解线程的状态对分析traces文件至关重要,下表列出了各线程的状态及含义: 

Read The Fucking Source Code

在解释后面的两个问题:2.去哪里获取?3.什么时候去获取?之前,我们需要简单的阅读下ActivityManagerService的源码了。

1) 在产生ANR的时候,会回调到AMS的appNotResponding()方法,以下为关键代码,中文注释为相关代码的解读:

final void appNotResponding(ProcessRecord app, ActivityRecord activity,ActivityRecord parent, boolean aboveSystem, final String annotation) {// firstPids与lastPids将记录将要dump线程堆栈信息的进程号,其中firstPids会优先输出,详见后面的dumpStackTraces()方法ArrayList<Integer> firstPids = new ArrayList<Integer>(5);SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);...synchronized (this) {// 判断重启、已处于ANR或Crash状态直接返回不处理// PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.if (mShuttingDown) {Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);return;} else if (app.notResponding) {Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);return;} else if (app.crashing) {Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);return;}// 设置notResponding标志位// In case we come through here for the same app before completing// this one, mark as anring now so we will bail out.app.notResponding = true;...// 这里将把当前进程的id最先加到列表中,因此能保证产生ANR的进程能在traces.txt的头部。// Dump thread traces as quickly as we can, starting with "interesting" processes.firstPids.add(app.pid);int parentPid = app.pid;if (parent != null && parent.app != null && parent.app.pid > 0) parentPid = parent.app.pid;if (parentPid != app.pid) firstPids.add(parentPid);if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);// 将设有persistent属性的进程加入firstPids队列,其他则加入lastPids队列// 其中mLruProcesses根据LRU算法存储了最近使用的进程信息for (int i = mLruProcesses.size() - 1; i >= 0; i--) {ProcessRecord r = mLruProcesses.get(i);if (r != null && r.thread != null) {int pid = r.pid;if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {if (r.persistent) {firstPids.add(pid);} else {lastPids.put(pid, Boolean.TRUE);}}}}}// 准备在logcat中打印出Cause Reason信息// Log the ANR to the main log.StringBuilder info = new StringBuilder();info.setLength(0);info.append("ANR in ").append(app.processName);if (activity != null && activity.shortComponentName != null) {info.append(" (").append(activity.shortComponentName).append(")");}info.append("\n");info.append("PID: ").append(app.pid).append("\n");if (annotation != null) {info.append("Reason: ").append(annotation).append("\n");}if (parent != null && parent != activity) {info.append("Parent: ").append(parent.shortComponentName).append("\n");}final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);// 执行此函数将dump相关进程中的线程堆栈信息到traces.txt文件中File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids,NATIVE_STACKS_OF_INTEREST);String cpuInfo = null;// 打印CPU usage相关信息if (MONITOR_CPU_USAGE) {updateCpuStatsNow();synchronized (mProcessCpuTracker) {cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);}info.append(processCpuTracker.printCurrentLoad());info.append(cpuInfo);}info.append(processCpuTracker.printCurrentState(anrTime));Slog.e(TAG, info.toString());if (tracesFile == null) {// There is no trace file, so dump (only) the alleged culprit's threads to the logProcess.sendSignal(app.pid, Process.SIGNAL_QUIT);}// 添加ANR信息至DropBox,默认有效期为3天addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,cpuInfo, tracesFile, null);...// 此处获取开发者选项—>高级—>显示所有"应用程序无响应"中的值,若勾选了显示Background ANR,则会显示Broadcast等后台组件所产生的ANR Dialog// Unless configured otherwise, swallow ANRs in background processes & kill the process.boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;synchronized (this) {mBatteryStatsService.noteProcessAnr(app.processName, app.uid);if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {app.kill("bg anr", true);return;}// Set the app's notResponding state, and look up the errorReportReceivermakeAppNotRespondingLocked(app,activity != null ? activity.shortComponentName : null,annotation != null ? "ANR " + annotation : "ANR",info.toString());// 通过hander发送消息,通知显示ANR Dialog// Bring up the infamous App Not Responding dialogMessage msg = Message.obtain();HashMap<String, Object> map = new HashMap<String, Object>();msg.what = SHOW_NOT_RESPONDING_MSG;msg.obj = map;msg.arg1 = aboveSystem ? 1 : 0;map.put("app", app);if (activity != null) {map.put("activity", activity);}mHandler.sendMessage(msg);}
}

2) 我们来详细看下dumpStackTraces()这个方法,此处将dump出firstPidslastPids进程的相关线程堆栈信息至traces.txt,中文注释为相关代码的解读。

public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {// 根据此环境变量获取traces.txt生成路径,默认值为/data/anr/traces.txtString tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);if (tracesPath == null || tracesPath.length() == 0) {return null;}File tracesFile = new File(tracesPath);try {File tracesDir = tracesFile.getParentFile();if (!tracesDir.exists()) {tracesDir.mkdirs();if (!SELinux.restorecon(tracesDir)) {return null;}}// 删除旧的traces.txt文件,并创建新文件FileUtils.setPermissions(tracesDir.getPath(), 0775, -1, -1);  // drwxrwxr-xif (clearTraces && tracesFile.exists()) tracesFile.delete();tracesFile.createNewFile();FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-} catch (IOException e) {Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);return null;}dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);return tracesFile;
}private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {// Use a FileObserver to detect when traces finish writing.// The order of traces is considered important to maintain for legibility.// 每个进程是按照先后顺序dump信息至traces.txt文件中的// 通过FileObserver监听完成写入的操作,并通过wait()/notify()机制等待/唤醒,并继续发送Signal给下一个进程,见下面的for循环FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {@Overridepublic synchronized void onEvent(int event, String path) { notify(); }};try {observer.startWatching();// First collect all of the stacks of the most important pids.if (firstPids != null) {try {int num = firstPids.size();for (int i = 0; i < num; i++) {synchronized (observer) {// 目标进程的虚拟机实例接收到SIGNAL_QUIT信号后会由"Signal Catcher"线程将进程中各个线程的函数堆栈信息输出到traces.txt文件中.// 优先输出firstPids中的进程堆栈信息Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);observer.wait(200);  // Wait for write-close, give up after 200msec}}} catch (InterruptedException e) {Slog.wtf(TAG, e);}}// Next collect the stacks of the native pidsif (nativeProcs != null) {int[] pids = Process.getPidsForCommands(nativeProcs);if (pids != null) {for (int pid : pids) {Debug.dumpNativeBacktraceToFile(pid, tracesPath);}}}// Lastly, measure CPU usage.if (processCpuTracker != null) {processCpuTracker.init();System.gc();processCpuTracker.update();try {synchronized (processCpuTracker) {processCpuTracker.wait(500); // measure over 1/2 second.}} catch (InterruptedException e) {}processCpuTracker.update();// 获取最近频繁使用CPU的进程// We'll take the stack crawls of just the top apps using CPU.final int N = processCpuTracker.countWorkingStats();int numProcs = 0;for (int i=0; i<N && numProcs<5; i++) {ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);// 根据频繁使用CPU的进程与lastPids输出相关进程堆栈信息if (lastPids.indexOfKey(stats.pid) >= 0) {numProcs++;try {synchronized (observer) {Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);observer.wait(200);  // Wait for write-close, give up after 200msec}} catch (InterruptedException e) {Slog.wtf(TAG, e);}}}}} finally {observer.stopWatching();}
}

二、去哪里获取?

1. Cause reason: 
- 关于reason信息的获取,到目前为止,我们只知道能在logcat中能检索到相关信息,难道要开个线程不断循环去检测logs?这想法看来还是Too Young Too Simple。 
- 这里先说下结论,可通过ActivityManagerService.getProcessesInErrorState()方法获取进程的ANR信息,此方法是通过逆向Bugly时发现的,后面会讲到。 
- 此方法会遍历mLruProcesses,并根据进程目前的异常状态如crash或者anr类型,返回具体的ProcessErrorStateInfo,具体看代码,比较简单:

public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {...synchronized (this) {// iterate across all processesfor (int i=mLruProcesses.size()-1; i>=0; i--) {ProcessRecord app = mLruProcesses.get(i);if (!allUsers && app.userId != userId) {continue;}if ((app.thread != null) && (app.crashing || app.notResponding)) {// This one's in trouble, so we'll generate a report for it// crashes are higher priority (in case there's a crash *and* an anr)ActivityManager.ProcessErrorStateInfo report = null;if (app.crashing) {report = app.crashingReport;} else if (app.notResponding) {report = app.notRespondingReport;}if (report != null) {if (errList == null) {errList = new ArrayList<ActivityManager.ProcessErrorStateInfo>(1);}errList.add(report);} else {Slog.w(TAG, "Missing app error report, app = " + app.processName +" crashing = " + app.crashing +" notResponding = " + app.notResponding);}}}}return errList;
}

注意app.notRespondingReport非空的时候才会返回,那它是什么时候初始化的呢?是在makeAppNotRespondingLocked()方法中。其中此方法又是在appNotResponding()中被调用的(在所有进程写完traces.txt之后,可回看上面AMS相关代码)

private void makeAppNotRespondingLocked(ProcessRecord app,String activity, String shortMsg, String longMsg) {app.notResponding = true;app.notRespondingReport = generateProcessError(app,ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,activity, shortMsg, longMsg, null);startAppProblemLocked(app);app.stopFreezingAllLocked();
}

2. ANR traces 
这部分内容获取比较简单,直接读取/data/anr/traces.txt里面第一个进程的信息即可,能保证首个进程即是当前ANR的进程,至于为什么上面分析AMS源码中已经说明了,当前进的pid会首先被add进firstPids中被优先输出。此处的难点是如何从traces中过滤出相关的信息,如进程名,进程pid,生成时间,各线程名、tid、优先级、状态、堆栈信息等。这里就要用到强大的正则表达式来进行过滤了,此处忽略一万字…(大家可以反编译Bugly的SDK,参考里面的正则表达式)

三、什么时候去获取?

在上面AMS的源码分析中,我们可以关注到dumpStackTraces()方法中的FileObserver,系统通过该类监听文件/data/anr/traces.txt来达到顺序依次写入各个进程的traces信息。按照这个思路,当ANR发生的时候,我们也可以通过监听该文件的写入情况来判断是否发生了ANR,看起来这是一个不错的时机。需要注意的一点是,所有应用发生ANR的时候都会进行回调,因此需要做一些过滤与判断,如包名、进程号等。

在实现前,我们先来看看Bugly中是如何实现的。这里笔者反编译的是Bugly-v1.2.8版本,搜索关键字FileObserver,果不其然很快发现了一些相关代码,位于com.tencent.bugly.crashreport.crash.anr包下的b.class

protected synchronized void b() {if(this.d()) {z.d("start when started!", new Object[0]);} else {this.o = new FileObserver("/data/anr/", 8) {public void onEvent(int event, String path) {if(path != null) {String var3 = "/data/anr/" + path;if(!var3.contains("trace")) {z.d("not anr file %s", new Object[]{var3});} else {b.this.a(var3);}}}};try {this.o.startWatching();z.a("start anr monitor!", new Object[0]);...} catch (Throwable var2) {this.o = null;z.d("start anr monitor failed!", new Object[0]);...}}
}
  • 上面这段代码比较简单,就是一个启动monitor的方法,监听/data/anr/这个目录,并过滤包含trace的文件名。在FileObserver创建的时候可以传入一个mask参数,8正是代表着CLOSE_WRITE这个常量,当有写入并且close的时候将会回调,跟AMS中的使用基本吻合。

继续翻看b.class的代码,当过滤出trace文件的时候,会执行b.this.a(var3)这个方法。从代码逻辑上可以看出,这是一个对trace文件有效性判断的方法,精简过后的代码如下:

public final void a(String var1) {....try {z.c("read trace first dump for create time!", new Object[0]);long var2 = -1L;com.tencent.bugly.crashreport.crash.anr.c.a var4 = com.tencent.bugly.crashreport.crash.anr.c.a(var1, false);if(var4 != null) {var2 = var4.c;}if(var2 == -1L) {z.d("trace dump fail could not get time!", new Object[0]);var2 = (new Date()).getTime();}if(Math.abs(var2 - this.f) < 10000L) {z.d("should not process ANR too Fre in %d", new Object[]{Integer.valueOf(10000)});} else {...if(var5 != null && var5.size() > 0) {ProcessErrorStateInfo var6 = this.a(this.g, 10000L);if(var6 == null) {z.c("proc state is unvisiable!", new Object[0]);} else if(var6.pid != Process.myPid()) {z.c("not mind proc!", new Object[]{var6.processName});} else {z.a("found visiable anr , start to process!", new Object[0]);this.a(this.g, var1, var6, var2, var5);}} else {z.d("can\'t get all thread skip this anr", new Object[0]);}}} catch (Throwable var13) {...z.e("handle anr error %s", new Object[]{var13.getClass().toString()});}...
}

上面这段代码的意思也比较好理解,先解析出时间,对一些重复的回调或太频繁的ANR进行过滤(为什么会重复回调,因为上面阅读AMS源码的时候也说明了,firstPids中各个进程的信息会依次写入trace文件中)。然后对进程异常状态和进程号进行判断,过滤掉无效或其他应用的回调,最后对有效的trace进行处理。

这个时候ProcessErrorStateInfo这个类引起了我的注意,我们继续翻代码看他到底获取的是什么信息,进入this.a(this.g, 10000L)这个方法:

protected ProcessErrorStateInfo a(Context var1, long var2) {var2 = var2 < 0L?0L:var2;z.c("to find!", new Object[0]);ActivityManager var4 = (ActivityManager)var1.getSystemService("activity");long var5 = var2 / 500L;int var7 = 0;do {z.c("waiting!", new Object[0]);List var8 = var4.getProcessesInErrorState();if(var8 != null) {Iterator var9 = var8.iterator();while(var9.hasNext()) {ProcessErrorStateInfo var10 = (ProcessErrorStateInfo)var9.next();if(var10.condition == 2) {z.c("found!", new Object[0]);return var10;}}}ag.a(500L);} while((long)(var7++) < var5);z.c("end!", new Object[0]);return null;
}

可以把这段代码copy到自己的项目中跑一下,首先通过ActivityManager.getProcessesInErrorState()来获取系统中有所有异常进程的信息,通过condition来过滤出目标进程,而2正是代表着NOT_RESPONDING这个常量,即ANR异常。而ProcessErrorStateInfo中的shortMsglongMsg变量正是我们之前在logcat中看到的Cause reason!

可以看到,其实Bugly对于ANR的触发时机监听也正是使用FileObserver来实现的。而且之前一直在思考logcat中的Cause reason是从哪里获取的现在也有了答案,反编译有些时候还真是个好法宝,嘿嘿嘿~

总结

至此,通过AMS源码分析和Bugly-SDK的逆向,我们终于解决了捕获ANR异常最重要的3个问题:1.获取什么信息?2.去哪里获取?3.什么时候去获取?希望这篇文章能给正在开发相关功能的同学们一些指导,顺便分享下自己的思路与经验~

参考

https://developer.android.com/training/articles/perf-anr.html#anr 
http://gityuan.com/2016/07/02/android-anr/ 
http://blog.csdn.net/tencent_bugly/article/details/46650675

点击此处阅读原文:https://codezjx.github.io/2017/08/06/anr-trace-analytics/

关于ANR异常捕获与分析,你所需要知道的一切相关推荐

  1. 一文教你轻松搞定ANR异常捕获与分析方法

    简介: 选择一款有超强捕获能力的专业产品,对于开发者定位和修复稳定性问题至关重要.友盟+U-APM SDK集成了UC 内核团队强大的技术及友盟+超强的错误捕获能力,通过数万次捕获实践中积累了丰富经验, ...

  2. 一文教你轻松搞定 ANR 异常捕获与分析方法

    1. ANR 产生原理 关于 ANR 的触发原因,Android 官方开发者文档中 "What Triggers ANR?" 有介绍,如下: Generally, the syst ...

  3. Android 系统(55)---Android App开发之ANR异常的原因分析及处理总结

    Android App开发之ANR异常的原因分析及处理总结 Android App开发之ANR异常的原因分析及处理总结 ANR的全称是application not responding,根据它的意思 ...

  4. xCrash 捕获ANR异常

    ANR异常 android ANR 异常的英文全称是 "Application Not Responding",中文意思即为应用无响应. 在介绍xCrash捕获ANR异常时,先简单 ...

  5. springboot怎么返回404_深度分析:SpringBoot异常捕获与封装处理,看完你学会了吗?...

    简介 日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管.这种方法对于程序员来说处理也比较麻烦, ...

  6. 【厚积薄发系列】C++项目总结14—Windows平台下异常捕获不到问题分析

    问题背景: Windows平台常用的异常捕获函数有时候不起作用,特别是XP系统上,有些runtime error捕获不到,但是其他系统如win7.win8等又能正常捕获,导致产品发出去后遇到这类异常, ...

  7. Android 全局异常捕获DefaultUncaughtExceptionHandler与Cockroach

    前言 Android中虽然可以通过设置 Thread.setDefaultUncaughtExceptionHandler来捕获全局的所有线程的异常,但主线程抛出异常时仍旧会导致activity闪退, ...

  8. atitit.js浏览器环境下的全局异常捕获

    atitit.js浏览器环境下的全局异常捕获 window.onerror = function(errorMessage, scriptURI, lineNumber) { var s= JSON. ...

  9. 如何捕获和分析 JavaScript Error

    前端工程师都知道 JavaScript 有基本的异常处理能力.我们可以 throw new Error(),浏览器也会在我们调用 API 出错时抛出异常.但估计绝大多数前端工程师都没考虑过收集这些异常 ...

最新文章

  1. 创建此对象的程序是quation_MathType出现此对象创建于Equation中的处理教程
  2. C#共享内存实例 附源码
  3. 如何管理多个 SSH 连接
  4. java环境安装之不能安装exe文件
  5. Nginx - request_time和upstream_response_time的区别
  6. Python基础——PyCharm版本——第六章、函数function
  7. django ModuleNotFoundError: No module named 'tinymce***'
  8. MFC多视图与重绘效率
  9. wince flash Android,关于wince下用C#实现flash播放器
  10. (2)WePHP 控制器与使用模板
  11. linux没有c编译器,兄弟们,我这有台电脑里的Linux缺少cc(C编译器),我该怎么把它补上去啊?急啊!!!...
  12. mysql如何找回误删除数据_mysql如何找回误删除数据
  13. python is not defined是什么意思_is not defined 问题?报错
  14. C#,调用GDI32.DLL绘制图形的源程序
  15. OA(二)编写基本的CURD
  16. Kaldi中语言模型
  17. Opencv 提取水平 垂直线,去除杂线,提取对象
  18. 手机android怎么开机画面,安卓开机动画特效软件下载
  19. 定义视频尺寸html,使HTML5视频海报与视频本身尺寸相同
  20. bootStrap输入框鼠标指针样式设定

热门文章

  1. 「C++控制台生存游戏」暗黑体素 DarkVoxel 控制台版
  2. c++简易病毒(附代码)
  3. 曝光一个骗子,大家小心一点,骗子QQ是493169239和707661812,842086828
  4. 示波器合成信号用matlab,用宽带示波器进行雷达信号的矢量分析
  5. 【JoJo的摄影笔记】胶卷的落日——柯达帝国兴衰录
  6. 国家电网公司通用车载监控终端技术解决方案,国网车载终端接入,国网统一车辆管理平台,国网统一车辆管理平台车载终端运行维护服务
  7. 计算机设置定时原理,可编程定时和计数器-微计算机原理-电子发烧友网站
  8. 男性英文名的意思 [2004-05-17 1:45 PM]
  9. 7-2 Say Hello to Integers (20分)
  10. byte的范围为何是-128~127,而不是-127~128?