一次CPU使用率100%引发的思考

一次CPU使用率100%引发的思考

最近要发一个版本,临了测试在挂机的时候发现一个CPU占用高,导致App卡死的bug。经过这次bug
真切感受到写代码完成功能容易,一旦代码量上去了,出问题的时候如何定位就是个难题。为了尽可能
少地避免这种上线后出现问题,出现问题后及时修复,掌握必要的问题分析工具和严谨的日志记录
是一个程序员的基本素养。本次事件对app开发的启示和使用的工具方法等:

  1. 必须有一个完善的日志框架,遇到问题时,可以收集必要的信息进行分析;
  2. 尽可能使用线程池并给每个线程命名,实在不得已要直接new一个Thread,记得设置它的name;
  3. 测试要覆盖尽可能多的场景,像这次的局域网环境就没覆盖到就有点不应该;
  4. 不管做什么功能,一定要有设计文档,代码一定要有注释,哪怕再简单,否则后人接手只能猜

一、Profiler

使用Profiler可以查看某一段时间内各个线程的堆栈,调试UI线程耗时简直爽到不行。使用也很简单(我也是第一次用来查找问题,虽然最后发现异常的线程看不到堆栈,但是很容易就上手了):

  1. 打开Profiler工具,点击左上角"SESSIONS"后面的"+"号选择要调试的设备和进程;
  2. 点击CPU,进入到详情后,可以选择"Sample Java Methods"等,然后点击"Record"
  3. 操作App,如果要看耗时,那么走一个流程,然后点击"Stop";
  4. 停止后可以选择不同的线程查看其堆栈信息。

如果有些时候没有Profiler用,通过ADB也可以查看线程状态(建议新建的线程都命名方便查看):

adb shell top -m 10 -t -d 5

二、Logcat日志

当遇到问题时,第一件事当然是看日志,一直以为logcat日志只是输出到Android Studio的
logcat…说出来有点丢人。

2.1 Logcat 输出到某个文件,如logcat.txt

# 这个文件可以是SD卡上的文件,也可以是通过adb连接的PC上的某个文件,注意"\"和"/"
adb logcat -v time >path/to/logcat.txt
# 输出到电脑的文件
adb logcat -v time >e:\logcat.txt
# 输出到SD卡的文件
adb logcat -v time >/sdcard/logcat.txt

如果要ADB断开后还能持续输出,那么需要输出到SD卡,并在后面加"&"符号

adb logcat -v time >/sdcard/logcat.txt&

2.2 anr日志

anr日志文件: data/anr/traces.txt,可以通过adb pull下来:

adb pull /data/anr/traces.txt

2.3 在app中输出logcat

有时候总有这么一种需求,必须记录app的所有日志,为了不丢失任何一行日志,只有下面这种方法了

class PrintLog {

    private val mContext: Context
    private var inputThread: ReadThread? = null
    private var errorThread: ReadThread? = null

    constructor(context: Context) {
        mContext = context
    }

    fun printLog(inputFile: File, errorFile: File) {
        val cmd = "logcat -v time"
        val processBuilder = ProcessBuilder(*arrayOf("sh", "-c", cmd))
        processBuilder.redirectErrorStream(true)
        try {
            val process = processBuilder.start()
            inputThread = ReadThread(mContext, process.inputStream, "InputStream", inputFile)
            inputThread?.start()
            errorThread = ReadThread(mContext, process.errorStream, "ErrorStream", errorFile)
            errorThread?.start()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    fun stop() {
        inputThread?.stop = true
        errorThread?.stop = true
    }

}

class ReadThread(context: Context, inputStream: InputStream, name: String, output: File) : Thread() {

    private val mContext: Context
    private var inputStream: InputStream
    private val mOutputLogFile: File
    var stop = false
    init {
        this.name = name
        this.inputStream = inputStream
        this.mContext = context
        this.mOutputLogFile = output
    }

    override fun run() {
        try {
            val buffer = ByteArray(1024 * 8)
            val writer = outputWriter
            while (!stop) {
                var length = inputStream.read(buffer)
                while (length != -1 && !stop) {
                    val tmp = ByteArray(length)
                    System.arraycopy(buffer, 0, tmp, 0, length)
                    val msg = String(tmp)
                    writer.write(msg)
                    length = inputStream.read(buffer)
                }
                try {
                    sleep(100)
                } catch (ex: Exception) {
                    ex.printStackTrace()
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private val outputWriter: BufferedWriter
        @Throws(Exception::class)
        get() {
            if (mOutputLogFile.exists()) {
                mOutputLogFile.delete()
            }
            mOutputLogFile.createNewFile()
            val fs = FileOutputStream(mOutputLogFile)
            val write = OutputStreamWriter(fs)
            return BufferedWriter(write)
        }

}

2.4 自定义输出文件log

有时候觉得logcat输出的日志太多,只想关注自己的日志,这时候,需要一个日志框架,
参考很久以前的博客一个Android Log框架
通过日志框架而不是直接使用logcat的好处就是扩展性强。

三、Stacktraces

有时候产品已上线,这时候找用户拿设备用Profiler分析明显是不可能的,这时候可以等用户主动反馈,
然后抓线程的堆栈,通过堆栈看能否分析出点什么问题。


fun printStacktrace() : String {
    val threadMap = Thread.getAllStackTraces()
    val stringBuilder = StringBuilder("stacktrace:")
    var enter = "\n"
    for (entry in threadMap) {
        val thread = entry.key
        stringBuilder.append("name: ").append(thread.name).append(enter)
        stringBuilder.append("id: ").append(thread.id).append(enter)
        stringBuilder.append("priority: ").append(thread.priority).append(enter)
        stringBuilder.append("state: ").append(thread.state.name)
        stringBuilder.append("isAlive:").append(thread.isAlive)
        stringBuilder.append("isInterrupted:").append(thread.isInterrupted)
        val stackElement = entry.value
        for (element in stackElement) {
            stringBuilder.append(element.className + ".")
                .append(element.methodName).append("(")
                .append(element.fileName).append(":")
                .append(element.lineNumber).append(")")
                .append(enter)
        }
        stringBuilder.append(enter)
    }
    return stringBuilder.toString()
}

解决问题的过程中百度了很多博客,因为时间紧迫就没有记下连接。

原创文章 25 获赞 12 访问量 1万+

猜你喜欢

转载自blog.csdn.net/half_bottle/article/details/103555528