Android 面试题 异常捕获 四

这篇具有很好参考价值的文章主要介绍了Android 面试题 异常捕获 四。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🔥 为什么要捕获奔溃 🔥

因为在开发或者测试阶段不能做到100%的问题解决,因为 app 上线之后会有你想不到的各种各样的使用的场景,而发生问题时用户只能描述一下怎么怎么怎么就出现了问题。也许反馈到开发这边可以100%复现那就可以得到解决,但是也有可能在本地复现不了,只有在用户的手机上可以出现,这可能和用户使用的场景(温度太高导致CPU限速,温度太低等),手机的内存,CPU,老年机等等都有关系。

在 android 里面,奔溃可以分为二大类,一个为 java 奔溃,一个为 native 奔溃。对于这二种奔溃需要用不同的方式去捕获。

一个合格的异常捕获组件也要能达到以下目的:

A : 支持在crash时进行更多扩展操作, 例如 :

        1、打印logcat和应用日志

        2、上报crash次数

        3、对不同的crash做不同的恢复措施

B : 可以针对业务不断改进和适应

🔥 Java异常分类 🔥 

可查的异常(checked exceptions)

编译器要求必须处置的异常(使用 try…catch…finally 或者 throws )。在方法中要么用try-catch语句捕获它并处理,要么用 throws 子句声明抛出它,否则编译不会通过。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。

不可查的异常(unchecked exceptions)

包括运行时异常(RuntimeException与其子类)和错误(Error)。在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。

🔥 收集Java层Crash 🔥

在 Applicaiton中进行初始化崩溃收集器

 

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //初始化崩溃收集器
        CollectCrashUtils.initColleteCrash();
    }
}

收集java层崩溃和native层崩溃 

public class CollectCrashUtils {
    public static void initColleteCrash() {
        //初始化Handler,收集java层崩溃
        MyJavaCrashHandler handler = new MyJavaCrashHandler();
        Thread.setDefaultUncaughtExceptionHandler(handler);

        //收集native层崩溃
        File file = new File("sdcard/Crashlog");
        if (!file.exists()) {
            file.mkdirs();
        }
        NativeBreakpad.init(file.getAbsolutePath());
    }
}

native层的崩溃收集可以使用编译好的breakpad.so 

java层崩溃实现Thread.UncaughtExceptionHandler接口进行收集

 

public class MyJavaCrashHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Log.e("程序出现异常了", "Thread = " + t.getName() + "\nThrowable = " + e.getMessage());
        String stackTraceInfo = getStackTraceInfo(e);
        Log.e("stackTraceInfo", stackTraceInfo);
        saveThrowableMessage(stackTraceInfo);
    }

    /**
     * 获取错误的信息
     *
     * @param throwable
     * @return
     */
    private String getStackTraceInfo(final Throwable throwable) {
        PrintWriter pw = null;
        Writer writer = new StringWriter();
        try {
            pw = new PrintWriter(writer);
            throwable.printStackTrace(pw);
        } catch (Exception e) {
            return "";
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
        return writer.toString();
    }

    private String logFilePath = "sdcard/Crashlog";

    private void saveThrowableMessage(String errorMessage) {
        if (TextUtils.isEmpty(errorMessage)) {
            return;
        }
        File file = new File(logFilePath);
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
            if (mkdirs) {
                writeStringToFile(errorMessage, file);
            }
        } else {
            writeStringToFile(errorMessage, file);
        }
    }

    private void writeStringToFile(final String errorMessage, final File file) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                FileOutputStream outputStream = null;
                try {
                    ByteArrayInputStream inputStream = new ByteArrayInputStream(errorMessage.getBytes());
                    outputStream = new FileOutputStream(new File(file, System.currentTimeMillis() + ".txt"));
                    int len = 0;
                    byte[] bytes = new byte[1024];
                    while ((len = inputStream.read(bytes)) != -1) {
                        outputStream.write(bytes, 0, len);
                    }
                    outputStream.flush();
                    Log.e("程序出异常了", "写入本地文件成功:" + file.getAbsolutePath());
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (outputStream != null) {
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }

}

 🔥 UncaughtExceptionHandler 异常🔥

Thread中存在两个UncaughtExceptionHandler:

  1. 一个是静态的defaultUncaughtExceptionHandler:来自所有线程中的Exception在抛出并且未捕获的情况下,都会从此路过。进程fork的时候设置的就是这个静态的defaultUncaughtExceptionHandler,管辖范围为整个进程。
  2. 另一个是非静态uncaughtExceptionHandler:为单个线程设置一个属于线程自己的uncaughtExceptionHandler,辖范围比较小。

