探究intent傳遞大小限制

前言

當咱們用Intent傳輸大數據時,有可能會出現錯誤:java

val intent = Intent(this@MainActivity, Main2Activity::class.java)
        val data = ByteArray(1024 * 1024)
        intent.putExtra("111", data)
        startActivity(intent)
複製代碼

如上咱們傳遞了1M大小的數據時,結果程序就一直反覆報以下TransactionTooLargeException錯誤:android

但咱們平時傳遞少許數據的時候是沒問題的。由此得知,經過intent在頁面間傳遞數據是有大小限制的。本文咱們就來分析下爲何頁面數據傳輸會有這個量的限制以及這個限制的大小具體是多少。面試

startActivity流程探究

首先咱們知道Context和Activity都含有startActivity,但二者最終都調用了Activity中的startActivity:數據庫

@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }
複製代碼

而startActivity最終會調用自身的startActivityForResult,省略了嵌套activity的代碼:多線程

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
      options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                // If this start is requesting a result, we can avoid making
                // the activity visible until the result is received.  Setting
                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                // activity hidden during this time, to avoid flickering.
                // This can only be done when a result is requested because
                // that guarantees we will get information back when the
                // activity is finished, no matter what happens to it.
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
    }
複製代碼

而後系統會調用Instrumentation中的execStartActivity方法:app

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
       ...
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
複製代碼

接着調用了ActivityManger.getService().startActivity ,getService返回的是系統進程中的AMS在app進程中的binder代理:ide

/**
     * @hide
     */
    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };
複製代碼

接下來就是App進程調用AMS進程中的方法了。簡單來講,系統進程中的AMS集中負責管理全部進程中的Activity。app進程與系統進程須要進行雙向通訊。好比打開一個新的Activity,則須要調用系統進程AMS中的方法進行實現,AMS等實現完畢須要回調app進程中的相關方法進行具體activity生命週期的回調。源碼分析

因此咱們在intent中攜帶的數據也要從APP進程傳輸到AMS進程,再由AMS進程傳輸到目標Activity所在進程。有同窗可能由疑問了,目標Acitivity所在進程不就是APP進程嗎?其實不是的,咱們能夠在Manifest.xml中設置android:process屬性來爲Activity, Service等指定單獨的進程,因此Activity的startActivity方法是原生支持跨進程通訊的。大數據

接下來簡單分析下binder機制。ui

binder數據傳輸

img

普通的由Zygote孵化而來的用戶進程,所映射的Binder內存大小是不到1M的,準確說是 110241024) - (4096 *2) :這個限制定義在frameworks/native/libs/binder/processState.cpp類中,若是傳輸說句超過這個大小,系統就會報錯,由於Binder自己就是爲了進程間頻繁而靈活的通訊所設計的,並非爲了拷貝大數據而使用的:

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
複製代碼

並能夠經過cat proc/[pid]/maps命令查看到。

而在內核中,其實也有個限制,是4M,不過因爲APP中已經限制了不到1M,這裏的限制彷佛也沒多大用途:

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;
    //限制不能超過4M
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
    。。。
    }
複製代碼

其實在TransactionTooLargeException中也提到了這個:

The Binder transaction buffer has a limited fixed size, currently 1Mb, which
is shared by all transactions in progress for the process.  Consequently this
exception can be thrown when there are many transactions in progress even when
most of the individual transactions are of moderate size.
複製代碼

只不過不是正好1MB,而是比1MB略小的值。

小結

至此咱們來解答開頭提出的問題,startActivity攜帶的數據會通過BInder內核再傳遞到目標Activity中去,由於binder映射內存的限制,因此startActivity也就會這個限制了。

替代方案

1、寫入臨時文件或者數據庫,經過FileProvider將該文件或者數據庫經過Uri發送至目標。通常適用於不一樣進程,好比分離進程的UI和後臺服務,或不一樣的App之間。之因此採用FileProvider是由於7.0之後,對分享本App文件存在着嚴格的權限檢查。

2、經過設置靜態類中的靜態變量進行數據交換。通常適用於同一進程內,這樣本質上數據在內存中只存在一份,經過靜態類進行傳遞。須要注意的是進行數據校對,以防多線程操做致使的數據顯示混亂。

參考資料

據說你Binder機制學的不錯,來面試下這幾個問題(一)

源碼分析:startActivity流程

相關文章
相關標籤/搜索