一. 背景
输入法在V2.30(185)和V2.35(196)两个版本中都碰到了大量的TransactionTooLargeException的崩溃问题,其中崩溃的堆栈有所不同:
- V2.30:android.app.ApplicationPackageManager.getInstalledPackages(ApplicationPackageManager.java:464)
- V2.35:android.view.inputmethod.InputMethodManager.getEnabledInputMethodList(InputMethodManager.java:602)
最终通过查找代码中的改动点,发现其中的原因就是:多个线程同时调用了以下的接口:
- List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
- List<RunningTaskInfo> tasksInfo = activityManager.getRunningTasks(1);
- List<PackageInfo> appInfoList = pkgManager.getInstalledPackages(0);
那么为什么调用这些接口会出现这个异常呢,我们从异常出现的原因为出发点进行讲解。
二. TransactionTooLargeException的原因
从android的开发文档(
http://developer.android.com/reference/android/os/TransactionTooLargeException.html)中我们了解到该异常的描述:
因为Binder太大了导致传输失败。
当调用远程(跨进程)方法时,方法所带的参数以及返回值多会以parcel对象保存在Binder传输缓冲区进行传输的。如果参数或者返回值所占的内存超过传输缓冲区,调用将会失败同时会抛出TransactionTooLargeException异常。
Binder传输缓冲区是同一进程中所有传输操作所共享的,大小为1Mb。因此当进程中同一时刻,如果有大量的跨进程方法调用时,就要注意了。
从以上异常的描述,可以总结出现该异常的原因:
- 跨进程方法调用
- 调用的方法带的参数或者返回值占用较多内存
- 同一时刻方法调用次数太多
三. 输入法为何出现TransactionTooLargeException
从TransactionTooLargeException出现的原因可知:
- 跨进程方法调用
查看android的框架原理机制了解android的框架机制是通过IPC的机制去实现各个服务的管理,所有服务管理对象都是跨进程的调用,如:
PackageManager,InputMethodManager,ActivityManager都是通过binder传输进行跨进程调用
- 调用的方法带的参数或者返回值占用较多内存
获取用户所有安装应用列表,如果用户安装大量应用的情况下,数据量还是比较大
- 同一时刻方法调用次数太多
输入法启动时会调用2次getInstalledPackages,广告sdk智能预加载每隔10s会调用一次getRunningAppProcesses或者getRunningTasks,调用5-6次getInstalledPackages
四. 如何避免
了解问题出现的条件后,该问题就迎刃而解,同时在项目开发过程中就需要避免问题的发生,整理的方法如下:
- 跨进程方法调用时尽量减少参数或者返回值的内存占用
如:getInstalledPackages使用过滤参数减少返回结果的内存占用
- 不要多个线程同时调用跨进程方法,尽量减少调用次数或者重复利用调用的结果
如: 整个项目中共用一份安装列表,减少getInstalledPackages的调用次数; 如果同一时刻需要调用getInstalledPackages,getRunningAppProcesses,getRunningTasks等方法,请使用同一个线程进行操作