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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 从刚开始接触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 并解决,让应用更完美,让你的老板更放心,票票来的更多一些。



相关文章
|
2月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
190 4
|
4月前
|
IDE Java 开发工具
深入探索安卓应用开发:从环境搭建到第一个"Hello, World!"应用
本文将引导读者完成安卓应用开发的初步入门,包括安装必要的开发工具、配置开发环境、创建第一个简单的安卓项目,以及解释其背后的一些基本概念。通过一步步的指导和解释,本文旨在为安卓开发新手提供一个清晰、易懂的起点,帮助读者顺利地迈出安卓开发的第一步。
235 65
|
4月前
|
存储 Java Android开发
探索安卓应用开发:构建你的第一个"Hello World"应用
【9月更文挑战第24天】在本文中,我们将踏上一段激动人心的旅程,深入安卓应用开发的奥秘。通过一个简单而经典的“Hello World”项目,我们将解锁安卓应用开发的基础概念和步骤。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供一次实操体验。从搭建开发环境到运行你的应用,每一步都清晰易懂,确保你能顺利地迈出安卓开发的第一步。让我们开始吧,探索如何将一行简单的代码转变为一个功能齐全的安卓应用!
|
27天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
56 14
|
30天前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
28天前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
28天前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
35 0
|
2月前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
2月前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
2月前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。