QQ无法正常在EvolutionX ROM上正常运行的原因。暂无解决方案

手机型号:Motorola Edge 2023 + Pro / X40 / Edge 40,国行称为X40
CPU:高通骁龙8 Gen2
内存:12GB RAM
系统:EvolutionX 10.3 (Magisk Rooted)
安卓版本:15

首先骂两万字腾讯。

欧欧的耶,大的来喽,啊不是,大坨的来喽。

自从五月份刷了EvolutionX之后,QQ这毒瘤东西老给我整活。最新版本的,尤其是带UE4的,欸,你猜怎么着,用着用着卡死了。必须重启应用,一筹莫展的时候,群友提供了远古版本,能登录的8.2.11,然后发现这个版本登录之后可以无痛升级到8.8.50,加入UE4之前的最后一个版本。所以用了两个多月。但是企鹅有个非常傻比的策略:版本检测。内置的小程序和插件是可以使用的,比方说从网易云音乐分享一个专门的链接,那么我发过去的时候我是可以看到的。然后我再一退出重进该群聊页面,欸,你猜怎么着:

包括但不限于网易云音乐的链接卡片,其他东西也会这样。思考了一下,还是决定试试牺牲聊天记录,装回新版qq试试看。

初次分析:ANR日志文件

源文件就不放了,第一是找不到了,第二是几十万条日志文件,看也没用,就挑最关键的出来讲

把QQ更新到了最新版本。

然后打开,运行。果不其然,出现了和之前如出一辙的未响应问题。此刻第一次ANR发生。

证据1:

马上抓取了ANR报告,报告开头首先锁定:

Subject: Input dispatching timed out (f1706ce com.tencent.mobileqq/com.tencent.sqshow.zootopia.ZootopiaTransparentActivity is not responding. Waited 5000ms for MotionEvent).

这行日志明确指出,ANR发生在QQ的ZootopiaTransparentActivity这个界面。不用想,sqshow已经说明了这就是super qq show,UE4的超级qq秀。非核心的装饰功能能让整个应用直接瘫痪,我是甘拜下风了。

证据2:

继续往下看:

RssHwmKb: 1855424
RssKb: 1777556

RssHwmKb指的是Resident Set Size High Water Mark,即进程在生命周期中达到的最高物理内存占用。

1855424 KB ≈ 1.8 GB。

作为对比,以下是一些常用社交软件的平均RAM占用:

软件

空闲/后台

日常使用

重度使用

关键技术/特点

Telegram

50-100MB(仅维持一个轻量级的通知服务)

150 - 300 MB (流畅滚动聊天列表、浏览纯文字频道、发送图片)

400 - 600 MB (进行视频通话、快速滑动有大量GIF和视频的频道、同时下载多个大文件)

原生开发。出了名的轻量和高效,底层代码非常干净。

Discord

100-150MB(后台连接状态和通知服务)

250 - 450 MB (在几个服务器间切换、浏览文字频道、查看图片)

500 - 800+ MB (加入语音频道、屏幕分享/直播、观看嵌入的视频流)

React Native跨平台框架,性能和内存优于网页套壳,但是略逊于纯原生。

WhatsApp

60-120MB(后台推送服务,非常省电)

200 - 350 MB (收发消息、图片、小视频,进行语音通话)

400 - 650 MB (高清视频通话、在大型群组中快速浏览历史媒体文件)

原生开发。经过多年极致优化,性能稳定。

Signal

70-130MB(后台推送服务,加密会稍占用资源)

200 - 400 MB (日常加密聊天、发送图片和语音)

450 - 650 MB (加密视频通话、浏览大量媒体)

原生开发。安全优先,但性能同样出色。

Instagram

100-200MB(后台预加载内容、推送服务)

300 - 600 MB (流畅地刷信息流Feed、看快拍Story)

600 - 900+ MB (频繁刷Reels短视频、使用带AR滤镜的相机、开直播)

混合开发,原生+React Native,媒体密集型应用本身内存占用就高。

QQ

