当前位置:   article > 正文

Android稳定性优化总结_android稳定性建设

android稳定性建设

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

Crash治理方法

常见Crash的处理方式:

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

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

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

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

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

Crash治理实践

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

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

Crash率评价
性能指标优秀值及格值极差值行业参考值
崩溃率(%)<=0.10.6>=10.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();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

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

Thread.setDefaultUncaughtExceptionHandler(MyExceptionHandler())

  • 1
  • 2

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

  • 1
  • 2

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

adb pull /data/anr/traces.txt trace.txt

  • 1
  • 2
本地ANR监控:

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

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

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

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

adb bugreport

  • 1
  • 2

在关键词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;
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
线上ANR监控:

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

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

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

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

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

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

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

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号