❤️Android 应用崩溃?嗯?莫慌,稳住!❤️

简介: 从刚开始接触Android开发,第一次发版,遇到程序崩溃,那就一个慌张。好几年过去了,现在的听到程序崩溃?嗯,稍等我看看什么问题,然后该锁定该锁定该解决解决。 发版前减少bug、崩溃等,发版后遇到bug、崩溃也不要慌张,毕竟 bug不 会因为你的慌张而自动修复对吧?要以最快的速度解决(解决问题同样是能力的体现),并说明问题轻重,看看是直接发版还是坐等下次。同时,吸取教训避免同样问题发生。 今天咱们就聊聊Android程序闪退。一个应用的崩溃率高低,决定了这个应用的质量。

前言



       从刚开始接触Android开发,第一次发版,遇到程序崩溃,那就一个慌张。好几年过去了,现在的听到程序崩溃?嗯,稍等我看看什么问题,然后该锁定该锁定该解决解决。


      发版前减少bug、崩溃等,发版后遇到bug、崩溃也不要慌张,毕竟 bug不 会因为你的慌张而自动修复对吧?要以最快的速度解决(解决问题同样是能力的体现),并说明问题轻重,看看是直接发版还是坐等下次。同时,吸取教训避免同样问题发生。


       今天咱们就聊聊Android程序闪退。一个应用的崩溃率高低,决定了这个应用的质量。


       为了解决崩溃问题,Android 系统会输出各种相应的 log 日志,当然还各式各样的三方库,大程度上降低了工程师锁定崩溃问题的难度。


       如果要给 crash 日志进行分类,可以分成 2 大类:


  • JVM 异常(Exception)堆栈信息,如下:


微信图片_20220522212429.png


  • native 代码崩溃日志,如下:


微信图片_20220522212452.png


JVM 异常堆栈信息


Java 中异常(Exception)分两种:


  • 检查异常 checked Exception。


  • 非检查异常 unchecked Exception。


检查异常:就是在代码编译时期,Android Studio 就会提示代码有错误,无法通过编译,比如 InterruptedException。如果我们没有在代码中将这些异常 catch,而是直接抛出,最终也有可能导致程序崩溃。


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


非检查异常:包括 error 和运行时异常(RuntimeException),Android Studio 并不会在编译时期提示这些异常信息,而是在程序运行时期因为代码错误而直接导致程序崩溃,比如 OOM 或者空指针异常(NullPointerException)。


1.2021-09-13 11:50:27.327 19984-19984/com.scc.demo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.scc.demo, PID: 19984
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.scc.demo/com.scc.demo.actvitiy.HandlerActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
        ...
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
        at com.scc.demo.actvitiy.HandlerActivity.onCreate(HandlerActivity.java:41)
        at android.app.Activity.performCreate(Activity.java:8000)
        ...



Java 异常


       对于上述两种异常我们都可以使用 UncaughtExceptionHandler 来进行捕获操作,它是 Thread 的一个内部接口,定义如下:


1.    public interface UncaughtExceptionHandler {
        /**
         * 当给定Thread由于给定的Throwable而终止时调用的方法。
         * 此方法抛出的任何异常都将被 Java 虚拟机忽略。
         * @param t Thread
         * @param e Throwable
         */
        void uncaughtException(Thread t, Throwable e);
    }



从官方对其介绍能够看出,对于传入的 Thread,如果因为"未捕获"异常而导致被终止,uncaughtException 则会被调用。我们可以借助它来间接捕获程序异常,并进行异常信息的记录工作,或者给出更友好的异常提示信息。


自定义异常处理类


自定义异常处理类


       自定义类实现 UncaughtExceptionHandler 接口,并实现 uncaughtException 方法:


