探究intent传递大小限制

栏目: IOS · Android · 发布时间: 6年前

内容简介:当我们用Intent传输大数据时,有可能会出现错误:如上我们传递了1M大小的数据时,结果程序就一直反复报如下TransactionTooLargeException错误:但我们平时传递少量数据的时候是没问题的。由此得知,通过intent在页面间传递数据是有大小限制的。本文我们就来分析下为什么页面数据传输会有这个量的限制以及这个限制的大小具体是多少。

当我们用Intent传输大数据时,有可能会出现错误:

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

如上我们传递了1M大小的数据时,结果程序就一直反复报如下TransactionTooLargeException错误:

探究intent传递大小限制

但我们平时传递少量数据的时候是没问题的。由此得知,通过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方法:

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代理:

/**
     * @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机制。

binder介绍

普通的由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也就会这个限制了。

替代方案

一、写入临时文件或者数据库,通过FileProvider将该文件或者数据库通过Uri发送至目标。一般适用于不同进程,比如分离进程的UI和后台服务,或不同的App之间。之所以采用FileProvider是因为7.0以后,对分享本App文件存在着严格的权限检查。

二、通过设置静态类中的静态变量进行数据交换。一般适用于同一进程内,这样本质上数据在内存中只存在一份,通过静态类进行传递。需要注意的是进行数据校对,以防多线程Data Racer出现导致的数据显示混乱。

参考资料

听说你Binder机制学的不错,来面试下这几个问题(一)

源码分析:startActivity流程


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

微商思维

微商思维

龚文祥、罗剑锋、触电会 / 金城出版社 / 2018-7 / 88.00元

微商不仅仅是一种继传统实体、电商之后的革命性新兴商业形态,更是一种能够写入中国商业史的思潮。龚文祥新著《微商思维》,从道的层面对广大微商人的商业实践智慧进行了高度浓缩与抽象总结,站在更高的视角解读微商背后的商业逻辑与本质。 本书前半部分,主要从本质、品牌、营销等几个方面,阐述了微商思维的内涵及应用场景,帮助读者了解并认识这种革命性的商业思维。 后半部分主要是触电会社群内部各位大咖的实操......一起来看看 《微商思维》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具