~200 - 400+ MB (预估) (即便在后台,大量非核心服务和SDK依然活动,导致极高

~600 MB - 1.2 GB+ (仅是常规聊天、浏览消息列表,内存就会因各种后台任务而失控增长)

~1.7 GB - 1.9 GB (ANR触发点) RssHwmKb: ~1885832

(这不是功能上的“重度使用”,而是内存泄漏达到临界点,触发全局GC风暴,导致所有线程卡死在 WaitForGcToCompleteLocked 的死亡状态)

巨石型“屎山”应用 (Monolithic Super-App) 强行集成UE4游戏引擎 (用于超级qq秀)、内嵌完整浏览器内核 (TBS/X5)、海量SDK (Bugly, 数据上报, 广告等)、脆弱的多进程架构 (:tool, :peak, :qzone)特征:极高的内存泄漏风险、进程间依赖死锁、灾难性的性能雪崩。

这是一个天文数字,对于一个移动App来说是极度病态的。这直接说明了存在严重的内存泄漏或极度不合理的内存使用。

证据3:

这还没完。继续往下看:

"encent.mobileqq" sysTid=9121
#01 pc ... (art::ConditionVariable::WaitHoldingLocks)
#02 pc ... (art::gc::Heap::WaitForGcToCompleteLocked)
#03 pc ... (art::gc::Heap::CollectGarbageInternal)
#04 pc ... (art::gc::Heap::AllocateInternalWithGc)

它显示主线程(UI线程)并非在执行自己的业务代码,而是在等待垃圾回收完成。因为内存占用实在太庞大,ART虚拟机不得不发起一场全局性的、耗时极长的GC。在这期间,所有线程(包括主线程)都被“Stop the world”,导致无法响应任何用户操作,最终触发ANR。

结论:

由此可以看出,QQ的卡死并不是偶发性的bug,尤其是在我的系统下如此多次的雪崩之后。这些证据表明,它存在由于UE4导致的、极其严重的内存泄漏,或者说,灾难性的内存雪崩。

对症下药:尝试药到病除

既然病灶已经明了,那么解决方案也很直接:禁用相关的组件。利用App Manager手动阉割掉这些问题模块。

清算清单:

  • sqshow / zootopia (超级qq秀)

  • vas / individuation (个性装扮,可选但是试试)

  • game / hippy (小游戏,小程序)

  • readinjoy / kandian(看点信息流)(看点都死多久了这东西还遗留着)

  • live / now (直播)

将这些模块禁用之后,ANR的确不再出现。这证明了方法是初步有效的。但是如果到这里就完了我就不会说暂无解决方案了。

再次雪崩:新的ANR

好景不长啊,好景不长。你个byd总是能给我整出点幺蛾子.jpg

ANR再次以新的方式出现,这次的日志指向了不同的目标。

证据4:

Process: com.tencent.mobileqq:peak
Subject: Process ProcessRecord{... com.tencent.mobileqq:peak/u0a715} failed to complete startup

这次出问题的不是主进程,而是专门负责处理图片和视频的:peak进程。不知道怎么发生的。继续往下看:

证据5:

"main" (peak进程主线程)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:230)
at com.tencent.qqnt.startup.NtStartup.c(P:368)

peak进程的主线程卡在了 CountDownLatch 上。这应该是一个同步工具,意味着它在傻等后台一个任务完成,然而那个任务因为某种原因被阻塞了,形成了启动依赖死锁。一环接一环,环环相扣的屎山……

证据6:

pid: 21272, tid: 21482, name: bugly_anr_dump >>> com.tencent.mobileqq <<<
signal 6 (SIGABRT), code -6 (SI_TKILL)
Abort message: '...'
backtrace:
  #00 pc ... libc.so (abort+160)
  #01 pc ... libc.so (free+108)
  #02 pc ... libBugly_Native.so 

问题就出在这里。Tombstone日志显示,在ANR发生后,一个名为bugly_anr_dump的线程,这应该是属于腾讯自家的Bugly崩溃上报SDK,在尝试工作时,自己发生了原生崩溃。崩溃点在free()函数,这是典型的C++内存错误。TM崩溃上报工具,却因为自身质量问题,直接把整个QQ进程都给干掉了,闪退。牛不牛逼你藤子哥。

结论:

这个时候我已经开始担忧了,因为这暴露了QQ极其脆弱的多进程启动依赖和质量堪忧的内置SDK。

说到这个就不得不提传世经典之你们是把马化腾的亲妈骨灰打包了吗

另辟蹊径:结果发现蹊径一样垃圾

