Intent的数据传输

在学习时,看到公司的规范里有句话:

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&lt;MyParcelable&gt; CREATOR
 *             = new Parcelable.Creator&lt;MyParcelable&gt;() {
 *         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后面学习了之后再做记录

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值