public class SccExceptionHandler implements Thread.UncaughtExceptionHandler {
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    private static SccExceptionHandler sccExceptionHandler;
    private Context mContext;
    public static SccExceptionHandler getInstence() {
        if (sccExceptionHandler == null) {
            synchronized (SccExceptionHandler.class) {
                sccExceptionHandler = new SccExceptionHandler();
            }
        }
        return sccExceptionHandler;
    }
    public void init(Context context) {
        mContext = context;
        //系统默认未捕获异常handler
        //the default uncaught exception handler for all threads
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //将当前Handler设为系统默认
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    @Override
    public void uncaughtException(@NonNull @NotNull Thread t, @NonNull @NotNull Throwable e) {
        if (!handlerUncaughtException(e) && mDefaultHandler != null) {
            //注释1:系统处理
            mDefaultHandler.uncaughtException(t, e);
        } else {
            //注释2:自己处理
            Intent intent = new Intent(mContext, ImageViewActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
            mContext.startActivity(intent);
            //终止进程
            android.os.Process.killProcess(android.os.Process.myPid());
            //终止当前运行的 Java 虚拟机。
            //参数用作状态代码; 按照惯例,非零状态代码表示异常终止。
            System.exit(0);
        }
    }
    //处理程序未捕获异常
    private boolean handlerUncaughtException(Throwable e) {
        //1.收集 crash 现场的相关信息,如当前 App 的版本信息,设备的相关信息以及异常信息。
        //2.日志的记录工作(如保存在本地),等开发人员排查问题或等下次启动APP上传至服务器。
        return true;
        //不想处理 return false;
    }
}


 注释1:在自定义异常处理类中需要持有线程默认异常处理类。这样做的目的是在自定义异常处理类无法处理或者处理异常失败时,还可以将异常交给系统做默认处理。


     注释2:如果自定义异常处理类成功处理异常,需要进行页面跳转,或者将程序进程"杀死"。否则程序会一直卡死在崩溃界面,并弹出无响应对话框。


android.os.Process.myPid():返回此进程的标识符,可与 killProcess 和 sendSignal 一起使用。


android.os.Process.killProcess(android.os.Process.myPid()):使用给定的 PID 终止进程。 请注意,尽管此 API 允许我们根据其 PID 请求终止任何进程,但内核仍会对您实际能够终止的 PID 施加标准限制。 通常这意味着只有运行调用者的包/应用程序的进程和由该应用程序创建的任何其他进程; 共享一个通用 UID 的包也将能够杀死彼此的进程。


使用自定义异常处理类


       SccExceptionHandler 定义好之后,就可以将其初始化,并将主线程注册到 SccExceptionHandler 中。如下:


public class SccApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        SccExceptionHandler.getInstence().init(this);
    }
}


native 异常


       当程序中的 native 代码发生崩溃时,系统会在 /data/tombstones/ 目录下保存一份详细的崩溃日志信息。由于对 native 还不是很熟悉就不误导大家,感兴趣的自己玩玩。


对于程序崩溃信号机制的介绍,可以参考腾讯的这篇文章:Android 平台 Native 代码的崩溃捕获机制及实现


线上崩溃日志获取


       上面介绍的 Java 和 Native 崩溃的捕获都是基于自己能够复现 bug 的前提下。但是对于线上的用户,这种操作方式是不太现实的。


       对于大多数公司来说,针对线上版本,没有必要自己实现一个抓取 log 的平台系统。最快速的实现方式就是集成第三方 SDK。目前比较成熟,采用也比较多的就是腾讯的 Bugly。


Bugly


       Bugly  基本能够满足线上版本捕获 crash 的所有需求,包括 Java 层和 Native 层的 crash 都可以获取相应的日志。并且每天 Bugly 都会邮件通知上一天的崩溃日志,方便测试和开发统计 bug 的分布以及崩溃率。


      接入文档接入文档文章最后会有简易版接入使用

微信图片_20220522213247.png


异常概括


微信图片_20220522213313.png


崩溃分析


微信图片_20220522213329.png


       程序崩溃分析这块我没做调整,这个是bugly自动抓取的。


错误分析


微信图片_20220522213355.png


具体内容


微信图片_20220522213359.png

微信图片_20220522213421.png


      这里我用来存放去服务端请求接口时的参数和返回的数据。,下面看看具体效果。


       使用起来相当方便,而且错误还提供解决方案,美滋滋。


xCrash


       xCrash 能为安卓 app 提供捕获 java 崩溃,native 崩溃和 ANR 的能力。不需要 root 权限或任何系统权限。


       xCrash 能在 app 进程崩溃或 ANR 时,在你指定的目录中生成一个 tombstone 文件(格式与安卓系统的 tombstone 文件类似)。


       xCrash 已经在 爱奇艺 的不同平台(手机,平板,电视)的很多安卓 app(包括爱奇艺视频)中被使用了很多年。


xCrash传送门