QQ不行,那TIM总可以了吧。TIM总不会有那个沙币超级qq秀了吧,总不会卡死了吧。

会的,兄弟,会的:

证据7:

Process: com.tencent.tim
RssHwmKb: 898120
"com.tencent.tim" sysTid=29273
#02 pc ... art::gc::Heap::WaitForGcToCompleteLocked

屌爆了。WaitForGcToCompleteLocked,我估摸着应该只是换皮,TIM的底层跟QQ应该还是同一坨屎山,或者可能为了显示QQ的那一套东西还是加了什么我不知道的东西进去。

小附录:降级小提示

对于有root的朋友来说,降级安装理论上是相当简单的,就是不知道会不会把登录信息顶掉,还是把聊天记录备份一份为好(虽然备份了旧版QQ也没法同步,说要更新到最新版。沙币)。至于没有root的朋友……只能卸载重装了或者root。目前最推荐的做法是,从一些Archive网站,比如APKMirror,下载8.2.11这个远古版本,也就是常说的Google Play Store版本。登录,所有号包括小号都要登录,然后升级到还勉强能和现代兼容的8.8.50,UE4前的最后一个版本。如果QQ号因为风控原因什么的登不上……还是老实用原来的ROM和最新版吧。

事后诸葛亮:如果要优化,怎么做?

核心优化思路:解耦、异步化、懒加载。

一:在内存问题上刮骨疗毒

这是这破东西亟须解决的最大问题,因为这是所有问题的根源,必须下最大的死手。目的是把内存从1.8GB的离谱水平降到500MB以下的水平。

功能模块化与动态化

现状是,所有功能,无论用户是否使用,比如狗操的超级QQ秀,还有小游戏这些,都作为主APK的一部分被加载到RAM当中。

如何优化?你们菜,没事,但你们能学啊。能不能学学人家Google Play Feature Delivery或者国内应用商店的类似技术,将那些臃肿而且根本没什么人用的非核心功能,什么超级QQ秀他妈的我看到这东西就来火、直播入口、小游戏中心,甚至包括QQ空间都打包成动态功能模块,Dynamic Feature Modules。只有当用户第一次点击这个功能的时候,系统才会去下载并且加载这个模块。用户不用,它就永远不会占用内存和存储空间。这能从根本上砍掉大部分的常驻RAM。

严格的内存泄漏检测与修复

写过Cpp或者其他一些语言的应该都知道内存泄露一旦发生有多麻烦,什么野指针什么乱七八糟的,那就从根源把它给处理掉。

日志中显示,存在大量无法被GC回收的对象。那就在开发流程中强制集成 LeakCanaryLeakCanary是什么?官网介绍,是安卓的一个内存泄漏检测库,对症下药。

然后,使用 Perfetto或者 Android Studio Profiler对应用的内存使用进行深度分析,找出不合理的对象持有和Bitmap缓存滥用(这是大头问题)等。

针对Bitmap的话,可以全面采用专业的图片加载库,比如 Glide或者 Coil,并且需要配置严格的内存缓存和Bitmap池策略,避免大图甚至十几MB的原图直接被加载到内存当中。所有图片展示之前必须经过压缩。可以的话,做个懒加载吧求你了,虽然我也不知道你们现在写了没,但我估计是没写。写了估计对性能也没太多正优化,甚至可能是负优化。

重新设计缓存策略

现在可能存在大量无时限、无大小限制的内存缓存。那么,就全面审查所有的内存缓存,比如 LruCache和Map等等,然后为所有的缓存设置合理的大小上限。并且对非必要数据使用弱引用或者软引用,让它们在内存紧张的时候能被GC回收,而不是和个僵尸一样死在那占用内存甚至引发死锁。如果可以的话,引入 DiskLruCache,将非高频使用的数据放到磁盘上,而不是一直霸占着内存。

二:多进程架构的引导

多进程的确是必要的,但是现在的依赖关系混乱,非常容易引发奇怪的问题。目的是让IPC高效、可考、不阻塞。讲实话我觉得这应该是IPC设计的基本原则怎么你们都做不到

统一且异步化的IPC通道

现在可能同时存在大量的 AIDLMessenger或者 ContentProvider等多种IPC方式,而且存在大量的同步调用。那么就统一IPC实现,比如基于AIDL封装一套内部的框架,或者要么干脆全面转更现代的gRPC或者随便其他啥库。这IPC都啥时候的了还在用,就嗯留着呗。并且,绝对要严禁在任何UI线程或者启动关键路径上进行同步的IPC调用。所有的跨进程请求都必须是异步的,并且通过Callback或者响应式编程返回结果。 FlowRXJava都行啊。最不济的方法就是提供一个带Timeout的IPC调用,不要搞个无限期等待,全部完蛋。

服务按需启动和及时销毁

其实和内存回收的思路类似的。ANR和Tombstone日志中显示大量的 :tool, :peak, :qzone等等进程在后台活动,可能是由广播或者系统时间唤醒的。那么就废除大部分静态注册的广播接收器,改成在应用运行的时候动态注册。或者,使用WorkManager来处理所有可延迟的后台任务,比如日志上报、资源预下载等等,而不是他妈的启动一个完整的 Service或者进程。、 WorkManager和安卓集成可以让系统智能调度,更省电也更稳定。并且确保所有辅助进程在完成所有任务之后,都有明确的自毁机制。妈的Cpp基础课里面的构造和析构函数,一模一样的东西,你们这都不会??

三:启动时序的流程再造

让应用,尤其是主界面以最快速度展示给用户,而不是在启动的时候首先加载你那个唐氏儿超级QQ秀。除了主界面,其他所有的初始化,全部推迟。

启动任务依赖图谱化和懒加载

从日志来看, NtStartup是一大坨的串行或者半串行任务链,而且还存在循环等待的问题。则使用启动优化框架,比如隔壁阿里云的Alpha,Google的App Startup或者你们自己内部应该也有吧。有吧,有吗?把所有的启动任务定义为一个个独立的Task。并且明确声明每一个Task之间的依赖关系,框架会自动构建一个DAG,有向无环图,最大化地并行执行无依赖的任务。

尤其是,应该严格区分“必须在 Application.onCreate()中完成的任务”和“可以在首页展示后再执行的任务”。95%以上的初始化都应该被延后。关键是, DtSdkInitStep这种第三方SDK初始化你们也他妈放高优先级,比业余还业余,后台线程懒加载也不做,这必须是最低优先级的东西你们到底会不会啊。

UI渲染优化

SplashActivity有可能承载了过多的布局和初始化逻辑。想到了Minecraft Java的原版垃圾渲染器

那么就确保 SplashActivity 的布局极其简单,避免过度绘制。并且,使用 Jetpack Compose 重构部分复杂也面。Compose的声明式UI范式能从根本上提升渲染效率,减少不必要的View对象构建。同时利用 Profile Guided Optimization ,就是那个PGO和 Baseline Profiles 提前告诉Android系统,哪些代码是启动热点路径,让ART虚拟机在安装的时候就对其进行预编译和优化。

当然这个过程肯定痛苦又漫长……这是在“不完全推倒重写”的前提下,如果从现实角度考虑,还不如直接他妈的推倒重做一个。哦对,把这群KPI奴隶踢出去。学学人家谷歌、人家AWS人家Meta怎么做的,好吗。吃枣药丸啊。


为什么国产ROM就没事?

为什么像EvolutionX这样干净、流畅、接近原生安卓的AOSP ROM,反而伺候不好QQ/TIM,而国内厂商那些预装一堆应用、深度魔改的ROM,我们就叫它们国产ROM,却能让它看似流畅运行?其实答案很简单:国产ROM已经被QQ同化了。

话密了,不是这个意思,意思是,国产ROM特地为QQ这种屎山做了妥协。它们并非简单地支撑QQ流畅运行,它们是在妥协,用一种近似于举国体制的方式,为QQ这样的超级屎山续命。这不是说EvolutionX不好,恰恰相反,问题在于EvolutionX太标准、太公平了,它用一套标准的、源于AOSP的规则来对待所有App。而QQ/TIM这套代码,早就被垃圾软件生态惯坏了,它是在一个法外之地野蛮生长起来的,它期望甚至依赖于国产ROM提供的特权和后门,这套特权和后门依赖一旦到了干净的生态就会各种崩溃最后死掉。

国产ROM都做了什么?

