Android稳定性优化总结

作者: 奔跑吧李博 | 来源:发表于2023-02-08 17:56 被阅读0次

    App Crash对于用户来讲是一种最糟糕的体验,它会导致流程中断、app口碑变差、app卸载、用户流失、订单流失等。

    Crash治理方法

    常见Crash的处理方式:

    • 根据Crash统计平台的堆栈,用户日志,操作路径定位和解决。

    • 寻找共性,机型、品牌、系统版本、所在页面、用户操作等辅助解决问题。

    • 复现场景,能够复现通常就很容易解决,可以线下复现或者云真机复现。

    • 对crash率较高的模块进行业务梳理,排查,重构等。

    • 与第三方sdk沟通升级解决问题,修改SDK的使用方式。

    Crash治理实践

    1.预防Crash
    对于稳定性来说,如果App已经到了线上才发现异常,那其实已经造成了损失,所以,对于稳定性的优化,其重点在于预防。Gerrit 是一个免费、开放源代码的代码审查软件,使用网页界面。我们公司大多数项目都使用Gerrit进行CodeReview,从项目开发阶段就会审查各种潜在的crash或者逻辑上的问题,严格按照代码+1、+2之后才能提交入库。

    2.长效保持需要科学流程
    应用稳定性的建设过程是一个细活,所以很容易出现这个版本优化好了,但是在接下来的版本中如果我们不管它,它就会发生持续恶化的情况,因此,我们必须从项目研发的每一个流程入手,建立科学完善的相关规范,才能保证长效的优化效果。在每个版本都要追踪崩溃监测平台,将对应的崩溃分发给对应业务的开发人员处理。

    Crash率评价
    性能指标 优秀值 及格值 极差值 行业参考值
    崩溃率(%) <=0.1 0.6 >=1 0.5

    那么,我们App的Crash率降低多少才能算是一个正常水平或优秀的水平呢?

    Java与Native的总崩溃率必须在千分之二以下。
    Crash率万分位为优秀:需要注意90%的Crash都是比较容易解决的,但是要解决最后的10%需要付出巨大的努力。

    Crash关键问题

    如果应用发生了Crash,我们应该尽可能还原Crash现场。因此,我们需要全面地采集应用发生Crash时的相关信息,如下所示:

    • 堆栈、设备、OS版本、进程、线程名、Logcat
    • 前后台、使用时长、App版本、小版本、渠道
    • CPU架构、内存信息、线程数、资源包信息、用户行为日志

    Crash监控方案

    Java中的Thread定义了一个接口: UncaughtExceptionHandler ;用于处理未捕获的异常导致线程的终止(注意:catch了的是捕获不到的),当我们的应用crash的时候,就会走 UncaughtExceptionHandler.uncaughtException ,在该方法中可以获取到异常的信息,我们通过 Thread.setDefaultUncaughtExceptionHandler 该方法来设置线程的默认异常处理器,我们可以将异常信息保存到本地或者是上传到服务器,方便我们快速的定位问题。

    class MyExceptionHandler implements Thread.UncaughtExceptionHandler {
    
        @Override
        public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
            File file = dealException(thread, throwable);
            //上传服务器
            ...
        }
    
        /*** 导出异常信息到SD卡 ** @param e */
        private File dealException(Thread thread, Throwable throwable) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            File crashFolder = new File(mContext.getExternalCacheDir().getAbsoluteFile(), CrashMonitor.DEFAULT_JAVA_CRASH_FOLDER_NAME);
            if (!crashFolder.exists()) {
                crashFolder.mkdirs();
            }
            File crashFile = new File(crashFolder, time + FILE_NAME_SUFFIX);
            try {
                // 往文件中写入数据
                PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile)));
                pw.println(time);
                pw.println(thread);
                pw.println(getPhoneInfo());
                throwable.printStackTrace(pw);  //将异常信息堆栈写入文件
                // 写入crash堆栈
                pw.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return crashFile;
        }
    
        private String getPhoneInfo() {
            PackageManager pm = mContext.getPackageManager();
            PackageInfo pi = null;
            StringBuilder sb = new StringBuilder();
    
            try {
                pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
    
                // App版本
                sb.append("App Version: ");
                sb.append(pi.versionName);
                sb.append("_");
                sb.append(pi.versionCode + "\n");
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
    
            // Android版本号
            sb.append("OS Version: ");
            sb.append(Build.VERSION.RELEASE);
            sb.append("_");
            sb.append(Build.VERSION.SDK_INT + "\n");
    
            // 手机制造商
            sb.append("Vendor: ");
            sb.append(Build.MANUFACTURER + "\n");
    
            // 手机型号
            sb.append("Model: ");
            sb.append(Build.MODEL + "\n");
    
            // CPU架构
            sb.append("CPU: ");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                sb.append(Arrays.toString(Build.SUPPORTED_ABIS));
            } else {
                sb.append(Build.CPU_ABI);
            }
            return sb.toString();
        }
    }
    

    然后在application中设置自定义的UncaughtExceptionHandler。

    Thread.setDefaultUncaughtExceptionHandler(MyExceptionHandler())
    

    ANR问题分析

    ANR发生的原因总结和解决办法

    1.在主线程中,进行了触屏点击滑动等操作,在5秒之内对该事件没有响应,就会导致ANR(例如,按键按下,屏幕触摸)
    2.BroadcastReceiver在10秒内没有执行完毕
    3.service是20秒

    根本原因是在主线程进行了耗时操作,导致后面的消息无法处理,比如:
    1.耗时的网络访问
    2.大量的数据读写
    3.数据库操作
    4.在主线程中调用thread的join()方法、sleep()方法、wait()方法
    5.其他线程持有锁,导致主线程等待超时
    6.子线程终止或崩溃导致主线程一直等待

    解决的主要办法就是开启子线程去处理这些耗时操作。修改主线程去等待子线程的锁。

    ANR排查流程

    1、抓取bugreport

    adb shell bugreport > bugreport.txt
    

    2、直接导出/data/anr/traces.txt文件

    adb pull /data/anr/traces.txt trace.txt
    
    本地ANR监控:

    监控方法:通过 FileOberver 监控 data/anr 文件夹下文件的变化,来确定ANR的发生。
    获取信息:主线程堆栈信息+ANR信息。

    由于刚监控到ANR发生时进程并没有进入ANR状态,而是先向 /data/anr/ 文件中写入进程信息,此时并不会立即获取到进程的ANR信息,需要循环等待进程进入ANR状态。

    解决ANR问题,首先要做的是找到问题,线下我们可以通过ADB命令导出ANR文件进行分析,线上我们可以使用FileObserver或ANR-WatchDog保存ANR堆栈信息,然后上传到服务器。

    ANR发生之后我们可以使用以下命令导出ANR文件:

    adb bugreport
    

    在关键词DALVIK THREADS和"main" prio=5 tid=1 Native下面可获取ANR的日志:

    使用FileObserver监控data/anr目录,当文件状态发生改变、创建或删除时,说明当前发生了anr事件。

    创建类继承FileObserver,监控data/anr文件:

    class AnrFileObserver extends FileObserver {
    
        @Override
        public void onEvent(int event, @Nullable String path) {
            switch (event) {
                case FileObserver.ACCESS:
    
                    break;
                case FileObserver.CREATE:
    
                    break;
                case FileObserver.MODIFY:
    
                    break;
            }
        }
    }
    
    线上ANR监控:

    接入 bugly,根据App版本,手机型号,发生时间,次数,产生位置进行位置问题定位并解决。

    ANR异常我们可分为线上监测和线下监测两个方向

    线上监测主要是利用FileObserver进行ANR目录文件变化监听,以ANR-WatchDog进行补充。
    FileObserver在使用过程中应注意高版本程序不可用以及预防死锁出现。
    线下监测主要是在报错之后利用ADB命令将错误的日志导出并找到错误的类进行分析。

    参考:
    https://www.jianshu.com/p/8abce3ff4687

    如果发生了异常情况,怎么快速止损?

    1.功能开关
    首先,需要让App具备一些高级的能力,我们对于任何要上线的新功能,要加上一个功能的开关,通过配置中心下发的开关呢,来决定是否要显示新功能的入口。如果有异常情况,可以紧急关闭新功能的入口,那就可以让这个App处于可控的状态了。

    2.关闭灰度发布
    如果问题更严重,范围更广,可以立即关闭灰度发布版本。

    3.动态修复:热修复、资源包更新
    目前热修复的方案其实已经比较成熟了,我们完全可以低成本地在我们的项目中添加热修复的能力,当然,如果有些功能是由RN或WeeX来实现就更好了,那就可以通过更新资源包的方式来实现动态更新。

    参考:
    https://mp.weixin.qq.com/s/Wwazb5HFd5hunq5GOkjWmQ

    相关文章

      网友评论

        本文标题:Android稳定性优化总结

        本文链接:https://www.haomeiwen.com/subject/yrirsltx.html