与其指望一夜暴富,不如尝试让财富与个人同步成长。否则,生活节奏在一夜之间全部打乱,紧随而来的欲望会让日子愈发失控。

上一篇:android日记(六)

1.在AndroidStudio中运行java应用

  • AndroidStudio也能运行Java Application,直接新建任意class,并添加下面的main方法,就可以run了。

android studio 崩溃日志 android crash日志_java

2.利用adb工具抓取crash日志

  • 手机上发生了一个crash,去哪看crash日志呢?
  • adb logcat抓所有日志
  • adb logcat --clear,清除所有日志
  • adb logcat --buffer=crash,抓取crash日志

android studio 崩溃日志 android crash日志_android studio 崩溃日志_02

  • adb logcat --buffer=crash > ./my_crash.rtf,抓取crash日志,并导出到文件。

android studio 崩溃日志 android crash日志_List_03

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

android studio 崩溃日志 android crash日志_android studio 崩溃日志_04


 通过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日记(八)