1.抛一个问题
这一天,法海想锻炼小青的定力,由于Bitmap也是一个Parcelable类型的数据,法海
想通过Intent
给小青
传个特别大的图片
intent.putExtra("myBitmap",fhBitmap)
如果“法海”
(Activity)使用Intent去传递一个大的Bitmap
给“小青”
(Activity),如果你的图片够大,会出现类似下面这样的错误,请继续往下看:
Caused by: android.os.TransactionTooLargeException: data parcel size 8294952 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:535)
at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3904)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1738)
至于是什么样的大图,这个只有法海知道了(小青:好羞涩啊)
🙈🙈🙈
所以TransactionTooLargeException这玩意爆出来的地方在哪呢?
2.问题定位分析
我们可以看到错误的日志信息里面看到调用了BinderProxy.transactNative
,这个transactNative是一个native方法
//android.os.BinderProxy
/**
* Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
在Android Code Search,全局搜索一下:android_os_BinderProxy_transact
//frameworks/base/core/jni/android_util_Binder.cpp
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
......
status_t err = target->transact(code, *data, reply, flags);
......
if (err == NO_ERROR) {
//如果匹配成功直接拦截不往下面执行了
return JNI_TRUE;
} else if (err == UNKNOWN_TRANSACTION) {
return JNI_FALSE;
}
signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
return JNI_FALSE;
}
我们打开signalExceptionForError方法看看里面的内容
//frameworks/base/core/jni/android_util_Binder.cpp
//处理异常的方法
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
bool canThrowRemoteException, int parcelSize)
{
switch (err) {
//其他异常,大家可以自行阅读了解;
//如:没有权限异常,文件太大,错误的文件描述符,等等;
........
case FAILED_TRANSACTION: {
const char* exceptionToThrow;
char msg[128];
//官方在FIXME中写道:事务过大是FAILED_TRANSACTION最常见的原因
//但它不是唯一的原因,Binder驱动可以返回 BR_FAILED_REPLY
//也有其他原因可能是:事务格式不正确,文件描述符FD已经被关闭等等
//parcelSize大于200K就会报错,canThrowRemoteException传递进来的是true
if (canThrowRemoteException && parcelSize > 200*1024) {
// bona fide large payload
exceptionToThrow = "android/os/TransactionTooLargeException";
snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
} else {
..........
}
//使用指定的类和消息内容抛出异常
jniThrowException(env, exceptionToThrow, msg);
} break;
........
}
}
此时我们看到: parcelSize大于200K就会报错,难道一定是200K以内?先别着急着下结论,继续往下看👇👇
3.提出疑问
法海:我有个疑问,我看到文档写的1M大小啊;
许仙:别急,妹夫,来先看一下文档的解释,看一下使用说明:
官方TransactionTooLargeException的文档中描述到:Binder 事务缓冲区有一个有限的固定大小,目前为 1MB,由进程所有正在进行的事务共享
可以看到写的是:共享事务的缓冲区
如来佛祖:汝等别急,我们简单测试一下,Intent传递201*1024
个字节数组,我们发现可以正常传递过去,Logcat仅仅输出了一个Error提示的日志信息,还是可以正常传递的
E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 205848, icicle size: 0
我们再测试一个值,intent传递800*1024
个字节数组,我们发现会崩溃
android.os.TransactionTooLargeException: data parcel size 821976 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:540)
at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction