android日记(七)
上一篇: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(); }
- 一些常用的正则规则
符号 规则 举例 ^ 匹配字符串开始 Pattern.matches("^[0-9].{0,6}", "1s2d3f4");//以数字开头
$ 匹配字符串结尾部 Pattern.matches(".{0,6}[0-9]$", "1q2w3");//以数字结尾
* 匹配前面字符或表达式零次或多次 Pattern.matches("f*q2", "q2");//匹配0个或多个f
+ 匹配前面字符或表达式1次或多次 Pattern.matches("(f1)+q2", "f1f1f1q2");//匹配1个或多个f1
? 匹配前面字符或表达式0次或1次 Pattern.matches("(f1)?q2", "f1q2");//匹配0个或1个f1
{n} 匹配前面字符或表达式正好n次 Pattern.matches("(f1){2}q2", "f1f1q2");//匹配2个f1
{n,} 匹配前面字符或表达式至少n次 Pattern.matches("(f1){2,}q2", "f1f1f1q2");//匹配至少2个f1
{n,m} 匹配前面字符或表达式至少n次至多m次 Pattern.matches("(f1){1,2}q2", "f1f1q2");//匹配至少1个至多2个f1
. 匹配除/r/n以外的任意字符1个 Pattern.matches("^(f1).{1,3}(q2)$", "f1yyyq2");//在f1和q2之间,有任意1-3个字符
x|y 匹配x或y Pattern.matches("foo(d|t)", "food");//匹配food或foot
[xyz] 匹配字符集,[]中的任何一个字符 Pattern.matches("foo[dt]", "foot");//匹配food或foot
[x-y] 匹配字符集范围 Pattern.matches("[a-z,A-Z]*", "aabbCCDD");//匹配任意大小写英文字母组合
[^x-y] 反向字符集 Pattern.matches("[^0-9]", "r");//匹配一个非数字字符
\d 匹配数字 Pattern.matches("(\\d)*", "1223456");//匹配任意数字组合
\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日记(八)