记录Intent跨进程传输引发的TransactionTooLa
2020-08-16 本文已影响0人
巴黎没有摩天轮Li
前言
我们都知道Activity启动过程是通过跨进程进行跳转的,携数据跳转Activity时,简单点说是APP进程传输到AMS进程,再由AMS进程传输到APP目标Activity进程,而且这个目标Activity进程也可能时单独的一个进程,因此我们通过Intent进行数据传输的过程其实也是一种跨进程传输,但是有没有想过传递的数据包大小是否有大小限制呢?
测试代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.RGB_565
val skyBitmap = BitmapFactory.decodeResource(resources, R.drawable.sky, options)
Log.d("BIT_COUNT", skyBitmap.byteCount.toString())
val intent = Intent()
intent.setClass(this, MainActivity::class.java)
intent.putExtra("BITMAP", skyBitmap)
startActivity(intent)
}
}
异常结果
2020-08-11 14:32:09.879 31782-31782/com.junlong0716.myintent E/AndroidRuntime: FATAL EXCEPTION: main
Caused by: android.os.TransactionTooLargeException: data parcel size 74342992 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:510)
at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3932)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1716)
at android.app.Activity.startActivityForResult(Activity.java:5260)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:676)
at android.app.Activity.startActivityForResult(Activity.java:5218)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:663)
at android.app.Activity.startActivity(Activity.java:5589)
at android.app.Activity.startActivity(Activity.java:5557)
at com.junlong0716.myintent.MainActivity.onCreate(MainActivity.kt:22)
at android.app.Activity.performCreate(Activity.java:7894)
at android.app.Activity.performCreate(Activity.java:7881)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3279)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3443)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2040)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7520)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
看来跨进程传输数据并不是想怎么传就怎么传的,至少我们现在根据异常名称知道传输数据的大小是有限制的。出现这个异常的原因本质就是通过Binder驱动传输的Parcel大小超过200kb,就会出现这个问题,很多场景都会有这个问题,而且在Bugly上还不太好进行定位排查。
寻找调用链
Parcelable序列化
data class MusicInfo(var id: Int, var name: String, var path: String) : Parcelable {
fun readFromParcel(source: Parcel) {
id = source.readInt()
name = source.readString()!!
path = source.readString()!!
}
constructor(source: Parcel) : this(
source.readInt(),
source.readString()!!,
source.readString()!!
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeInt(id)
writeString(name)
writeString(path)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<MusicInfo> = object : Parcelable.Creator<MusicInfo> {
override fun createFromParcel(source: Parcel): MusicInfo = MusicInfo(source)
override fun newArray(size: Int): Array<MusicInfo?> = arrayOfNulls(size)
}
}
}
Parcel#writeString()
static native void nativeWriteString(long nativePtr, String val);
可以看到最终调用了jni方法,我们来找下android_os_Parcel.cpp这个源码文件

继续向下探索
static void android_os_Parcel_writeString(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
status_t err = NO_MEMORY;
if (val) {
const jchar* str = env->GetStringCritical(val, 0);
if (str) {
err = parcel->writeString16(
reinterpret_cast<const char16_t*>(str),
env->GetStringLength(val));
env->ReleaseStringCritical(val, str);
}
} else {
err = parcel->writeString16(NULL, 0);
}
if (err != NO_ERROR) {
// 走一波异常判断
signalExceptionForError(env, clazz, err);
}
}
}

最终发现传输失败原因是超过最大范围了。
但是我在模拟这个异常的时候,发现Android7.0以上才会复现出来,Android 6.0 及以下并没有出现这个问题,代码都是一样的,所以我去查询了一下官方文档,最终找到如下

仅仅针对大图片传输的解决办法
1、采用Bundle#putBinder()方法
2、socket方式 不过会拷贝两次 性能不好
3、传递图片路径 网络路径或者磁盘
4、图片分片传递再重组 太麻烦
总结
产生TransactionTooLargeException的根本原因就是传输大小超过了200k的限制,不过有很多问题比如在Fragment#saveInstance中保存数据,Dialog相关也都可能会产生这个问题,解决点无一例外减小传输数据的大小。