Sentry


       Sentry 是一项可帮助您实时监控和修复崩溃的服务。 服务器使用 Python,但它包含一个完整的 API,用于在任何应用程序中从任何语言发送事件。


Sentry传送门


       XCrash 和 Sentry,这两者比 Bugly 好的地方就是除了自动拦截界面崩溃事件,还可以主动上报错误信息。


       可以看出 XCrash 的使用更加灵活,工程师的掌控性更高。可以通过设置不同的过滤方式,针对性地上报相应的 crash 日志。并且在捕获到 crash 之后,可以加入自定义的操作,比如本地保存日志或者直接进行网络上传等。


       另外:Sentry 还有一个好处就是可以通过设置过滤,来判断是否上报 crash 日志。这对于 SDK 的开发人员是很有用的。比如一些 SDK 的开发商只是想收集自身 SDK 引入的 crash,对于用户的其他操作导致的 crash 进行过滤,这种情况就可以考虑集成 Sentry。


Bugly 简单使用


       感觉教程乱的可以自己去上文找Bugle文档自己集成,很简单的。


库文件导入


自动集成(推荐)


plugins {
    id 'com.android.application'
}
android {
    compileSdkVersion 30//项目的编译版本
    defaultConfig {
        applicationId "com.scc.demo"//包名
        minSdkVersion 23//最低的兼容的Android系统版本
        targetSdkVersion 30//目标版本,表示你在该Android系统版本已经做过充分的测试
        versionCode 1//版本号
        versionName "1.0.0"//版本名称
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a','x86'
            //运行环境,要上传Google Play必须兼容64位,这里仅兼容ARM架构
            //对于ARM架构,32 位库位于armeabi-v7a 中。64 位等效项是arm64-v8a。
            //对于x86体系结构,查找x86(用于 32 位)和 x86_64(用于 64 位)。
        }
    }
}
dependencies {
    //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9
    implementation 'com.tencent.bugly:crashreport:latest.release'
    //其中latest.release指代最新Bugly NDK版本号,也可以指定明确的版本号,例如3.0
    //集成Bugly NDK时,需要同时集成Bugly SDK。
    implementation 'com.tencent.bugly:nativecrashreport:latest.release'
}


  注意:自动集成时会自动包含Bugly SO库,建议在Module的build.gradle文件中使用NDK的"abiFilter"配置,设置支持的SO库架构。  


如果在添加"abiFilter"之后Android Studio出现以下提示:

      NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin.


       则在项目根目录的gradle.properties文件中添加:

       android.useDeprecatedNdk=true


初始化


public class SccApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //70594a1ff8 Bugly新建产品的 App ID
        CrashReport.initCrashReport(getApplicationContext(), "70594a1ff8", false);
    }
}


错误分析


设置


private void setCrashReport(String url, String name, Map<String, String> params, String message) {
        try {
            if (params != null && !MStringUtils.isNullOrEmpty(url) && !MStringUtils.isNullOrEmpty(name) && !MStringUtils.isNullOrEmpty(params.toString()) && !MStringUtils.isNullOrEmpty(message)) {
                CrashReport.putUserData(AppGlobalUtils.getApplication(), "SccParams", params.toString());
                CrashReport.putUserData(AppGlobalUtils.getApplication(), "Data",   "LoginName-Scc001:" + message);
                CrashReport.postCatchedException(new RuntimeException(name + ":" + url + ":" + message));
            }
        } catch (Exception e) {
        }
    }


调用

   

HashMap<String,String> hashMap = new HashMap<>();
        hashMap.put("name","scc001");
        hashMap.put("pass","111111");
        String returnData = "哈哈哈哈哈";
        setCrashReport("loin/register","Main",hashMap,returnData);


效果


错误列表


微信图片_20220522213904.png



错误详情


微信图片_20220522213938.png


出错堆栈


微信图片_20220522213951.png


跟踪数据


微信图片_20220522214003.png


崩溃分析


       这个不用咱自己设置,Bugly自动抓取,下面提供跟错误分析类似功能这里就不多描述了。

微信图片_20220522214034.png



       本文内容到这里就算结束了。希望能帮你快速锁定 bug 并解决,让应用更完美,让你的老板更放心,票票来的更多一些。



相关文章
|
4天前
|
Android开发
Android应用实例(一)之---有道辞典VZ.0
Android应用实例(一)之---有道辞典VZ.0
10 2
|
13天前
|
移动开发 Java Android开发
构建高效Android应用:Kotlin协程的实践之路
【4月更文挑战第30天】在移动开发领域,随着用户需求的不断增长和设备性能的持续提升,实现流畅且高效的用户体验已成为开发者的首要任务。针对Android平台,Kotlin协程作为一种新兴的异步编程解决方案,以其轻量级线程管理和简洁的代码逻辑受到广泛关注。本文将深入探讨Kotlin协程的概念、优势以及在实际Android应用中的运用,通过实例演示如何利用协程提升应用性能和响应能力,为开发者提供一条构建更高效Android应用的实践路径。
|
1天前
|
移动开发 监控 Android开发
构建高效Android应用:Kotlin协程的实践与优化
【5月更文挑战第12天】 在移动开发领域,性能与响应性是衡量一个应用程序优劣的关键指标。特别是在Android平台上,由于设备的多样性和系统资源的限制,开发者需要精心编写代码以确保应用流畅运行。近年来,Kotlin语言因其简洁性和功能性而广受欢迎,尤其是其协程特性,为异步编程提供了强大而轻量级的解决方案。本文将深入探讨如何在Android应用中使用Kotlin协程来提升性能,以及如何针对实际问题进行优化,确保应用的高效稳定执行。
|
2天前
|
移动开发 API Android开发
构建高效Android应用:Kotlin协程的实践指南
【5月更文挑战第11天】 在移动开发领域,性能优化和资源管理是至关重要的。特别地,对于Android开发者来说,合理利用Kotlin协程可以极大地改善应用的响应性和稳定性。本文将深入探讨Kotlin协程在Android中的实际应用,包括它们如何简化异步编程模型、提高UI线程的响应性,以及减少内存消耗。我们将通过具体案例分析,了解如何在实际项目中有效地使用协程,从而帮助开发者构建更加高效的Android应用程序。
|
3天前
|
开发工具 Android开发 Windows
Android应用] 问题2:ERROR: unknown virtual device name:
Android应用] 问题2:ERROR: unknown virtual device name:
|
3天前
|
XML JSON API
转Android上基于JSON的数据交互应用
转Android上基于JSON的数据交互应用
|
5天前
|
安全 Java Android开发
构建高效Android应用:采用Kotlin进行内存优化的策略
【5月更文挑战第8天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,合理管理内存资源是确保应用流畅运行的关键因素之一。近年来,Kotlin作为官方推荐的开发语言,以其简洁、安全和互操作性的特点受到开发者青睐。本文将深入探讨利用Kotlin语言特性,通过具体策略对Android应用的内存使用进行优化,旨在帮助开发者提高应用性能,减少内存消耗,避免常见的内存泄漏问题。
8 0
|
6天前
|
移动开发 数据库 Android开发
构建高效Android应用:Kotlin协程的全面应用
【5月更文挑战第7天】 在移动开发领域,性能优化与流畅的用户体验是至关重要的。随着Kotlin语言的流行,其并发神器——协程,已成为提升Android应用性能的重要工具。本文将深入探讨如何在Android项目中利用Kotlin协程进行异步编程、网络请求和数据库操作,以及如何通过协程简化代码结构,增强应用的响应性和稳定性。我们的目标是为开发者提供一套实用的协程使用模式和最佳实践,以便构建更加高效的Android应用。
23 3
|
6天前
|
移动开发 数据库 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第7天】 在移动开发领域,性能优化和资源管理始终是核心议题。随着Kotlin语言的普及,其提供的协程特性为Android开发者带来了异步编程的新范式。本文将深入探讨如何通过Kotlin协程来优化Android应用的性能,实现流畅的用户体验,并减少资源消耗。我们将分析协程的核心概念,并通过实际案例演示其在Android开发中的应用场景和优势。
|
9天前
|
移动开发 前端开发 Android开发
构建高效Android应用:探究Kotlin协程的优势
【5月更文挑战第4天】 在移动开发领域,尤其是对于Android开发者而言,编写响应迅速且高效的应用程序至关重要。Kotlin作为一种现代的编程语言,其提供的协程特性为异步编程带来了革命性的改变。本文将深入探讨Kotlin协程在Android开发中的应用优势,并通过实例代码展示如何利用协程简化异步任务处理,提高应用性能和用户体验。