Thread类的异常处理变量声明 :

//成员变量,线程独有的
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
//静态变量,用于所有线程
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

当一个线程由于未捕获异常即将终止时,Java虚拟机将使用Thread的getuncaughtexceptionhandler()方法查询线程的uncaughtException处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。

一个线程如果没有设置uncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获异常。线程组ThreadGroup实现了UncaughtExceptionHandler,所以可以用来处理未捕获异常。

ThreadGroup实现的uncaughtException如下

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread ""
                                 + t.getName() + "" ");
                e.printStackTrace(System.err);
            }
        }
    }

线程组处理未捕获异常的逻辑是:

  1. 首先将异常消息通知给父线程组处理。
  2. 否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常。
  3. 如果没有默认的异常处理器则将错误信息输出到System.err。

🔥 异常(Crash)处理和捕获 🔥

 在Android中,运行时异常(属于不可查的异常),如果没有在try-catch语句捕获并处理,就会产生Crash,导致程序崩溃。

Crash是App稳定性的一个重要指标,大量的Crash是非常差的用户体验,会导致用户的流失。Crash发生之后,我们应该及时处理并解决,然后发版对其进行修复。

那么,问题来了,我们想要解决Crash,但是Crash发生在用户手机上,我们如何能拿到我们想要的错误信息呢?

Android中添加全局的Crash监控实战

在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash的发生。

1、创建一个自定义UncaughtExceptionHandler类
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        //回调函数,处理异常
        //在这里将崩溃日志读取出来,然后保存到SD卡,或者直接上传到日志服务器
       
        //如果我们也想继续调用系统的默认处理,可以先把系统UncaughtExceptionHandler存下来,然后在这里调用。
    }
}

2. 设置全局监控 

CrashHandler crashHandler = new CrashHandler();
Thread.setDefaultUncaughtExceptionHandler(crashHandler);

完成以上2个步骤,我们就可以实现全局的Crash监控了。这里所说的全局,是指针对整个进程生效。

🔥 Android系统中Crash的处理、分发逻辑 🔥

上面我们了解了怎么在Android中捕获Crash,实现Crash的监控、上报。那么,在Android中,系统是如何处理、分发Crash的呢?

异常处理的注册

App启动时,会通过zygote进程fork一个进程,然后创建VM虚拟机,然后会调用到zygoteInit进行初始化工作。

zygoteInit方法

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java的zygoteInit方法:

public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
            ClassLoader classLoader) {
        ……
        RuntimeInit.commonInit();
        ……
    }

zygoteInit调用了RuntimeInit.commonInit()方法。

 RuntimeInit.commonInit()方法

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));
    }

 逻辑解析:

  1. LoggingHandler用于处理打印日志,我们不做详细分析,感兴趣的可以自己看下。
  2. Thread.setDefaultUncaughtExceptionHandler用于注册系统默认异常处理的逻辑。

这里的RuntimeHooks.setUncaughtExceptionPreHandler方法,其实是调用了Thread的setUncaughtExceptionPreHandler方法。

Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用。

我们先来看异常的分发逻辑,后面再分析KillApplicationHandler中对异常的处理逻辑。

异常的分发

Thread的dispatchUncaughtException负责处理异常的分发逻辑。

Thread的dispatchUncaughtException
public final void dispatchUncaughtException(Throwable e) {
        // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
        Thread.UncaughtExceptionHandler initialUeh =
                Thread.getUncaughtExceptionPreHandler();
        if (initialUeh != null) {
            try {
                initialUeh.uncaughtException(this, e);
            } catch (RuntimeException | Error ignored) {
                // Throwables thrown by the initial handler are ignored
            }
        }
        // END Android-added: uncaughtExceptionPreHandler for use by platform.
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

这里首先处理setUncaughtExceptionPreHandler注册的异常处理方法,然后在调用通过setDefaultUncaughtExceptionHandler或setUncaughtExceptionHandler方法注册的异常处理方法。

Thread的getUncaughtExceptionHandler()方法

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
逻辑解析:
  1. 当uncaughtExceptionHandler不为空时,返回uncaughtExceptionHandler。uncaughtExceptionHandler是通过setUncaughtExceptionHandler方法注册异常处理Handler。
  2. 否则,返回group。
  3. 这里的group,其实就是当前线程所在的线程组。并且线程组ThreadGroup同样实现了UncaughtExceptionHandler接口。

ThreadGroup的uncaughtException

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread ""
                                 + t.getName() + "" ");
                e.printStackTrace(System.err);
            }
        }
    }
