During the study, I saw a sentence in the company's specifications:
For data communication between activities, if the amount of data is relatively large, the method of intent+Parcelable shall be avoided, and alternative methods such as EventBus can be considered to avoid TransactionTooLargeException;
At the beginning, I didn't know much about the internal principle. If I just memorize it by rote, it won't work. Therefore, I want to make an in-depth analysis of this problem:
1. First, we need to understand how to transfer data when intent.
Usually, we pass data in Activity, which is to put data method in Intent, then call startActivity or startActivityForResult to pull up another Activity.
Intent mSecondIntent = new Intent(MainActivity.this, SecondActivity.class); byte[] val = new byte[1024*10]; mSecondIntent.putExtra("byteArr", val); startActivity(mSecondIntent);
putExtra() here has multiple overloads, which can transfer different data. We can see from the source code:
public @NonNull Intent putExtra(String name, @Nullable byte[] value) { if (mExtras == null) { mExtras = new Bundle(); } mExtras.putByteArray(name, value); return this; }
In fact, it is stored in the Bundle through an internal variable mExtras.
Therefore, data transmission is realized through Intent.
2. Through Intent+Parcelable data:
Parcelable is a serialization (converting objects into binary streams / sequences that can be transferred) interface provided by Android: public interface Parcelable {} / / it is an interface, and through the Android source code annotation, we need to define our own Parcelable class to implement it. In fact, it is to write and reply data through Parcel. describeContent(), writeToParcel(), and deserialization need to be implemented -- define a variable creator, and implement the creator interface in Parcelable through an anonymous inner class.
There is a simple use example in the notes of Pracelable for Android:
/** * 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 {
Therefore, even if the Parcelable is used to transfer objects or object arrays, in fact, when the data is finally transferred, it is the same as mentioned above through intent PutExtrac () puts it in Intent and then calls startActivity() to pass it out.
3. Why can't big data use Intent+Parcelable
In fact, even if we can't use Intent, we can't use Intent as long as the data is large enough, whether it's a Parcelable object or not. We use byte to simulate the scene of transmitting 1M data: (why does 1M say later)
Intent mSecondIntent = new Intent(MainActivity.this, SecondActivity.class); byte[] val = new byte[1024*1024]; mSecondIntent.putExtra("byteArr", val); startActivity(mSecondIntent);
The following error will be reported:
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
You can see that this is a caused by: Android os. Transactiontoolargeexception: exception. Let's first look at the exception definition of TransactionToolLargeException in frameworks / base / core / Java / Android / OS / transactiontoolargeexception Java file:
The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process.
As can be seen from the comments in the file, we now limit the memory size of Binder transaction to 1M.
Therefore, this should be because the amount of data exceeds the maximum memory that Binder can transfer during the transfer process, and a TooLarge exception is reported.
4. How startActivity() transfers data:
According to the stack information and source code, after we call startactivity (), we will call activity startActivity(). Finally, in instrumentation Calling ActivityTaskManager. in java#execStartActivity () getService(). startActivity();
In this process, Intent is passed in as a parameter, and some data may be added in the process.
In activitytaskmanager Getservice () will get an iactivitytaskmanager Stub. Binder proxy object of prox (this is related to binder communication. There is IActivityTaskManager.aidl file in android/app / directory. Prox on the Java side is automatically generated through Aidl)
We can simulate this situation and write an aidl file ourselves, in which the Intent parameter is as 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); }
Use AS to automatically generate the startActivity() in the java file, AS shown below:
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; }
In this, if the parameter has an Intent object, it will call Intent Writetoparcle() writes the data in Intent to the Parcel type variable data. Finally, through mremote Transact () is sent to the server (Binder communication), that is, ActivityTaskManagerService
The file is in frameworks / base / services / core / Java / COM / Android / server / WM / activitytaskmanagerservice java
It is inherited from iactivitytaskmanager Stubs are automatically generated through aidl.
The data sent through transact() in Prox will be transferred to onTransact() in Stub, so the communication between processes is realized.
5. Summary
Therefore, to sum up, when we pull up another Activity through Intent, we will conduct inter process communication and transfer the data in Intent to ATMS through Binder. In this process, the memory size of Binder is limited. When we transfer too much data, we will trigger TransactionTooLargeException.
TODO: record after learning from EventBus