一. 崩溃:
概念:
崩溃率 UV = 发生崩溃的UV / 登录UV
崩溃率小于1/1000为正常,1/10000为优秀
崩溃捕获:
-
Java层捕获:
设置默认的未捕获异常处理器,Thread.setDefaultUncaughtExceptionHandler() -
Native层捕获:
-
常见signal:
SIGABRT 6 : 常见非法UTF8字段
SIGSEGV 11: 非法内存操作 -
信号处理函数:
int sigaction(int, const struct sigaction*, struct sigaction*);
参数:
1.int:表示需要关注的信号量;
2.sigaction:用于声明当某个信号发生的时候,应该如何处理;
3.sigaction:获取旧的信号处理方法。其中sigaction的结构体中:
sa_sigaction:是一个信号处理函数指针,void (* signal_handle)(int code, siginfo_t *si, void *context);
sa_flags:需要处理的信号值 -
棘手问题解决:
- 文件句柄泄漏,导致创建日志文件失败
提前申请文件句柄fd预留 - 栈溢出,导致日志生成失败
使用signalstack,使用方法:申请一块内存空间作为可选的信号处理函数栈使用
使用 sigaltstack 函数通知系统可选的信号处理栈帧的存在及其位置
当使用 sigaction 函数建立一个信号处理函数时,通过指定 SA_ONSTACK 标志通知系统这个信号处理函数应该在可选的栈帧上面执行注册的信号处理函数
- 文件句柄泄漏,导致创建日志文件失败
-
常用第三方:
Breakpad:google比较权威的库
-
获取堆栈信息:
- 获取Java 堆栈:
native崩溃时,通过unwind只能拿到Native堆栈。我们希望可以拿到当时各个线程的Java堆栈- Thread.getAllStackTraces()。
优点:简单,兼容性好。
缺点:
a. 成功率不高,依靠系统接口在极端情况也会失败。
b. 7.0之后这个接口是没有主线程堆栈。
c. 使用Java层的接口需要暂停线程 - hook libart.so。通过hook ThreadList和Thread的函数,获得跟ANR一样的堆栈。为了稳定性,我们会在fork子进程执行。
优点:信息很全,基本跟ANR的日志一样,有native线程状态,锁信息等等。
缺点:黑科技的兼容性问题,失败时可以用Thread.getAllStackTraces()兜底
- Thread.getAllStackTraces()。
二. ANR:
出现ANR的一般有以下几种类型:
-
KeyDispatchTimeout(常见)
input事件在5S内没有处理完成发生了ANR。
logcat日志关键字:Input event dispatching timed out -
BroadcastTimeout
前台Broadcast:onReceiver在10S内没有处理完成发生ANR。
后台Broadcast:onReceiver在60s内没有处理完成发生ANR。
logcat日志关键字:Timeout of broadcast BroadcastRecord -
ServiceTimeout
前台Service:onCreate,onStart,onBind等生命周期在20s内没有处理完成发生ANR。
后台Service:onCreate,onStart,onBind等生命周期在200s内没有处理完成发生ANR
logcat日志关键字:Timeout executing service -
ContentProviderTimeout
ContentProvider 在10S内没有处理完成发生ANR。 logcat日志关键字:timeout publishing content providers
ANR出现的原因
- 主线程频繁进行耗时的IO操作:如数据库读写
- 多线程操作的死锁,主线程被block;
- 主线程被Binder 对端block;
- System Server中WatchDog出现ANR;
- service binder的连接达到上线无法和和System Server通信
- 系统资源已耗尽(管道、CPU、IO)
分析:
当APP不响应、响应慢了、或者WatchDog的监视没有得到回应时,系统就会dump出一个traces.txt文件,存放在文件目录:/data/anr/traces.txt,通过traces文件,我们可以拿到线程名、堆栈信息、线程当前状态、binder call等信息。
-
如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑是应用内一些代码不合理消耗掉了CPU资源,比如死循环或者一些算法库进行大量高精度复杂运算导致CPU长期占用较高,这就要结合trace和ANR前后的log进一步分析了。
-
如果CPU总用量不高,那么很有可能是一些耗时操作或者锁的问题使主线程被阻塞 ,导致ANR。
监听:
- 通过FileObserver监听traces.txt的变化来监听ANR的发生;
- 通过监听信号:由于发生ANR时,ActivityMangerService会发送 SIGQUIT 信号,通过信号监听函数来监听这个信号,然后调用libc的dump()方法打印改应用中所有线程的当前状态,并不强制退出,这些状态通常保存在一个特定的叫做trace的文件中。
三. 内存:
名词解析:
- VSS : Virtual Set Size 虚拟耗用内存(包含共享库占用的内存),即单个进程全部可访问的地址空间,其大小可能包括还尚未在内存中驻留的部分。对于确定单个进程实际内存使用大小,VSS用处不大。
- RSS : Resident Set Size 实际使用物理内存(包含共享库占用的内存),即单个进程实际占用的内存大小,RSS不太准确的地方在于它包括该进程所使用共享库全部内存大小。对于一个共享库,可能被多个进程使用,实际该共享库只会被装入内存一次。
- PSS : Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)PSS相对于RSS计算共享库内存大小是按比例的。N个进程共享,该库对PSS大小的贡献只有1/N。
- USS : Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)即单个进程私有的内存大小,即该进程独占的内存部分。USS揭示了运行一个特定进程在的真实内存增量大小。如果进程终止,USS就是实际被返还给系统的内存大小。
内存信息查看方式:
- 系统剩余内存。关于系统内存状态,可以直接读取文件 /proc/meminfo。当系统可用内存很小(低于 MemTotal 的 10%)时,OOM、大量 GC、系统频繁自杀拉起等问题都非常容易出现。
- 应用使用内存。包括 Java 内存、RSS(Resident Set Size)、PSS(Proportional Set Size),我们可以得出应用本身内存的占用大小和分布。PSS 和 RSS 通过 /proc/self/smap 计算,可以进一步得到例如 apk、dex、so 等更加详细的分类统计。
- 虚拟内存。虚拟内存可以通过 /proc/self/status 得到,通过 /proc/self/maps 文件可以得到具体的分布情况。有时候我们一般不太重视虚拟内存,但是很多类似 OOM、tgkill 等问题都是虚拟内存不足导致的。