逻辑解析:

线程组处理未捕获异常的逻辑是:

  1. 首先将异常消息通知给父线程组处理。
  2. 否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常。
  3. 如果没有默认的异常处理器则将错误信息输出到System.err。

🔥 异常的处理 🔥 

系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,我们来看代码

KillApplicationHandler类的uncaughtException方法
        public void uncaughtException(Thread t, Throwable e) {
            try {
                //确保LoggingHandler的执行
                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();
                }
                //调用AMS,展示弹出等逻辑
                // 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);
            }
        }
逻辑解析:
  1. 调用ensureLogging(t, e)确保LoggingHandler的执行(有去重逻辑,不用担心重复执行)。
  2. 然后调用了ActivityManager.getService().handleApplicationCrash方法来进行处理。
  3. 最后调用Process.killProcess(Process.myPid())来杀死进程,并且退出VM。

 AMS的handleApplicationCrash方法

我们继续来看ActivityManagerService的handleApplicationCrash方法:
位置:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

  public void handleApplicationCrash(IBinder app,
            ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
        ProcessRecord r = findAppProcess(app, "Crash");
        final String processName = app == null ? "system_server"
                : (r == null ? "unknown" : r.processName);

        handleApplicationCrashInner("crash", r, processName, crashInfo);
    }

这里通过Application的binder,取得进程的ProcessRecord对象,然后调用handleApplicationCrashInner方法。

AMS的handleApplicationCrashInner方法 

 final AppErrors mAppErrors;
    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
        /*
        处理一些错误日志相关的逻辑
        */
        //调用
        mAppErrors.crashApplication(r, crashInfo);
    }

这里处理一些错误日志相关的逻辑,然后调用AppErrors的crashApplication方法。

 AppErrors的crashApplication方法

