先来了解下一个空白app的进程中线程数量:
分析迷你世界中app线程之前,先来了解下空白app ,中线程情况:
一个空白app的线程大概是27个:
一个空白的的app,包含FinalizerDaemon线程、FinalizerWatchd线程、RenderThread、主线程、ReferenceQueued、HeapTaskDaemon、SignalCatcher、GCDaemon、hwuiTask。
游戏中进程线程状况
游戏的进程中线程数大概在200个左右
为了搞清楚这些线程分别是来源于哪个模块,哪个sdk ,因此考虑采用epic hook thread ,在其调用启动的打印调用栈。
这里以mobpush推送的线程为案例:
Mobpush sdk 的线程,带有M-前缀的线程名:
按线程名和调用栈,去统计每个模块/sdk中情况,这里列举几个线程大户的sdk。
推送sdk带来的线程
x5浏览器sdk 带来的线程
广告带来的线程:
目前游戏中线程优化的棘手点:
- Android 带有的消息异步线程:HandlerThread或者带有loooper的线程占有常驻线程,无法处理。
- 存在广告sdk和x5 中存在热加载的外来dex,无法进行优化。
- c++ (即游戏侧)中走的pthread库,目前无法监控,处理。
领导也是给到压力,要优化线程数,减少线程数
线程优化方案
为了进一步减少线程 OOM的发生,采用对线程池(常驻的核心线程)进行超时销毁策略。
1.针对自身业务的线程池进行优化
- 尽量使用kotlin协程或者rxjava 等,减少常驻的线程池
- 对线程池组件的线程池,也设置核心线程可关闭
对第三方sdk或开源库中线程池,使用Booster 进行优化
重点:让空闲的核心线程结束,当下次有任务会再次创建线程,这是一种有效的收敛线程的解决方案,booster 对整个apk 中线程代码进行替换优化。
在根目录的build中使用方式:
classpath "com.didiglobal.booster:booster-transform-thread:$booster_version"
通过对编译apk 进行查看,booster 对线程做了哪些逻辑操作:
1.将每个线程添加了添加了名字,方便追踪问题
2.是将一些keepAliveTime>0的线程,设置允许核心线程数结束。
对ThreadPoolExecutor的匿名类进行替换,允许核心线程超时结束。
对AsyncTask 线程池设置,允许核心线程超时结束。
对Executors中 newCachedThreadPool()、newSingleThreadExecutor()、newScheduledThreadPool() 三种线程池进行优化,允许核心线程超时结束。
重点:让空闲的核心线程结束,当下次有任务会再次创建线程,这是一种有效的收敛线程的解决方案,booster 对整个apk 中线程代码进行替换优化。
优化后的效果
经过以上方式,游戏的线程数量稳定在192左右,大概能减少常驻核心线程10多个。
后期也会考虑使用多进程的方式,将线程大户的sdk放到单独进程中去,比如web进程,减少主进程中线程压力,毕竟一个线程占用1M的虚拟内存。
若是考虑黑科技优化线程,可使用腾讯的matrix 和阿里阿里开源 Patrons,进一步优化。