Analysis and Fix of Android Memory Leak Caused by AsyncTask SerialExecutor Blocking on Vivo Devices
The analysis reveals that on Vivo devices running Android 9, a static AsyncTask SERIAL_EXECUTOR blocks the UpdateBottomFlagTask in several order‑related activities, leaking the Activity context, and the fix replaces this executor via reflection with a custom parallel SerialExecutorProxy to prevent the leak.
背景:APM平台显示得物Android交易线全部版本内存泄漏率在11.9号从2.13%涨到17.18%。
问题分析:通过内存泄漏分析发现泄漏集中在OrderConfirmActivityV2、OrderDetailActivityV2、BuyerShippingDetailActivity三个页面。进一步定位到AsyncTask的静态变量SERIAL_EXECUTOR导致任务队列阻塞,UpdateBottomFlagTask引用Activity Context无法释放。
引用链信息:AsyncTask.SERIAL_EXECUTOR → SerialExecutor.mTasks → ArrayDeque.elements → UpdateBottomFlagTask → AbsListView → BuyerShippingDetailActivity Context。
根源:Vivo 9.0 ROM在AbsListView中新增内部类UpdateBottomFlagTask(继承AsyncTask),在doInBackground中判断isSuperFloatViewServiceRunning()(判断是否存在截长屏服务SuperShotFloatViewService),若存在则更新系统Setting。由于AsyncTask的串行执行机制,前序耗时任务阻塞队列导致UpdateBottomFlagTask得不到及时执行,造成Context泄漏。
解决方案:通过反射替换AsyncTask的SERIAL_EXECUTOR为自定义的SerialExecutorProxy,使每个任务分配新线程执行(或仅对UpdateBottomFlagTask单独处理),避免队列阻塞。代码示例: class VivoLeakFixUtil { public static void initVivoLeakHander() { if ((Build.VERSION.SDK_INT == Build.VERSION_CODES.P) && isVivo()) { try { setFinalStatic(AsyncTask.class.getDeclaredField("SERIAL_EXECUTOR"), new SerialExecutorProxy()); Field defaultfield = AsyncTask.class.getDeclaredField("sDefaultExecutor"); defaultfield.setAccessible(true); defaultfield.set(null, AsyncTask.SERIAL_EXECUTOR); Timber.tag(TAG).d("initVivoLeakHander success"); } catch (Exception e) { Timber.tag(TAG).e(e); } } } private static boolean isVivo() { return Build.MANUFACTURER.contains("vivo") || Build.MANUFACTURER.contains("VIVO"); } private static void setFinalStatic(Field field, Object newValue) throws Exception { field.setAccessible(true); Field accessFlagsFiled = Field.class.getDeclaredField("accessFlags"); accessFlagsFiled.setAccessible(true); accessFlagsFiled.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); }}class SerialExecutorProxy implements Executor { private static final String TAG = "ActivityLRT"; private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public synchronized void execute(Runnable r) { String name = "thread---" + threadNumber.getAndIncrement(); Timber.tag(TAG).d("scheduleNext thread name = %s", name); new Thread(r, name).start(); }}
在Application初始化时根据配置中心开关调用VivoLeakFixUtil.initVivoLeakHander();
本地验证:在Demo中启动多个耗时AsyncTask阻塞队列,进行截长屏操作触发UpdateBottomFlagTask,未采用 fix 时发生内存泄漏;采用 fix 后任务并行执行,泄漏消失。
后续计划:通过埋点监控AsyncTask执行信息(线程状态、等待时间、开始/结束时间、执行时长),定位阻塞任务并进行优化。
DeWu Technology
A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.