稳定性优化 - 容灾方案 - 安全模式实现

概述

看了这篇 jsonchao 的这篇神文 深入探索Android稳定性优化
很是佩服。

里面有讲到了容灾方案 的建设,总结了4条。分别是

  1. 功能开关
  2. 统跳中心
  3. 动态修复
  4. 安全模式



功能开关

想实现功能开关,App一般需要有长连接的能力,或者写个轮询替代。公司也要建设相应的参数配置平台。这样就可以实现定时更新参数配置的能力。

开发某个重要功能、改动范围很大的业务的时候,都可以加上一个开关。

开关默认是打开了(获取不到就使用默认值true),所以默认会走新功能的代码处理逻辑。
如果遇到新功能有异常,在参数平台配置紧急配置新功能开关为关闭。



统跳中心

在 OkHttp 里写一个拦截器,可以提前拦截到请求Response。

检测返回的状态码。比如约定返回 ret:10015 就弹一个吐司,吐司文案取返回里的 msg 字段。约定返回 ret:10016 就使用 ARouter 进行H5页面跳转,跳转的url取返回里的 url 字段。



动态修复

其实就是集成 Tinker 这类的热修复库,拥有热修复能力。



安全模式

Crash 里面最严重的当属启动Crash,如果用户打开app就崩,打开app就崩,是个严重事故不说,更麻烦的是用户才不会清理数据重新打开,只可能一怒之下把 App 删除。

文章里面讲到 微信读书、蘑菇街、淘宝、天猫等APP都使用了安全模式保障客户端启动流程,启动失败后给用户自救机会。
安全模式的核心特点:多次crash后重置为安装初始状态,严重Bug可阻塞性热修复。



根据文章的介绍,我自己写了个实现。

1. 核心处理类

package com.yao.mocklocation.tool

import android.annotation.SuppressLint
import android.content.Context
import android.os.Process
import android.util.Log
import com.yao.mocklocation.util.AppUtil
import kotlin.system.exitProcess

@SuppressLint("StaticFieldLeak")
object CrashHandler : Thread.UncaughtExceptionHandler {

    private const val TAG = "CrashHandler"
    private const val SAFE_MODE = "safe_mode"
    private const val CRASH_FILE_NAME_1 = "crash_file_name_1"
    private const val CRASH_FILE_NAME_2 = "crash_file_name_2"
    private const val CRASH_FILE_NAME_3 = "crash_file_name_3"

    var levelOneSafeMode = false

    var levelTwoSafeMode = false

    private var defaultCrashHandler: Thread.UncaughtExceptionHandler? = null

    private lateinit var context: Context

    fun init(context: Context) {
        this.context = context
        defaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler(this)


        // 清除安全模式缓存记录
        Handler().postDelayed({
            clear()
        }, 60 * 1000L) // 时间应改成可配置

        // 检查看是否要进入安全模式
        if (contains(CRASH_FILE_NAME_1) && contains(CRASH_FILE_NAME_2)) {
            if (contains(CRASH_FILE_NAME_3)) {
                entryLevelTwoSafeMode()
            } else {
                entryLevelOneSafeMode()
            }
        }
    }

    override fun uncaughtException(thread: Thread, throwable: Throwable) {
        process(throwable)
        if (defaultCrashHandler != null) {
            defaultCrashHandler?.uncaughtException(thread, throwable)
        } else {
            Process.killProcess(Process.myPid())
            exitProcess(0)
        }
    }

    private fun process(throwable: Throwable) {
        if (contains(CRASH_FILE_NAME_1)) {
            if (contains(CRASH_FILE_NAME_2)) {
                if (!contains(CRASH_FILE_NAME_3)) {
                    saveCrash(CRASH_FILE_NAME_3, throwable)
                }
            } else {
                saveCrash(CRASH_FILE_NAME_2, throwable)
            }
        } else {
            saveCrash(CRASH_FILE_NAME_1, throwable)
        }
    }

    private fun entryLevelOneSafeMode() {
        levelOneSafeMode = true
        Log.e(TAG, "进入一级安全模式")
    }

    private fun entryLevelTwoSafeMode() {
        levelOneSafeMode = true
        levelTwoSafeMode = true
        Log.e(TAG, "进入二级安全模式")
    }

    private fun contains(key: String?): Boolean {
        return context.getSharedPreferences(SAFE_MODE, Context.MODE_PRIVATE).contains(key)
    }

    public fun loadCrash(key: String?): String? {
        return context.getSharedPreferences(SAFE_MODE, Context.MODE_PRIVATE).getString(key, null)
    }

    /**
     * 保存crash
     */
    private fun saveCrash(key: String?, throwable: Throwable): Boolean {
        // 这个方法里,正确的做法是把crash日志、cpu占用、内存占用、其他线程的堆栈等一些相关信息一起保存成一个文件。
        // 然后用SP里保存文件路径,注意要使用commit。
        val crashLog = Log.getStackTraceString(throwable)
        return context.getSharedPreferences(SAFE_MODE, Context.MODE_PRIVATE).edit().putString(key, crashLog).commit()
    }

    /**
     * 当版本有升级或者热修复后,可以调用此方法清楚所有标记
     */
    public fun clear() {
        context.getSharedPreferences(SAFE_MODE, Context.MODE_PRIVATE).edit().clear().apply()
    }
}

2. 初始化

比如在 Application 里面初始化

package com.yao.mocklocation.core

import android.app.Application
import com.yao.mocklocation.tool.CrashHandler

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        CrashHandler.init(applicationContext)
    }
}

3. 进入安全模式后的处理

安全模式的判断和相应的处理都是根据具体业务场景来定的。

举个例子,一般我们首页就会去请求 feed 流、广告弹窗、今日任务之类的接口。所以一级安全模式(连续2次崩溃)可以拦截掉首页的非必要请求,对于 feed 流返回一个空数据列表。对于广告弹窗、今日任务返回一些特殊错误码,确保下方业务不会处理。

二级安全模式(连续3次崩溃),可以在 Splash 页进行阻塞请求热修复接口,如果有数据则必须等待下载完热修复包 + 热修复完成,然后重启App。如果没有数据(可能刚发生的事故,还没来得及热修复处理)则清除一些业务缓存数据。例如token,让用户重新走登录流程。

if (CrashHandler.levelOneSafeMode) {
    // 写在 OkHttp 的拦截器里,拦截一些非必要请求,自己构建一个Response返回下方业务不会处理的错误码
}

if (CrashHandler.levelTwoSafeMode) {
	// 在Splash页面阻塞请求热修复接口,如果有数据则必须热修复完成然后重启App。
	// 如果热修复接口没有数据,则清除业务缓存。
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值