在学习时,看到公司的规范里有句话:
Activity间的数据通信,对于数据量比较大的,避免使用intent+Parcelable的方式,可以考虑EventBus等代替方法,以免造成TransactionTooLargeException;
因为刚开始,不太了解内部的原理,如果只是死记硬背肯定是不行的,因此想对该问题做一个深入的分析:
1、首先需要理解intent时如何传递数据的。
通常,我们在Activity中传递数据,都是将数据方法Intent中,然后调用startActivity或startActivityForResult来拉起另一个Activity的。
Intent mSecondIntent = new Intent(MainActivity.this, SecondActivity.class);
byte[] val = new byte[1024*10];
mSecondIntent.putExtra("byteArr", val);
startActivity(mSecondIntent);
这里的putExtra()有多个重载,可以实现不同数据的传递,通过源码我们可以看到:
public @NonNull Intent putExtra(String name, @Nullable byte[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putByteArray(name, value);
return this;
}
实际上是通过一个内部变量mExtras,将其存储在Bundle中的。
所以传递数据就算通过Intent来实现的。
2、通过Intent+Parcelable数据:
Parcelable是Android为我们提供的序列化(将对象转换为可以传输的二进制流/序列)接口:public interface Parcelable{}//它是一个接口,并且通过Android源码注释,我们需要定义自己的Parcelable类来实现它。实际上就是通过Parcel写入和回复数据。需要实现describeContent()、writeToParcel(),以及反序列化----定义一个变量CREATOR,通过匿名内部类实现Parcelable中的Creator接口。
Android的Pracelable的注释中有一个简单的使用例子:
/**
* Interface for classes whose instances can be written to
* and restored from a {@link Parcel}. Classes implementing the Parcelable
* interface must also have a non-null static field called <code>CREATOR</code>
* of a type that implements the {@link Parcelable.Creator} interface.
*
* <p>A typical implementation of Parcelable is:</p>
*
* <pre>
* public class MyParcelable implements Parcelable {
* private int mData;
*
* public int describeContents() {
* return 0;
* }
*
* public void writeToParcel(Parcel out, int flags) {
* out.writeInt(mData);
* }
*
* public static final Parcelable.Creator<MyParcelable> CREATOR
* = new Parcelable.Creator<MyParcelable>() {
* public MyParcelable createFromParcel(Parcel in) {
* return new MyParcelable(in);
* }
*
* public MyParcelable[] newArray(int size) {
* return new MyParcelable[size];
* }
* };
*
* private MyParcelable(Parcel in) {
* mData = in.readInt();
* }
* }</pre>
*/
public interface Parcelable {
所以,Parcelable就算用来传递对象或对象数组的时候用到的东西,实际上最后传递数据时,还是和前面说到的一样,通过Intent.putExtrac()将其放到Intent中,然后调用startActivity()将其传递出去;
3、为何大数据不能用Intent+Parcelable
实际上就算不能用Intent,因为不管是否Parcelable对象,只要数据足够大的时候,都不能用Intent,我们使用byte模拟下传递1M数据的场景:(为什么1M后面会说)
Intent mSecondIntent = new Intent(MainActivity.this, SecondActivity.class);
byte[] val = new byte[1024*1024];
mSecondIntent.putExtra("byteArr", val);
startActivity(mSecondIntent);
会报如下错误:
2021-06-05 14:32:39.264 20256-20256/com.example.myapplication E/JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 1049016 2021-06-05 14:32:39.264 20256-20256/com.example.myapplication D/AndroidRuntime: Shutting down VM 2021-06-05 14:32:39.267 20256-20256/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.myapplication, PID: 20256 java.lang.RuntimeException: Failure from system at android.app.Instrumentation.execStartActivity(Instrumentation.java:1711) at android.app.Activity.startActivityForResult(Activity.java:5192) at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:676) at android.app.Activity.startActivityForResult(Activity.java:5150) at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:663) at android.app.Activity.startActivity(Activity.java:5521) at android.app.Activity.startActivity(Activity.java:5489) at com.example.myapplication.MainActivity.lambda$onCreate$0$MainActivity(MainActivity.java:38) at com.example.myapplication.-$$Lambda$MainActivity$jRtpOGtrw8cqMNyhtDGNK0XOTPE.onClick(Unknown Source:2) at android.view.View.performClick(View.java:7125) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) at android.view.View.performClickInternal(View.java:7102) at android.view.View.access$3500(View.java:801) at android.view.View$PerformClick.run(View.java:27336) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) Caused by: android.os.TransactionTooLargeException: data parcel size 1049016 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:3847) at android.app.Instrumentation.execStartActivity(Instrumentation.java:1705) at android.app.Activity.startActivityForResult(Activity.java:5192) at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:676) at android.app.Activity.startActivityForResult(Activity.java:5150) at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:663) at android.app.Activity.startActivity(Activity.java:5521) at android.app.Activity.startActivity(Activity.java:5489) at com.example.myapplication.MainActivity.lambda$onCreate$0$MainActivity(MainActivity.java:38) at com.example.myapplication.-$$Lambda$MainActivity$jRtpOGtrw8cqMNyhtDGNK0XOTPE.onClick(Unknown Source:2) at android.view.View.performClick(View.java:7125) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) at android.view.View.performClickInternal(View.java:7102) at android.view.View.access$3500(View.java:801) at android.view.View$PerformClick.run(View.java:27336) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
2021-06-05 14:32:39.275 20256-20256/com.example.myapplication I/Process: Sending signal. PID: 20256 SIG: 9
可以看到这是一个 Caused by: android.os.TransactionTooLargeException: 异常,我们先看TransactionToolLargeException这个异常定义,在frameworks/base/core/java/android/os/TransactionTooLargeException.java文件中:
The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process.
文件中注释可以看到,我们现在限制Binder transaction的内存大小就为1M。
所以这个应该是因为传递过程中,数据量超过了Binder所能传递的最大内存,报TooLarge异常。
4、startActivity()如何传递数据的:
根据堆栈信息和源码,我们调用startActivity()后,会调到Activity.startActivity()。最后在Instrumentation.java#execStartActivity()中调用ActivityTaskManager.getService().startActivity();
在这个过程中,Intent都是作为参数传递进去的,过程中可能还会往里面添加一些数据。
在ActivityTaskManager.getService()会得到一个IActivityTaskManager.Stub.Prox的Binder代理对象(这个和Binder通信有关,在android/app/目录下存在IActivityTaskManager.aidl文件,这个Java端的Prox就算通过aidl自动生成的)
我们可以模拟该情况,自己写一个aidl文件,其中Intent参数作为in:
// IActivityTaskManager.aidl
package com.example.myapplication;
import android.content.Intent;
/**
* System private API for talking with the activity task manager that handles how activities are
* managed on screen.
*
* {@hide}
*/
interface IActivityTaskManager {
//int startActivity(int a);
int startActivity(in Intent intent);
}
使用AS自动生成java文件中的startActivity()如下所示:
package com.example.myapplication;
/**
* System private API for talking with the activity task manager that handles how activities are
* managed on screen.
*
* {@hide}
*/
public interface IActivityTaskManager extends android.os.IInterface
{
/** Default implementation for IActivityTaskManager. */
public static class Default implements com.example.myapplication.IActivityTaskManager
{
//int startActivity(int a);
@Override public int startActivity(android.content.Intent intent) throws android.os.RemoteException
{
return 0;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.myapplication.IActivityTaskManager
{
private static final java.lang.String DESCRIPTOR = "com.example.myapplication.IActivityTaskManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.myapplication.IActivityTaskManager interface,
* generating a proxy if needed.
*/
public static com.example.myapplication.IActivityTaskManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.myapplication.IActivityTaskManager))) {
return ((com.example.myapplication.IActivityTaskManager)iin);
}
return new com.example.myapplication.IActivityTaskManager.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_startActivity:
{
data.enforceInterface(descriptor);
android.content.Intent _arg0;
if ((0!=data.readInt())) {
_arg0 = android.content.Intent.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
int _result = this.startActivity(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.myapplication.IActivityTaskManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
//int startActivity(int a);
@Override public int startActivity(android.content.Intent intent) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((intent!=null)) {
_data.writeInt(1);
intent.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_startActivity, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().startActivity(intent);
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.example.myapplication.IActivityTaskManager sDefaultImpl;
}
static final int TRANSACTION_startActivity = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.example.myapplication.IActivityTaskManager impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.myapplication.IActivityTaskManager getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
//int startActivity(int a);
public int startActivity(android.content.Intent intent) throws android.os.RemoteException;
}
在这个里面,如果参数存在Intent对象,它会调用intent.writeToParcle()将Intent中的数据写到Parcel类型变量data中。最后通过mRemote.transact()发送给服务端(Binder通信),也就是ActivityTaskManagerService
该文件在frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
它是继承自IActivityTaskManager.Stub,也就是通过aidl自动生成的。
在Prox中通过transact()发送的数据,会调走到Stub的onTransact()中,因此实现了进程间的通信。
5、总结
所以,综上,当我们通过Intent来拉起另一个Activity时,会进行进程间通信,通过Binder把Intent中的数据传递出去给ATMS,而这个过程中Binder的内存大小时有限的,当我们传递数据过大时,会触发TransactionTooLargeException异常。
TODO:EventBus后面学习了之后再做记录