美文网首页Android开发Android开发
记录Intent跨进程传输引发的TransactionTooLa

记录Intent跨进程传输引发的TransactionTooLa

作者: 巴黎没有摩天轮Li | 来源:发表于2020-08-16 17:29 被阅读0次

前言

我们都知道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这个源码文件


frameworks/base/core/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 及以下并没有出现这个问题,代码都是一样的,所以我去查询了一下官方文档,最终找到如下


Android7.0行为变更

仅仅针对大图片传输的解决办法

1、采用Bundle#putBinder()方法
2、socket方式 不过会拷贝两次 性能不好
3、传递图片路径 网络路径或者磁盘
4、图片分片传递再重组 太麻烦

总结

产生TransactionTooLargeException的根本原因就是传输大小超过了200k的限制,不过有很多问题比如在Fragment#saveInstance中保存数据,Dialog相关也都可能会产生这个问题,解决点无一例外减小传输数据的大小。

相关文章

网友评论

    本文标题:记录Intent跨进程传输引发的TransactionTooLa

    本文链接:https://www.haomeiwen.com/subject/mgufdktx.html