首先我们要知道这些国产ROM的系统服务里,有一份写死的白名单。像微信、QQ、支付宝这种国民级应用,它们的进程优先级天生就比其他App高。系统内存再紧张, lowmemorykiller宁可杀掉十个普通应用,也绝不敢动QQ一下。同样,在识别到QQ是前台应用时,系统调度器会非常“智能”地将它的主线程和渲染线程死死地绑定在CPU的大核心上,并给予最高的频率,不惜让手机发烫也要保证其“流畅”。

更有甚者,部分ROM甚至会在开机后,就预先将QQ的部分核心服务加载到内存中,甚至锁定这部分内存,防止被系统回收。这也就是有的时候为什么QQ打开之后必须到后台强制终止的原因,而且一些系统里面关闭自启动也不好使。你的ROM和QQ一起在作祟。

但是EvolutionX不干。它遵循AOSP的公平调度原则。QQ对它来说,只是一个普通的App。当QQ在后台疯狂吃内存时,EvolutionX的 lowmemorykiller会一视同仁地考虑将它干掉,这就可能导致QQ的服务被杀后又重启,反复折腾。当前台运行时,CPU的分配也遵循标准的完全公平调度算法,不会给QQ任何特权。

同样的。为了解决安卓通知乱象,各家都搞了自己的Push服务。QQ/TIM会深度适配这些厂商通道,通过一个稳定、省电的TCP长连接接收消息,唤醒成本极低。厂商会向腾讯这类头部合作伙伴,开放一些未公开的系统API。比如更高效的文件读写权限、更底层的图形渲染接口、绕过标准Doze模式的唤醒能力等。QQ的代码里充满了对这些私有API的调用。还有不向普通用户开放的QQ文件权限,乱七八糟的东西全JB存本地你是人啊

那EvolutionX呢?它只有最基础的FCM(Firebase Cloud Messaging)推送支持,而且在国内等于残废。QQ在EvolutionX上,只能依赖自己“拉活”的流氓方式来保活,这本身就增加了系统负担和不稳定因素。当QQ尝试调用那些MIUI、ColorOS、HarmonyOS特有的私有API时,在EvolutionX上会直接失败,然后 fallback到一套效率低下的标准API上,甚至直接出错。日志中看到的各种启动依赖死锁,很可能就是某个后台任务在调用一个不存在的厂商后门时被卡住了。

还有一个挺好玩的。国产ROM以其极其严苛的后台查杀闻名。除了白名单里的应用,任何App只要在后台稍有动静,立刻就会被优化掉,俗称杀后台。这虽然牺牲了多任务体验,但客观上为QQ这样的前台应用创造了一个极其干净的内存环境,让QQ这个毒瘤根本察觉不到自己在内存泄漏。但是EvoX,它遵循安卓原生的后台管理逻辑,相对宽松。它允许应用在后台合理地存活。这就给了QQ这种有内存泄漏问题的App一个巨大的舞台,让它可以肆无忌惮地在后台泄漏内存,直到内存占用达到1.7-1.8GB的临界点,然后GC自爆。

究其原因,还是国内软件、硬件生态问题,甚至可以溯源到社会问题和消费主义盛行。厂商为了卖手机,必须保证国民App的流畅,于是不断为其开后门、喂资源。腾讯为了KPI,不断在App里堆砌功能,吃透厂商给的特权,代码越来越臃肿,越来越不遵守标准规范,甚至还要干掉任何尝试对它提出意见或者进行更改的第三方。著名的例子就是第三方BOT和LiteloaderQQNT最终的停止维护,因为随着版本更新,QQ加入了一些检测,具体不是很清楚,但反正就是对文件修改进行了检测,然后对对应账号进行风控。

这个恶性循环的结果是,App和ROM形成了一种畸形的“共生关系”。而像EvolutionX这样试图回归安卓本源、追求纯净和公平的“清流”,反而成了异类,因为它打破了这种“潜规则”。

Electron架构下的QQNT,是真的做到了NT。脑瘫。腾讯本身,倘若不是吃了发展早的红利,早就消失在历史的长河当中了。


QQ无法正常在EvolutionX ROM上正常运行的原因。暂无解决方案
http://localhost:50392/archives/qq-cannot-run-on-evox
作者
Cynsm
发布于
2025年07月24日
许可协议