捉虫记之TransactionTooLargeException

来源:互联网 发布:软件测试面试 编辑:程序博客网 时间:2024/06/06 05:08

问题背景

最近公司的Crash数据平台中出现很多次TransactionTooLargeException导致的Crash,这个Crash在小米、Vivo、Oppo三款机器上尤为凸显,但总的来说发生的几率不是很高,从数据来看我们200W日活的App,发生概率低于千分之二。这个问题属于典型的疑难杂症,原因有2点:一、概率很低我们很难复现,再加上我们使用的Crash收集平台本身的功能限制,我们无法取得比较详细的日志,更不知道用户做了哪些操作导致这个Crash的;二、因为我们自己无法重现这个问题,所以每次尝试性的改动都必须跟随一个灰度版本发布,然后再观察Crash数据,通常要一个星期才能确保有足够量的用户都更新了最新版本,换句话说我们每次尝试性的修复都要一个星期后才知道效果。

为什么概率这么低的Crash我们还要大费周折去修复呢,这里要说明一下我们的业务背景了。为了解决Android系统Webview在各个Android版本以及设备上的体验差异,我们使用了Crosswalk这个开源的Web引擎,Crosswalk是基于Chromium和Blink的开源Web引擎,主要解决系统原生Webview的性能、H5兼容以及体验差异问题,关于Crosswalk的详细信息可以参考官网。回到TransactionTooLargeException这个问题中来,通过观察线上数据,我们开启Crosswalk后,这个Crash数量就激增;而使用系统Webview,几乎不会发生这个问题。所以基本可以确定是我们对Crosswalk使用不当导致的。 为了能再我们的产品中使用Crosswalk,同时不能对Crash率影响太多,我们必须修复这个问题,这个过程很蛋疼,这篇文章记录了我们解决这个问题的思路。


关于TransactionTooLarge

首先看一下什么是TransactionTooLargeException,什么情况下会发生这个问题,官方文档解释的很清楚:

"During a remote procedure call, the arguments and the return value of the call are transferred as Parcel objects stored in the Binder transaction buffer. If the arguments or the return value are too large to fit in the transaction buffer, then the call will fail and TransactionTooLargeExceptionwill be thrown. The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process. "  

简单地说,就是Binder通信数据Buffer超过系统限制了(目前是不能超过1M)。 看一下我们收集到的TransactionTooLargeException的堆栈:

java.lang.RuntimeException: Adding window failed       at android.view.ViewRootImpl.setView(ViewRootImpl.java:531)       at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:269)       at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)       at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3070)       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2334)       at android.app.ActivityThread.access$1100(ActivityThread.java:141)       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1242)       at com.mogujie.commanager.internal.hack.MGJHDelegate.sendToSystem(MGJHDelegate.java:190)       at com.mogujie.commanager.internal.hack.MGJHDelegate.handleMessage(MGJHDelegate.java:175)       at android.os.Handler.dispatchMessage(Handler.java:98)       at android.os.Looper.loop(Looper.java:136)       at android.app.ActivityThread.main(ActivityThread.java:5290)       at java.lang.reflect.Method.invokeNative(Method.java)       at java.lang.reflect.Method.invoke(Method.java:515)       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:859)       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:675)       at dalvik.system.NativeStart.main(NativeStart.java)Caused by: android.os.TransactionTooLargeException       at android.os.BinderProxy.transact(Binder.java)       at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:701)       at android.view.ViewRootImpl.setView(ViewRootImpl.java:520)       at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:269)       at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)       at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3070)       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2334)       at android.app.ActivityThread.access$1100(ActivityThread.java:141)       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1242)       at com.mogujie.commanager.internal.hack.MGJHDelegate.sendToSystem(MGJHDelegate.java:190)       at com.mogujie.commanager.internal.hack.MGJHDelegate.handleMessage(MGJHDelegate.java:175)       at android.os.Handler.dispatchMessage(Handler.java:98)       at android.os.Looper.loop(Looper.java:136)       at android.app.ActivityThread.main(ActivityThread.java:5290)       at java.lang.reflect.Method.invokeNative(Method.java)       at java.lang.reflect.Method.invoke(Method.java:515)       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:859)       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:675)       at dalvik.system.NativeStart.main(NativeStart.java

我不知道别人看这堆栈啥感觉,反正我看完后心中有一群草泥马奔过,完全不知道是啥意思。网上也有其他人遇到过这个问题,比如这个。大部分人的问题都是因为确实使用Binder传了大量的数据,比如图片之类的,这种情况下其实是比较好解决的。我们的问题比较复杂一点,你很难再Chromium内核的代码中找到那里用Binder用出问题了。


解决思路

1. removeAllViews

解决这个问题我们主要的思路就是如何尽可能的释放Chromium占用的系统资源,包括内存、GPU缓存、Binder buffer等。也有一些网友在使用Webview的时候遇到过TransactionTooLargeException,他的解决办法是我们可以借鉴的,那篇文章在这里。主要思路就是在包含Webview的Activity destroy时调用removeAllViews,代码片段如下:

@Overrideprotected void onDestroy() {if (mWebView != null) {((ViewGroup) mWebView.getParent()).removeView(mWebView);  mWebView.removeAllViews();  mWebView.destroy();}super.onDestroy();}
加上了这个逻辑以后,小米上的问题修复了,但很多机器上问题仍然存在。

2. 释放GPU缓存

还是一样的思路,怎么尽可能多一点地释放Chromium占用的系统资源,腾讯的一位工程师的这篇文章中介绍了使用反射的方法,调用系统内部接口强制释放GPU缓存。这个加上以后效果没有特别明显,怀疑是我们用法又问题,后面还会进一步研究。

WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);

3. 拦截TransactionTooLargeTooLarge

1) 怎么拦截TransactionTooLarge:TransactionTooLarge异常对App开发者来说是不可见的,从上面的堆栈可以看出ViewRootImpl.setView已经拦截处理了这个异常,然后抛出的是RuntimeException. 

2) 如何拦截ViewRootImpl.setView抛出的RuntimeException异常:ViewRootImpl是Android Framework的窗口管理相关逻辑,应用层是不能直接Catch到这个异常的。就算Catch到这个异常,又如何判断这个异常是由于TransactionTooLargeException导致的呢?

3) 如果拦截住了TransactionTooLargeException,后面要怎么做才能确保App不会Crash?

4) 怎么模拟出TrasctionTooLargeException来调试拦截方案?


0 0
原创粉丝点击