与其指望一夜暴富,不如尝试让财富与个人同步成长。否则,生活节奏在一夜之间全部打乱,紧随而来的欲望会让日子愈发失控。
上一篇:android日记(六)
1.在AndroidStudio中运行java应用
- AndroidStudio也能运行Java Application,直接新建任意class,并添加下面的main方法,就可以run了。
2.利用adb工具抓取crash日志
- 手机上发生了一个crash,去哪看crash日志呢?
- adb logcat抓所有日志
- adb logcat --clear,清除所有日志
- adb logcat --buffer=crash,抓取crash日志
- adb logcat --buffer=crash > ./my_crash.rtf,抓取crash日志,并导出到文件。
3.java8双冒号用法
- 遍历list,传统for循环
List<String> list = Arrays.asList("a", "b", "c");
for(String s : list){
Log.d("tag", s);
}
- 在java8引入forEach后,可以接受一个消费者(Consumer)接口,实现遍历,其源码如下:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
其本质是通过for循环,调用consumer实例的accept()方法。实际使用时,只需要定义好相应的consumer接口,遍历操作放在accept()方法体中即可,
List<String> list = Arrays.asList("a", "b", "c");
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String v) {
Log.d("tag", v);
}
};
list.forEach(consumer);
- 上面的写法如果替换为lamaba表达式,
List<String> list = Arrays.asList("a", "b", "c");
Consumer<String> consumer = v -> Log.d("tag", v);
list.forEach(consumer);
更进一步的,省略consumer局部变量,
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(v -> Log.d("tag", v));
- 有时候,循环体里不只一句代码,还是可以像上面一样,用lamada表达式,做一个接口闭包,
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(value -> {
String upper = value.toUpperCase();
Log.d("tag", upper);
});
也可以把循环体写成独立的方法,
private void test(){
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(value -> {
printValue("tag", value);
});
}
public void printValue(String tag, String value) {
String upper = value.toUpperCase();
Log.d(tag, upper);
}
- 当这样写的时候,可以使用双冒号(::)替换lamada,双冒号后面跟的是方法名,注意只有方法名,而不带括号。
private void test(){
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(this::printValue);
}
public void printValue(String value) {
String upper = value.toUpperCase();
Log.d("tag", upper);
}
- 具体来说地,双冒号声明的一个接口的实现方法,lamada表达式的简写形式
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View.OnClickListener onClickListener = this::showViewId;
container.setOnClickListener(onClickListener);
...
}
private void showViewId(View view) {
Log.d("tag", view.getId() + "");
}
或者干脆,
container.setOnClickListener(this::showViewId);
private void showViewId(View view) {
Log.d("tag", view.getId() + "");
}
4.java正则表达式
- java.util.regrex正则包中,提供了Patter类(编译正则规则)和 Matcher类(匹配引擎),Patter.match(regex, content)方法用于执行正则匹配,用regex规则来匹配content字符串,匹配成功则返回true,匹配错误则返回false。
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
- 一些常用的正则规则
符号 | 规则 | 举例 |
^ | 匹配字符串开始 |
|
$ | 匹配字符串结尾部 |
|
* | 匹配前面字符或表达式零次或多次 |
|
+ | 匹配前面字符或表达式1次或多次 |
|
? | 匹配前面字符或表达式0次或1次 |
|
{n} | 匹配前面字符或表达式正好n次 |
|
{n,} | 匹配前面字符或表达式至少n次 |
|
{n,m} | 匹配前面字符或表达式至少n次至多m次 |
|
. | 匹配除/r/n以外的任意字符1个 |
|
x|y | 匹配x或y |
|
[xyz] | 匹配字符集,[]中的任何一个字符 |
|
[x-y] | 匹配字符集范围 |
|
[^x-y] | 反向字符集 |
|
\d | 匹配数字 |
|
\s | 匹配空格 | Pattern.matches("[a-z]*\\s[a-z]*", "hello world");//匹配空格 |
- 正则语法有误时,会抛出PatternSyntaxException异常
class JavaMainClass {
public static void main(String[] args) {
System.out.println("Java正则匹配");
Assert.assertTrue("正则匹配失败", match());
System.out.println("正则匹配成功");
}
private static boolean match() {
return Pattern.matches("\\", "1");//正则语法有误
}
}
Exception in thread "main" java.util.regex.PatternSyntaxException: Unexpected internal error near index 1
\
at java.util.regex.Pattern.error(Pattern.java:1969)
at java.util.regex.Pattern.compile(Pattern.java:1708)
at java.util.regex.Pattern.<init>(Pattern.java:1352)
at java.util.regex.Pattern.compile(Pattern.java:1028)
at java.util.regex.Pattern.matches(Pattern.java:1133)
at com.example.kotlinrichtext.JavaMainClass.match(JavaMainClass.java:20)
at com.example.kotlinrichtext.JavaMainClass.main(JavaMainClass.java:15)
5.关于java.lang.NullPointerException: Attempt to read from field 'int android.view.View.mViewFlags' on null object refrence
- 当remove一个动画view时,遭遇了java.lang.NullPointerException: Attemp to read from field 'int android.view.View.mViewFlags' on a null object refrence
java.lang.NullPointerException: Attempt to read from field 'int android.view.View.mViewFlags' on a null object reference
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4204)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:20474)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.View.draw(View.java:21350)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4446)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4205)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:20474)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.View.draw(View.java:21350)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4446)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4205)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.View.draw(View.java:21621)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at com.android.internal.policy.DecorView.draw(DecorView.java:964)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:20483)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:584)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:590)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:668)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewRootImpl.draw(ViewRootImpl.java:3840)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3648)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2954)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1857)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8089)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1057)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.Choreographer.doCallbacks(Choreographer.java:875)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:776)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1042)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:888)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:100)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.os.Looper.loop(Looper.java:213)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8178)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
12-16 13:51:57.150 32599 32599 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)
- 顺着日志,进入ViewGroup.dispatchDraw()源码,
@Override
protected void dispatchDraw(Canvas canvas) {
...省略若干代码.....
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
...省略若干代码.....
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
//这里发生了空指针异常,child为null
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...省略若干代码.....
}
- 重点是下面这几句,报的就是这里的child.mViewFlags时,child为空了。child是从getAndVerifyPreorderedView()中根据传入的childIndex取到的,而childIndex实际上就是i。
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)
- 很显然,是遍历preorderedList时,index超出了,导致取出的child为空。那么index为什么会超出呢?根据for循环的条件,index的取值范围为0 ~ childerenCount-1。那childrenCount和preorderedList分别从哪来
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
ArrayList<View> preorderedList = buildOrderedChildList();
ArrayList<View> buildOrderedChildList() {
final int childrenCount = mChildrenCount;
if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
// callers should clear, so clear shouldn't be necessary, but for safety...
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// add next child (in child order) to end of list
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
final float currentZ = nextChild.getZ();
// insert ahead of any Views with greater Z
int insertIndex = i;
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
可以看出,mChildrenCount = mChildren.size; preorderedList = mChildren。
- 于是乎,之所以会出现遍历时child为空的可能是,mChildren在对mChildrenCount赋值之后,自身又被remove掉了元素。正是因为在view的dispatchDraw()期间,触发了removeView()导致。
- 因此,解决办法就是,当dispatchDraw()所在的显示view的loop消息中,携带了removeView()的操作。可以将removeView()的操作放在下一个loop消息中,即可解决问题,
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
removeView(mView);
}
});
或者,
rootView.post(new Runnable() {
@Override
public void run() {
removeView(mView);
}
});
6.使用adb导出anr日志
- 发生anr的主要情况包括:activity事件无法在5s内响应;service无法在20s内处理完;boardcast无法在10s内处理完;
- 当发生anr时,AMS会通知ProcessRecord进行处理。
boolean inputDispatchingTimedOut(ProcessRecord proc, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem, String reason) {
...
proc.appNotResponding(activityShortComponentName, aInfo,
parentShortComponentName, parentProcess, aboveSystem, annotation);
}
return true;
}
ProcessRecord中对应源码如下:
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation) {
ArrayList<Integer> firstPids = new ArrayList<>(5);
SparseArray<Boolean> lastPids = new SparseArray<>(20);
...
synchronized (mService) {
...
// 组装anr信息.
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(processName);
if (activityShortComponentName != null) {
info.append(" (").append(activityShortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parentShortComponentName != null
&& parentShortComponentName.equals(activityShortComponentName)) {
info.append("Parent: ").append(parentShortComponentName).append("\n");
}
...
info.append(processCpuTracker.printCurrentState(anrTime));
//打印ANR日志
Slog.e(TAG, info.toString());
//保存trace文件
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
(isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids,nativePids);
...
//显示anr提示框
synchronized (mService) {
...
// mUiHandler can be null if the AMS is constructed with injector only. This will only
// happen in tests.
if (mService.mUiHandler != null) {
// Bring up the infamous App Not Responding dialog
Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);
mService.mUiHandler.sendMessage(msg);
}
}
}
可见,ProcessRecord对于ANR主要做了:组装ANR信息并打印日志,保存TRACE文件,显示系统的ANR提示弹窗。
- 当anr发生时,可以用关键字“ANR in ”搜索日志,
2020-12-10 15:30:36.713 1637-1791/? E/ActivityManager: ANR in com.my.packageName:myProcess
PID: 31660
Reason: executing service com.my.packageName/im.lib.IMService
Load: 50.3 / 47.09 / 32.83
CPU usage from 138636ms to 0ms ago (2020-12-10 15:28:17.299 to 2020-12-10 15:30:35.935):
24% 1637/system_server: 17% user + 6.9% kernel / faults: 176346 minor 9 major
10% 782/surfaceflinger: 5.9% user + 4.7% kernel / faults: 4492 minor
9.8% 21989/com.huawei.recsys: 8.1% user + 1.6% kernel / faults: 22830 minor
8.3% 743/android.hardware.graphics.composer@2.2-service: 3.4% user + 4.9% kernel / faults: 213 minor
6.5% 820/aptouch_daemon: 5.9% user + 0.5% kernel / faults: 44 minor
6.4% 512/logd: 2.9% user + 3.4% kernel / faults: 120 minor
5.2% 4295/adbd: 1.1% user + 4.1% kernel / faults: 6878 minor
3.7% 2538/com.android.systemui: 2.6% user + 1.1% kernel / faults: 18172 minor 23 major
3.6% 761/vendor.huawei.hardware.hwdisplay.displayengine@1.2-service: 2.4% user + 1.2% kernel
3.5% 2935/com.huawei.android.launcher: 2.3% user + 1.1% kernel / faults: 26764 minor
2.3% 1447/hisi_hcc: 0% user + 2.3% kernel
2.2% 7862/com.my.packageName:pushsdk: 1.8% user + 0.3% kernel / faults: 25252 minor
2.1% 1060/hwpged: 0.6% user + 1.4% kernel / faults: 1096 minor
2% 264/kworker/u16:4: 0% user + 2% kernel / faults: 2 minor
1.9% 806/kworker/u16:8: 0% user + 1.9% kernel / faults: 1 minor
1.3% 2748/com.huawei.hwid.core: 1% user + 0.3% kernel / faults: 17638 minor
1.3% 261/kworker/u16:2: 0% user + 1.3% kernel / faults: 1 minor
1.3% 322/irq/213-thp: 0% user + 1.3% kernel
1.3% 2902/com.huawei.iaware: 0.8% user + 0.5% kernel / faults: 5423 minor
1.3% 265/kworker/u16:5: 0% user + 1.3% kernel
1.2% 775/ashmemd: 0.4% user + 0.8% kernel
1.2% 1453/hisi_rxdata: 0% user + 1.2% kernel
1.1% 1031/displayengineserver: 0.4% user + 0.6% kernel
1.1% 1080/hiview: 0.4% user + 0.7% kernel / faults: 1458 minor
1% 1055/kworker/u16:9: 0% user + 1% kernel / faults: 1 minor
1% 1033/dubaid: 0.3% user + 0.6% kernel / faults: 2958 minor
1% 783/powerlogd: 0.6% user + 0.3% kernel / faults: 52 minor
0.9% 1271/pci_rx_hi_task: 0% user + 0.9% kernel
0.9% 94/spi3: 0% user + 0.9% kernel
0.9% 4289/sugov:0: 0% user + 0.9% kernel
0.9% 687/netd: 0.2% user + 0.6% kernel / faults: 4958 minor
0.9% 5260/chargelogcat-c: 0% user + 0.9% kernel / faults: 23 minor
0.8% 22225/com.huawei.lbs: 0.4% user + 0.3% kernel / faults: 6819 minor
0.7% 781/lmkd: 0% user + 0.7% kernel
0.6% 5376/com.huawei.hiai.engineservice: 0.4% user + 0.2% kernel / faults: 11848 minor
0.6% 513/servicemanager: 0.2% user + 0.3% kernel
0.5% 4292/sugov:6: 0% user + 0.5% kernel
0.5% 9/rcu_preempt: 0% user + 0.5% kernel
0.5% 2923/com.android.phone: 0.3% user + 0.2% kernel / faults: 1531 minor
0.5% 2785/com.huawei.HwOPServer: 0.3% user + 0.1% kernel / faults: 3009 minor 1 major
0.5% 2867/com.huawei.systemserver: 0.3% user + 0.2% kernel / faults: 1203 minor
0.5% 4291/sugov:4: 0% user + 0.5% kernel
0.5% 1873/hignss_1103: 0.3% user + 0.2% kernel / faults: 1332 minor
0.5% 325/hw_kstate: 0% user + 0.5% kernel
0.5% 1027/AGPService: 0.3% user + 0.2% kernel / faults: 1 minor
0.4% 3077/kworker/u17:2: 0% user + 0.4% kernel
0.1% 6713/com.baidu.input_huawei: 0% user + 0% kernel / faults: 1222 minor
0.4% 2810/com.huawei.hiview: 0.2% user + 0.1% kernel / faults: 1207 minor
0.3% 127/kworker/u17:0: 0% user + 0.3% kernel
0.3% 1/init: 0.2% user + 0% kernel / faults: 38 minor
0% 689/zygote: 0% user + 0% kernel / faults: 7344 minor
0.3% 3056/com.huawei.systemmanager:service: 0.2% user + 0.1% kernel / faults: 1177 minor 1 major
0.3% 3108/com.huawei.hwid.persistent: 0.2% user + 0% kernel / faults: 2291 minor
0% 772/vendor.huawei.hardware.perfgenius@2.0-service: 0% user + 0% kernel / faults: 2 minor
0.3% 4630/com.huawei.skytone:service: 0.2% user + 0% kernel / faults: 6026 minor
0.2% 8/ksoftirqd/0: 0% user + 0.2% kernel
0.2% 4431/com.huawei.skytone: 0.2% user + 0% kernel / faults: 1781 minor
0.2% 269/kworker/0:1H: 0% user + 0.2% kernel
0.2% 1047/statsd: 0.1% use
这个日志里指明了发生anr的进程和原因,以及发生anr前的一段时间内cpu的使用情况。
- 接下来重点是去查看对应时间的trace文件,找寻导致anr的真正某后凶手,那trace文件在哪里呢。查看保存trace文件的方法dumpStackTraces(),可以看到,文件放在/data/anr路径下。
public static File dumpStackTraces(ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
...
final File tracesDir = new File(ANR_TRACE_DIR);
maybePruneOldTraces(tracesDir);
File tracesFile = createAnrDumpFile(tracesDir);
if (tracesFile == null) {
return null;
}
dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids);
return tracesFile;
}
public static final String ANR_TRACE_DIR = "/data/anr";
- 这时候,adb命令就真正登场了,进入/data/anr目录,执行ls,一一列出anr文件,文件名是以anr_开头,并拼接当前时间字符串。
my@myMacBook-Pro% adb shell
HWLIO:/ $ cd data/anr
HWLIO:/data/anr $ ls
anr_2020-12-10-14-30-37-769 anr_2020-12-10-14-37-50-857 anr_2020-12-10-15-03-50-085 anr_2020-12-10-15-18-50-142 anr_2020-12-10-15-22-02-679 anr_2020-12-11-11-05-41-093 anr_2020-12-11-14-13-32-742
anr_2020-12-10-14-31-01-270 anr_2020-12-10-15-00-27-194 anr_2020-12-10-15-14-42-962 anr_2020-12-10-15-19-45-901 anr_2020-12-10-15-30-36-566 anr_2020-12-11-11-42-33-396 dumptrace_LcYlVb
HWLIO:/data/anr $ %
这时候,执行adb pull,提取trace文件
adb pull /data/anr
不料,Permission Denied了,因为手机没有root。
my@myMacBook-Pro % adb pull data/anr
adb: error: failed to copy 'data/anr/anr_2020-12-10-15-00-27-194' to './anr/anr_2020-12-10-15-00-27-194': remote open failed: Permission denied
- 别慌,还有大招。执行adb report命令,会将data/anr文件夹打包成一个zip文件,并dump到设备存储的bugreports文件夹下。
my@myMacBook-Pro % adb bugreport
[ 85%] generating bugreport-LIO-AL00-HUAWEILIO-AL00-2020-12-11-17-24-03.zip
通过androidstudio打开Device File Explorer,就可以到bugreports中获取trace文件了。
- 分析trace,可以看到发生anr时,主线程在执行什么,从而帮助找出凶手。
suspend all histogram: Sum: 1.105ms 99% C.I. 2us-620.160us Avg: 61.388us Max: 664us
DALVIK THREADS (58):
"Signal Catcher" daemon prio=5 tid=7 Runnable
| group="system" sCount=0 dsCount=0 flags=0 obj=0x12d406e0 self=0xd8d2f600
| sysTid=31671 nice=0 cgrp=default sched=0/0 handle=0xdd781230
| state=R schedstat=( 156390111 1957293 70 ) utm=10 stm=5 core=7 HZ=100
| stack=0xdd686000-0xdd688000 stackSize=1008KB
| held mutexes= "mutator lock"(shared held)
native: #00 pc 0030b027 /apex/com.android.runtime/lib/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+78)
native: #01 pc 003c8b2d /apex/com.android.runtime/lib/libart.so (art::Thread::DumpStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool, BacktraceMap*, bool) const+360)
native: #02 pc 003c52ef /apex/com.android.runtime/lib/libart.so (art::Thread::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool, BacktraceMap*, bool) const+34)
native: #03 pc 003de031 /apex/com.android.runtime/lib/libart.so (art::DumpCheckpoint::Run(art::Thread*)+600)
native: #04 pc 003d8951 /apex/com.android.runtime/lib/libart.so (art::ThreadList::RunCheckpoint(art::Closure*, art::Closure*)+296)
native: #05 pc 003d8073 /apex/com.android.runtime/lib/libart.so (art::ThreadList::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool)+766)
native: #06 pc 003d7c93 /apex/com.android.runtime/lib/libart.so (art::ThreadList::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char>>&)+634)
native: #07 pc 0039fc57 /apex/com.android.runtime/lib/libart.so (art::Runtime::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char>>&)+130)
native: #08 pc 003aecc3 /apex/com.android.runtime/lib/libart.so (art::SignalCatcher::HandleSigQuit()+1038)
native: #09 pc 003ae11f /apex/com.android.runtime/lib/libart.so (art::SignalCatcher::Run(void*)+230)
native: #10 pc 0009c557 /apex/com.android.runtime/lib/bionic/libc.so (__pthread_start(void*)+20)
native: #11 pc 00055a07 /apex/com.android.runtime/lib/bionic/libc.so (__start_thread+30)
(no managed stack frames)
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x73246460 self=0xf1af9e00
| sysTid=31660 nice=-10 cgrp=default sched=1073741825/2 handle=0xf1f71fc0
| state=S schedstat=( 110079676 8794270 391 ) utm=7 stm=3 core=5 HZ=100
| stack=0xff21b000-0xff21d000 stackSize=8192KB
| held mutexes=
kernel: (couldn't read /proc/self/task/31660/stack)
native: #00 pc 0004eef0 /apex/com.android.runtime/lib/bionic/libc.so (syscall+28)
native: #01 pc 00054db9 /apex/com.android.runtime/lib/bionic/libc.so (__futex_wait_ex(void volatile*, bool, int, bool, timespec const*)+88)
native: #02 pc 0009bc81 /apex/com.android.runtime/lib/bionic/libc.so (pthread_cond_wait+32)
native: #03 pc 0153094d /system/product/app/HwWebview/HwWebview.apk!libwebviewchromium.so (offset 512a000) (???)
native: #04 pc 01530d45 /system/product/app/HwWebview/HwWebview.apk!libwebviewchromium.so (offset 512a000) (???)
native: #05 pc 01530bfd /system/product/app/HwWebview/HwWebview.apk!libwebviewchromium.so (offset 512a000) (???)
native: #06 pc 008ae5a7 /system/product/app/HwWebview/HwWebview.apk!libwebviewchromium.so (offset 512a000) (???)
native: #07 pc 008ae1e7 /system/product/app/HwWebview/HwWebview.apk!libwebviewchromium.so (offset 512a000) (Java_J_N_MNbaakJs+62)
native: #08 pc 000ba453 /data/dalvik-cache/arm/system@product@app@HwWebview@HwWebview.apk@classes.dex (art_jni_trampoline+138)
native: #09 pc 001cba65 /data/dalvik-cache/arm/system@product@app@HwWebview@HwWebview.apk@classes.dex (v2.setAcceptFileSchemeCookiesImpl+68)
native: #10 pc 000e1bc5 /apex/com.android.runtime/lib/libart.so (art_quick_invoke_stub_internal+68)
native: #11 pc 00450c8b /apex/com.android.runtime/lib/libart.so (art_quick_invoke_stub+250)
native: #12 pc 000e9ff5 /apex/com.android.runtime/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+160)
native: #13 pc 0021b7f3 /apex/com.android.runtime/lib/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+274)
native: #14 pc 0021795b /apex/com.android.runtime/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+802)
native: #15 pc 00445e41 /apex/com.android.runtime/lib/libart.so (MterpInvokeVirtual+584)
native: #16 pc 000dc814 /apex/com.android.runtime/lib/libart.so (mterp_op_invoke_virtual+20)
native: #17 pc 002823e4 /system/framework/framework.jar (android.webkit.CookieManager.setAcceptFileSchemeCookies+8)
native: #18 pc 004486c1 /apex/com.android.runtime/lib/libart.so (MterpInvokeStatic+932)
native: #19 pc 000dc994 /apex/com.android.runtime/lib/libart.so (mterp_op_invoke_static+20)
native: #20 pc 008fee0c /data/app/com.my.packageName-zCbnGV02F-N4dB7_zu5TJw==/oat/arm/base.vdex (com.my.packageName.util.CookieUtils.<clinit>+24)
native: #21 pc 001f7afd /apex/com.android.runtime/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.17840058838137626416+268)
native: #22 pc 001fc2b9 /apex/com.android.runtime/lib/libart.so (art::interpreter::EnterInterpreterFromEntryPoint(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*)+120)
native: #23 pc 0043a17d /apex/com.android.runtime/lib/libart.so (artQuickToInterpreterBridge+832)
native: #24 pc 000e65a1 /apex/com.android.runtime/lib/libart.so (art_quick_to_interpreter_bridge+32)
native: #25 pc 000e1bc5 /apex/com.android.runtime/lib/libart.so (art_quick_invoke_stub_internal+68)
native: #26 pc 00450d9f /apex/com.android.runtime/lib/libart.so (art_quick_invoke_static_stub+246)
native: #27 pc 000ea009 /apex/com.android.runtime/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+180)
native: #28 pc 0010aac9 /apex/com.android.runtime/lib/libart.so (art::ClassLinker::InitializeClass(art::Thread*, art::Handle<art::mirror::Class>, bool, bool)+1864)
native: #29 pc 000faf87 /apex/com.android.runtime/lib/libart.so (art::ClassLinker::EnsureInitialized(art::Thread*, art::Handle<art::mirror::Class>, bool, bool)+58)
native: #30 pc 0043cbfb /apex/com.android.runtime/lib/libart.so (artQuickResolutionTrampoline+2186)
native: #31 pc 000e64a1 /apex/com.android.runtime/lib/libart.so (art_quick_resolution_trampoline+32)
at J.N.MNbaakJs(Native method)
at v2.setAcceptFileSchemeCookiesImpl(PG:3)
at android.webkit.CookieManager.setAcceptFileSchemeCookies(CookieManager.java:274)
at com.my.packageName.util.CookieUtils.<clinit>(SourceFile:24)
at com.my.packageName.config.LoginConfig.checkLoginStatus(SourceFile:32)
at com.my.packageName.config.LoginConfig.isLogined(SourceFile:22)
at com.my.packageName.ad.ADSDKUtils$Companion.init(SourceFile:67)
at com.my.packageName.ad.ADSDKUtils.init(SourceFile:-1)
at com.my.packageName.launch.MainApplication.onCreate(SourceFile:164)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1202)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7375)
at android.app.ActivityThread.access$2400(ActivityThread.java:251)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2280)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8375)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
7.异常捕获UncaughtExceptionHandler
- RuntimeInit中有两个内部类,LoggingHandler和KillApplicationHandler,它们都实现了Thread.UncaughtExceptionHandler。
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;
@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;
// Don't re-enter if KillApplicationHandler has already run
if (mCrashing) return;
// mApplicationObject is null for non-zygote java programs (e.g. "am")
// There are also apps running with the system UID. We don't want the
// first clause in either of these two cases, only for system_server.
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
StringBuilder message = new StringBuilder();
// The "FATAL EXCEPTION" string is still used on Android even though
// apps can set a custom UncaughtExceptionHandler that renders uncaught
// exceptions non-fatal.
//打印日志,关键字FATAL EXCEPTION
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);
}
}
}
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;
/**
* Create a new KillApplicationHandler that follows the given LoggingHandler.
* If {@link #uncaughtException(Thread, Throwable) uncaughtException} is called
* on the created instance without {@code loggingHandler} having been triggered,
* {@link LoggingHandler#uncaughtException(Thread, Throwable)
* loggingHandler.uncaughtException} will be called first.
*
* @param loggingHandler the {@link LoggingHandler} expected to have run before
* this instance's {@link #uncaughtException(Thread, Throwable) uncaughtException}
* is being called.
*/
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
ensureLogging(t, e);
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;
// Try to end profiling. If a profiler is running at this point, and we kill the
// process (below), the in-memory buffer will be lost. So try to stop, which will
// flush the buffer. (This makes method trace profiling useful to debug crashes.)
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// Bring up crash dialog, wait for it to be dismissed
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
// Try everything to make sure this process goes away.
//杀死进程
Process.killProcess(Process.myPid());
System.exit(10);
}
}
/**
* Ensures that the logging handler has been triggered.
*
* See b/73380984. This reinstates the pre-O behavior of
*
* {@code thread.getUncaughtExceptionHandler().uncaughtException(thread, e);}
*
* logging the exception (in addition to killing the app). This behavior
* was never documented / guaranteed but helps in diagnostics of apps
* using the pattern.
*
* If this KillApplicationHandler is invoked the "regular" way (by
* {@link Thread#dispatchUncaughtException(Throwable)
* Thread.dispatchUncaughtException} in case of an uncaught exception)
* then the pre-handler (expected to be {@link #mLoggingHandler}) will already
* have run. Otherwise, we manually invoke it here.
*/
private void ensureLogging(Thread t, Throwable e) {
if (!mLoggingHandler.mTriggered) {
try {
mLoggingHandler.uncaughtException(t, e);
} catch (Throwable loggingThrowable) {
// Ignored.
}
}
}
}
其中,LoggingHandler负责在发生exception时,打印crash日志;KillApplicationHandler负责在发生exception时杀死进程,当杀死的进程是主进程时,也就发生了crash。
- 在RuntimeInit.commonInit()中,分别通过Thread.setUncaughtExceptionPreHandler()和Thread.setUncaughtExceptionHandler()为LoggingHandler和KillApplicationHandler注册。
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
LoggingHandler loggingHandler = new LoggingHandler();
RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
...
}
- Thread.setCaughtExceptionPreHandler()覆盖所有线程,会在回调DefaultExceptionHandler之前调用;
- Thread.setCaughtExceptionHandler()同样回覆盖所有线程,可以在应用层被重复调用,并且每一次调用后,都会覆盖上一次设置的DefaultUncaughtExceptionHandler;
- Thread.currentThread.setUncaughtExceptionHandler(),只可以覆盖当前线程的异常。如果某个线程存在自定义的UncaughtExceptionHandler,则回调时,会忽略全局的DefaultUncaughtHandler。
8.能否通过UncaughtExceptionHandler来捕获异常避免crash
- 是否可以自定义UncaughtExceptionHandler,在拦截异常后,不执行杀进程处理,从而避免发生crash呢?
- just like this,在MainApplication中设置自定义的UncaughtExceptionHandler,覆盖KillApplicationHandler,当异常发生时,仅做打印日志的处理,不执行Process.killProcess()。
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
Log.e(tag, "${Thread.currentThread.name} 捕获到异常:" + e.message)
}
}
}
- 试试就知道了,首先在主线程中,人为制造一条Exception,结果发现:设置的DefaultUncaughtExceptionHandler成功捕获到了异常信息,但是,尽管没有执行Process.killProcess(),程序依然停止运行了。
private fun makeMainThreadCrash() {
val array = mutableListOf("1", "2", "3")
Log.d(tag, array[3])
}
原因在于,进程虽然没有杀,但是主线程由于遭遇异常后无法继续存活了,从而app进入了ANR状态。实际上,很多厂商在处理这种情况时,系统也会直接杀掉进程退出app。
- 其次,如果是在子线程中发生异常,又会怎样呢?下面方法在子线程制造一条异常,此时MainApplication中的DefaultUncaughtExceptionHandler仍然捕获到了信息,但与主线程异常不一样,此后app还可以继续正常运行,没有发生crash。这是因为,异常发生在子线程,只是导致子线程挂了,而主线程依然存活。
private fun makeSubThreadCrash() {
try {
Thread {
val array = arrayOf(1, 2, 3)
Log.d(tag, array[3].toString())
}.start()
} catch (e: Exception) {
Log.e(tag, "catch exception: " + e.message)
}
}
- 通过打印线程信息,可以验证上面的结论,
private fun dumpAllThreadsInfo() {
val threadSet = Thread.getAllStackTraces().keys
for (thread in threadSet) {
Log.d(
tag, "dumpAllThreadsInfo thread.name = " + thread.name
+ ";thread.state = " + thread.state
+ ";thread.isAlive = " + thread.isAlive
+ ";thread.isDaemon = " + thread.isDaemon
+ ";group = " + thread.threadGroup
)
}
}
对应的调用代码
gifImageView.setOnClickListener {
Log.d(tag, "ready to make crash")
makeSubThreadCrash()
Handler().postDelayed({
Log.d(tag,"等3秒后,再次dump Threads info")
dumpAllThreadsInfo()
}, 3000)
}
得到日志如下:
2020-12-14 19:17:46.954 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: ready to make crash
2020-12-14 19:17:46.963 28310-28511/com.example.nevercrashapp E/NEVER_CRASH: Thread-3 捕获到异常:length=3; index=3
2020-12-14 19:17:46.966 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = FinalizerWatchdogDaemon;thread.state = TIMED_WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:46.966 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = HeapTaskDaemon;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:46.966 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = main;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.966 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = queued-work-looper-schedule-handler;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.966 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Signal Catcher;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = ReferenceQueueDaemon;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Profile Saver;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Jit thread pool worker thread 0;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = RenderThread;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_5;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_4;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Thread-3;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = AppEyeUiProbeThread;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = FinalizerDaemon;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_2;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.967 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_1;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.968 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_3;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:46.968 28310-28511/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = queued-work-looper;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.956 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: 等3秒后,再次dump Threads info
2020-12-14 19:17:49.959 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = FinalizerWatchdogDaemon;thread.state = TIMED_WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:49.959 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = HeapTaskDaemon;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:49.960 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = main;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.960 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = queued-work-looper-schedule-handler;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.960 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Signal Catcher;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:49.961 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = ReferenceQueueDaemon;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:49.961 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Profile Saver;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:49.961 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Jit thread pool worker thread 0;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.962 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = RenderThread;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.962 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_5;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.963 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_4;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.963 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = AppEyeUiProbeThread;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.963 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = FinalizerDaemon;thread.state = WAITING;thread.isAlive = true;thread.isDaemon = true;group = java.lang.ThreadGroup[name=system,maxpri=10]
2020-12-14 19:17:49.964 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_2;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.964 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_1;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.964 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = Binder:28310_3;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
2020-12-14 19:17:49.965 28310-28310/com.example.nevercrashapp D/NEVER_CRASH: dumpAllThreadsInfo thread.name = queued-work-looper;thread.state = RUNNABLE;thread.isAlive = true;thread.isDaemon = false;group = java.lang.ThreadGroup[name=main,maxpri=10]
- 分析上面的日志,一开始线程Thread-3中发生了异常,DafaultUncaughtExceptionHandler捕获到异常并打印异常信息和线程栈,此时线程栈中的Thread-3正在打印日志,还处于存活状态(isAlive=true)。此后,主线程中的3秒后的异步任务再次dumpAllThreadsInfo(),此时的线程栈中已找不到Thread-3了,表明线程Thread-3此时已经销毁,并且此时main线程仍然存活,app可以正常运行。
- 结论:使用UncaughtExceptionHandler可以在避免因子线程发生异常导致的crash,却无法避免主线程中发生异常导致的crash。于是,可以对有crash风险的子线程,Thread.currentThread().setUncaughtExceptionHandler(),来捕获当前子线程的异常,避免发生crash。
9.crash防护手段
- 基于全局异常捕获处理,捕获到异常后,判断是不是主线程,如果是主线程则执行杀进程,而子线程则不杀进程。此方案可以避免进程中的全部子线程发生异常导致的crash,但不能避免主线程crash。
Thread.setDefaultUncaughtExceptionHandler { _, e ->
Log.e(tag, "${Thread.currentThread().name} 捕获到异常:${e.message}")
dumpAllThreadsInfo()
if(Looper.getMainLooper() == Looper.myLooper()){
Process.killProcess(Process.myPid())
exitProcess(10)
}
}
- 基于Handler事件机制的全局try-catch防护,由于整个android系统是基于消息驱动的,Looper内部是一个死循环,不断地从MessageQueue中取出消息,由消息来通知具体要做什么任务。如果对这个死循环做一个try-catch处理,便可以拦截当前Looper的全部异常,如果保护的是MainLooper,那就意味着拦截了主线程了异常。
- 话不多说,看代码,向MainLooper中提交一个Handler消息,内部是一个死循环,循环体内的操作进行try-catch保护,那么主线程的异常都可以被拦截掉了。
class MainApplication : Application() {
private val tag = "NEVER_CRASH"
override fun onCreate() {
super.onCreate()
openCrashProtected()
}
private fun openCrashProtected() {
Log.d(tag, "openCrashProtected")
Handler(Looper.getMainLooper()).post {
while (true) {
try {
Looper.loop()
Log.d(tag, "main looper execute loop")
} catch (e: Throwable) {
Log.e(tag, "catch exception: " + e.message)
}
}
}
}
}
- try-catch拦截MainLooper只是一种理论上的crash防护手段,直接套用在工程上没有应用意义,因为主线程的异常被拦截后,虽然没有crash,但很可能也阻断了用户的交互。
10.替换if-else
- 不想写大量的if-else怎么办,比如要做一个加减乘除的运算,传统的使用if-else,
public static int handleCalculate(String type, int a, int b) {
if (type.equals("ADD")) {
return a + b;
} else if (type.equals("SUB")) {
return a - b;
} else if (type.equals("MUL")) {
return a * b;
} else if (type.equals("DIV")) {
return a / b;
} else {
return 0;
}
}
- 使用Map替换策略模式中的if-else,设计抽象类,
interface IOperate {
int apply(int a, int b);
}
工厂类,
class OperatorFactory {
static Map<String, IOperate> operateMap = new HashMap<>();
static {
operateMap.put("ADD", new AddOperate());
operateMap.put("SUB", new SubOperate());
operateMap.put("MUL", new MulOperate());
operateMap.put("DIV", new DivOperate());
}
private static class AddOperate implements IOperate {
@Override
public int apply(int a, int b) {
return a + b;
}
}
private static class SubOperate implements IOperate {
@Override
public int apply(int a, int b) {
return a - b;
}
}
private static class MulOperate implements IOperate {
@Override
public int apply(int a, int b) {
return a * b;
}
}
private static class DivOperate implements IOperate {
@Override
public int apply(int a, int b) {
return a / b;
}
}
}
调用工厂方法,
public static int handleCalculateByFactory(String type, int a, int b) {
return OperatorFactory.operateMap.get(type).apply(a, b);
}
- 使用枚举替换if-else,
enum OperateEnum implements IOperate {
ADD {
@Override
public int apply(int a, int b) {
return a + b;
}
}, SUB {
@Override
public int apply(int a, int b) {
return a - b;
}
}, MUL {
@Override
public int apply(int a, int b) {
return a * b;
}
}, DIV {
@Override
public int apply(int a, int b) {
return a / b;
}
}
}
调用枚举方法,
public static int handleCalculateByEnum(String type, int a, int b) {
return OperateEnum.valueOf(type).apply(a, b);
}
下一篇:android日记(八)