阅读 525

TT语音线程优化

前言

TT语音每次启动app到进入首页就会创建240个左右的线程,其中有接近一半是获取SharePreferences对象所创建的线程,这些线程很多都是一次性的,用完后没有立即销毁,这样会占用内存,而且,频繁的创建线程,可能会导致线程数过多造成OOM,因此,需要找到可优化的空间,然后尽可能优化这些线程,但是,如果要找到优化的空间,就需要了解整个app的线程使用情况,监听到哪个页面,哪行代码,创建了哪个线程,才能给我们的优化带来准确的方向

监控方案

通过AOP运行时插桩的方式,这里借助了epic,监听APP中所有线程的创建,可以监听到哪个页面,哪行代码,创建了那个线程,然后通过日志方式打印创建的线程信息,代码如下

class ThreadCheck : XC_MethodHook() {

    var isCanAppendLog = false

    override fun afterHookedMethod(param: MethodHookParam?) {
        super.afterHookedMethod(param)

        if (param?.thisObject is Thread) {
            val thread = param.thisObject as Thread
            val es = Thread.currentThread().stackTrace

            val normalInfo = StringBuilder(" \nThreadTrace:")
                    .append("\nthreadName:${thread.name}")
                    .append("\n====================================threadTraceStart=======================================")

            for (e in es) {

                if (e.className == "dalvik.system.VMStack" && e.methodName == "getThreadStackTrace") {
                    isCanAppendLog = false
                }

                if (e.className == "me.weishu.epic.art.entry.Entry" && e.methodName == "referenceBridge") {
                    isCanAppendLog = true
                } else {
                    if (isCanAppendLog) {
                        normalInfo.append("\n${e.className}(methodName:${e.methodName},lineNumber:${e.lineNumber})")
                    }
                }
            }
            normalInfo.append("\n=====================================threadTraceEnd=======================================")
            Log.i(tag, normalInfo.toString())
        }
    }

    companion object {
        const val tag = "====>ThreadCheck"

        fun hook() {

            if (!BuildConfig.DEBUG) {
                return
            }

            DexposedBridge.hookAllConstructors(Thread::class.java, ThreadCheck())
        }
    }

}
复制代码

日志打印格式

image.png

从上图结合下图可以看出在下面的类创建了AsyncTask,而且使用完后没有立即销毁,造成了内存的浪费,而且,频繁的创建线程可能会导致APP因为线程数量过多造成OOM

image.png

分析

image.png

image.png

如上图所示,TT语音在android8.0以上的机型上出现的大量这种类似的OOM,这是因为线程数量过多或者虚拟内存不足造成的,创建线程是需要消耗虚拟内存的,不同机型允许的最大线程数量是不一样的,例如在华为的部分机型上,这个上限被修改的很低(大约500),需要注意的是,APP的线程总数=工作中的线程数+sleep状态的线程数,所以这些手机容易出现线程数溢出的问题,而TT语音一进入首页就会大概创建240个左右的线程,虽然这里面有很多线程都是很快就工作结束的,然后变成无用线程的,也就是说这些工作结束后的线程并不会计入APP的线程总数中,但是,频繁的创建线程依然可能会导致上图中的OOM,因为尽管他们工作时间很短,但是依然可能是压垮骆驼的最后一根稻草,而且,频繁的创建线程,会造成虚拟内存的消耗,加大OOM的可能性,因此,尽量减少线程的创建是线程优化的关键一步,然后,开始统计线程使用情况,情况如下:

线程使用情况

1. 代码中使用 new Thread或者new AsyncTask或者new HandlerThread创建的线程,例如上图中创建了AsyncTask
2. 获取SharePreferences对象的时候创建的线程,每次获取SharePreferences对象的时候都会重新创建线程,原因是我们获取SharePreferences对象的模式是多进程模式,这个情况下每次获取SharePreferences对象的时候都会创建新线程
3. 代码中协程创建的线程
4. 第三方sdk创建的线程以及获取SharePreference对象创建的线程
5. 代码中使用线程池创建的线程
6. Maven依赖库创建的线程
复制代码

优化方式

  1. 获取SharePreferences对象的时候考虑数据是否在多个进程中使用,如果不是,将模式改成MODE_PRIVATE,这样就不会每次获取SharePreferences对象的时候都重新创建线程
  2. 对于代码中使用new Thread或者new AsyncTask或者new HandlerThread创建的线程,建议改成使用线程池或者协程来复用线程

QQ交流群

770892444

文章分类
Android
文章标签