/frameworks/base/services/core/java/com/android/server/am/AppErrors.java

 void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();

        final long origId = Binder.clearCallingIdentity();
        try {
            crashApplicationInner(r, crashInfo, callingPid, callingUid);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

crashApplication调用crashApplicationInner方法,处理系统的crash弹框等逻辑 .

🔥 Crash优化建议 🔥 

Crash是App性能的一个非常重要的指标,我们要尽可能的减少Crash,增加App的稳定性,以下是几点实践经验:文章来源地址https://www.toymoban.com/news/detail-621161.html

  1. 要有可靠的Crash日志收集方式:可以自己实现,也可以集成第三方SDK来采集分析。
  2. 当一个Crash发生了,我们不但需要针性的解决这一个Crash,而且要考虑这一类Crash怎么去解决和预防,只有这样才能使得该类Crash真正的解决,而不是反复出现。
  3. 不能随意的使用try-catch,这样只会隐蔽真正的问题,要从根本上了解Crash的原因,根据原因去解决。
  4. 增加代码检测,预防常规可检测的代码问题的产生,预防胜于治理。

🔥  总结归纳 🔥 

  1. Java异常可分为:可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
  2. Java中,可以通过设置Thread类的uncaughtExceptionHandler属性或静态属性defaultUncaughtExceptionHandler来设置不可查异常的回调处理。
  3. 在Android中,运行时异常(属于不可查的异常),如果没有在try-catch语句捕获并处理,就会产生Crash,导致程序崩溃。
  4. 在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash的发生。
  5. 通过Thread的静态方法setDefaultUncaughtExceptionHandler方法,可以注册全局的默认Crash监控。通过Thread的setUncaughtExceptionHandler方法来注册某个线程的异常监控。
  6. setDefaultUncaughtExceptionHandler方法和setUncaughtExceptionHandler方法有注册顺序的问题,多次注册后,只有最后一次生效。
  7. Android系统中,默认的Crash处理Handler,是在进程创建时,通过RuntimeInit.commonInit()方法进行注册的。
  8. Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用。
  9. Thread的dispatchUncaughtException负责处理异常的分发逻辑。
  10. Android中,异常分发顺序为:
    • 首先处理setUncaughtExceptionPreHandler注册的异常处理方法;
    • 然后处理线程私有的(uncaughtExceptionHandler)Handler异常处理方法;
    • 如果私有Handler不存在,则处理ThreadGroup的Handler异常处理方法;
    • ThreadGroup中,优先调用父线程组的处理逻辑,否则,调用通过setUncaughtExceptionHandler方法注册异常处理Handler。
  11. 系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,系统默认Crash弹框等逻辑是通过AMS的handleApplicationCrash方法执行的。
  12. Crash是App性能的一个非常重要的指标,我们要尽可能的减少Crash,增加App的稳定性。

到了这里,关于Android 面试题 异常捕获 四的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c++捕获异常

    catch  块跟在  try  块后面,用于捕获异常。您可以指定想要捕捉的异常类型,这是由 catch 后的括号内的异常声明决定的。 try { // 保护代码 }catch( ExceptionName e ) { // 处理 ExceptionName 异常的代码 } 上面的代码会捕获一个类型为  ExceptionName  的异常。如果您想让 catch 块能

    2024年02月09日
    浏览(37)
  • Python 异常捕获与处理

    当我们写程序难免遇到报错,专业的称呼叫做异常,行业俗语叫做bug,由于异常情况出现会导致代码停止运行,所以在编写过程中要尽可能避免。 语法错误 这一类错误很好理解,相当于你不会写,比如用if或for的时候不知道他们怎么写,这种情况比较夸张了,好好学过的人都

    2023年04月22日
    浏览(53)
  • Java中的异常Exception和捕获,自定义异常

    1.1 什么是程序的异常  在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的。 异常 :指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停

    2023年04月20日
    浏览(41)
  • 如何在Python中捕获异常

    本文主要介绍 Python 捕获异常的各种技术。首先,回顾 Python 的异常处理机制,然后深入研究并学习如何识别捕获的异常内容,以及忽略异常。 公众号: 滑翔的纸飞机 Python 代码在运行的过程中,偶尔将出现意料之内或之外的错误从而引发异常。例如,如果尝试读取不存在的

    2024年01月22日
    浏览(43)
  • 爬虫异常捕获与处理方法详解

    Hey!作为一名专业的爬虫代理供应商,我今天要和大家分享一些关于爬虫异常捕获与处理的方法。在进行爬虫操作时,我们经常会遇到各种异常情况,例如网络连接错误、请求超时、数据解析错误等等。这些异常情况可能会导致程序崩溃或数据丢失,因此,我们需要学会如何

    2024年02月11日
    浏览(43)
  • Python十类常见异常类型(附捕获以及异常处理方式)

    大家好,我是辣条哥!今天给大家讲讲我们刚开始写代码是都会出现的问题-我们刚开始常见的异常以及处理方法! 异常是指在程序运行过程中出现的错误或异常情况,例如除以0、访问不存在的变量等。Python提供了一些内置的异常类型,同时也支持自定义异常类型。 想找辣条

    2024年02月08日
    浏览(82)
  • 【SpringBoot】springboot实现全局异常捕获

    为什么要做异常处理: 原因有三: 1、将系统产生的全部异常统一捕获处理。 2、自定义异常需要由全局异常来捕获。 3、JSR303规范的validator参数校验器、参数校验不通过、本身无法使用try…catch 其实对于前后端分离的项目做异常处理是很有必要的 在不出异常的情况下,后端

    2024年02月12日
    浏览(42)
  • 前端创建WebSocket时报错,怎么捕获异常

    Uncaught DOMException: Failed to construct ‘WebSocket’: The URL ‘XXX’ is invalid. 前端new WebSocket();时报错 搜了半天两种说法: 1.在onerror的回调中捕获 2.设计缺陷,捕获不了 第一种说法明显不可以,这是创建时出错,这时WebSocket对象还没有创建成功,它哪里来的onerror 方法 这里我利用prox

    2024年02月12日
    浏览(41)
  • 解决node项目一个极度困难的捕获异常却无法读取异常信息的问题

    这个项目是集成了第三方NeteaseCloudMusicApi项目的接口代码,我没有直接使用它的接口,因为需要再跑一个npm run开个端口,感觉很麻烦。 所以下定决心,使用拆分代码的方式,硬生生将这个api项目的部分api接口代码集成到了我自己的项目。 当然前提是必须能看懂它的代码 ,然

    2024年02月08日
    浏览(50)
  • C#学习笔记---异常捕获和变量运算符

    使用异常捕获可以捕获出现异常的代码块,防止因为异常抛出造成的程序卡死的情况发生。 try{}catch{}finally{}结构 算术运算符 算术运算符是英语数值类型变量运算的运算符,运算结果仍旧为数值。 赋值运算符: = 注意:赋值运算符理解将右边数值 赋值给 左边变量。 算术运算

    2024年02月08日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包