编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1App启动流程梳理
  • 1.2ActivityThread分析
  • 1.3Context设计思想
  • 2.1Activity基础介绍
  • 2.2Activity启动流程
  • 2.3Activity布局创建
  • 2.4Activity布局绘制
  • 2.5Service基础介绍
  • 2.6Service启动流程
  • 2.7Receiver广播基础
  • 2.8Receiver深入原理
  • 2.9ContentProvider分析
  • 2.10Fragment实践
  • 2.11Intent深入思考
  • 3.1Paint和Canvas
  • 3.2View的绘制基础
  • 3.3onMeasure流程设计
  • 3.4onLayout流程设计
  • 3.5onDraw流程设计
  • 3.6View工作原理
  • 3.7View刷新设计流程
  • 3.8自定义View控件
  • 3.9自定义ViewGroup控件
  • 4.1Handler基础使用
  • 4.2消息机制流程分析
  • 4.3Handler深度解析
  • 4.4Message深度理解
  • 4.5MessageQueue解析
  • 4.6Looper深度解析
  • 4.7理解Handler同步屏障
  • 4.8ThreadLocal分析
  • 4.9ThreadLocal场景
  • 5.1View事件设计思考
  • 5.2View滑动冲突处理
  • 5.3View事件源码分析
  • 5.4View事件总结案例
  • 6.1DecorView设计思想
  • 6.2视图的载体View
  • 6.3视图管理者Window
  • 6.4窗口管理服务WMS
  • 6.5布局解析者Inflater
  • 7.1AsyncTask深入介绍
  • 7.2HandlerThread设计
  • 7.3IntentService设计
  • 8.1IPC通信方式介绍
  • 8.2AIDL进程间通信
  • 8.7Binder通信机制设计
  • /zh/android/basic/9.1注解设计思想和原理.html
  • 9.2APT技术设计详解
  • 9.3APT多种案例实践

2.11Intent深入思考

目录介绍

  • 01.Intent可以传递多大数据
    • 1.1 先看一个测试案例
    • 1.2 可以传递多大数据
    • 1.3 解决思路分析说明
    • 1.4 Intent和StartActivity分析
    • 1.5 思考题测试分析
    • 1.6 解决大对象传递
  • 02.intent和bundle的关系
    • 2.1 为何要有bundle
  • 03.intent数据传递的限制
    • 3.1 为何不能直接传map
  • 06.PendingIntent分析
    • 6.1 作用是什么呢

考点分析

  • 01.intent可以传递多大的数据,数据过大会怎么样,会出现什么问题?
  • 02.如何查找intent限制数据的证明?为何要限制?
  • 03.如何解决intent传递过大的数据问题?有哪些方法?
  • 04.intent和bundle的关系?
  • 05.intent为何不能直接传递map数据?

01.Intent可以传递多大数据

1.1 先看一个测试案例

  • 测试案例:用intent传递 1M 的byte数组
    Intent intent = new Intent(AActivity.this, BActivity.class);
    byte[] bytes = new byte[1024 * 1024];
    intent.putExtra("key", bytes);
    startActivity(intent);
  • 结果:报异常 TransactionTooLargeException
     Caused by: android.os.TransactionTooLargeException: data parcel size 1048956 bytes
            at android.os.BinderProxy.transactNative(Native Method)
            at android.os.BinderProxy.transact(Binder.java:615)
            at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3070)
            at android.app.Instrumentation.execStartActivity(Instrumentation.java:1518)

1.2 可以传递多大数据

  • 不少资料中写到,Intent 在 Activity 间传递基础类型数据或者可序列化的对象数据。但是 Intent 对数据大小是有限制的,当超过这个限制后,就会触发 TransactionTooLargeException 异常。
  • 异常原因
    • Intent 传递大数据,会出现TransactionTooLargeException的场景,这本身也是一道面试题,经常在面试中被问到。
    • 其实这个问题,如果遇到了,查查文档就知道了。在 TransactionTooLargeException(https://developer.android.com/reference/android/os/TransactionTooLargeException.html) 的文档中,其实已经将触发原因详细说明了。
    • 简单来说,Intent 传输数据的机制中,用到了 Binder。Intent 中的数据,会作为 Parcel 被存储在 Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输。
    • 而这个 Binder 事务缓冲区具有一个有限的固定大小,当前为 1MB。你可别以为传递 1MB 以下的数据就安全了,这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。也就是说 Intent 在 Activity 间传输数据,本身也不适合传递太大的数据。
  • Bundle 的锅?
    • 这里再补充一些细节,Intent 使用 Bundle 存储数据,到底是值传递(深拷贝)还是引用传递?
    • Intent 传输的数据,都存放在一个 Bundle 类型的对象 mExtras 中,Bundle 要求所有存储的数据,都是可被序列化的。
    • 在 Android 中,序列化数据需要实现 Serializable 或者 Parcelable。对于基础数据类型的包装类,本身就是实现了 Serializable,而我们自定义的对象,按需实现这两个序列化接口的其中一个即可。
  • 那是不是只要通过 Bundle 传递数据,就会面临序列化的问题?
    • 并不是,Activity 之间传递数据,首先要考虑跨进程的问题,而 Android 中又是通过 Binder 机制来解决跨进程通信的问题。涉及到跨进程,对于复杂数据就要涉及到序列化和反序列化的过程,这就注定是一次值传递(深拷贝)的过程。
    • 这个问题用反证法也可以解释,如果是引用传递,那传递过去的只是对象的引用,指向了对象的存储地址,就只相当于一个 Int 的大小,也就根本不会出现 TransactionTooLargeException 异常。
  • 传输数据序列化和 Bundle 没有关系,只与 Binder 的跨进程通信有关。为什么要强调这个呢?
    • 在 Android 中,使用 Bundle 传输数据,并非 Intent 独有的。例如使用弹窗时,DialogFragment 中也可以通过 setArguments(Bundle) 传递一个 Bundle 对象给对话框。
    • Fragment 本身是不涉及跨进程的,这里虽然使用了 Bundle 传输数据,但是并没有通过 Binder,也就是不存在序列化和反序列化。和 Fragment 数据传递相关的 Bundle,其实传递的是原对象的引用。
    • 有兴趣可以做个试验,弹出 Dialog 时传递一个对象,Dialog 中修改数据后,在 Activity 中检查数据是否被修改了。

1.3 解决思路分析说明

  • 知道异常的原因,就好解决了。既然原因在于 Binder 传输限制了数据的大小,那我们不走 Binder 通信就好了。可以从数据源上来考虑。
  • 例如 Bitmap,本身就已经实现了 Parcelable 是可以支持序列化的。用 Intent 传输,稍微大一点的图一定会出现 TransactionTooLargeException。当然真是业务场景,肯定不存在传递 Bitmap 的情况。
  • 那就先看看这个图片的数据源。Drawable?本地文件?线上图片?无论数据源在哪里,我们只需要传递一个 drawable_id、路径、URL,就可以还原这张图片,无需将这个 Bitmap 对象传递过去。大数据总有数据源,从数据源还原数据,对我们而言只是调用一个方法而已。
  • 再简单总结一下。
    • Intent 无法传递大数据是因为其内部使用了 Binder 通信机制,Binder 事务缓冲区限制了传递数据的大小。
    • Binder 事务缓冲区的大小限定在 1MB,但是这个尺寸是共享的,也就是并不是传递 1MB 以下的数据就绝对安全,要视当前的环境而定。
    • 不要挑战 Intent 传递数据大小的极限,对于大数据,例如长字符串、Bitmap 等,不要考虑 Intent 传递数据的方案。
  • 具体解决方案可以看:第26篇之Intent传递大数据

1.4 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方法是原生支持跨进程通信的。

1.5 思考题测试分析

  • 思考
    • 实际开发中intent如何传递大数据
    • intent传递数据和bundle传递数据有何区别
    • intent传递数据为何限制大小
  • 应该掌握
    • intent传递数据的原理
    • Android数据传递序列化过程
    • binder机制的简单运作
  • 遇到的问题分析
    • 在Activity间使用Intent传递List含有大量序列化的对象的时候,或者传递较大bitmap等较大量数据的时候会引起页面卡顿。而且Android本身也限制了能够传递的数据大小在1MB左右。这就要求我们不得不为传输大量数据寻求一个解决方法。
    • 通常我们可以想到的一个方法是当从A页面跳转至B页面的时候将需要传递的大对象赋值给A页面的一个静态变量,在B页面去取A页面的值。这种方式简单却有很多问题,比如可能会有很多其他页面访问B页面这会导致静态变量管理混乱,而且如果在组件化开发的过程中,需要进行组件间跳转的时候只能把这种静态变量写在BaseLibrary中,这显然是不够友好的。

1.6 思考题测试分析

1.6.1 创建对象的缓存区
  • 创建对象的缓存区,可以使用单例模式,代码如下所示
    /**
     * <pre>
     *     @author yangchong
     *     blog  : https://github.com/yangchong211
     *     time  : 2019/9/18
     *     desc  : 数据缓冲区,替代intent传递大数据方案
     *     revise:
     * </pre>
     */
    public class ModelStorage {
    
        private List<PseriesModelPicListBean> picListBeans = new ArrayList<>();
    
        public static ModelStorage getInstance(){
            return SingletonHolder.instance;
        }
    
        private static class SingletonHolder{
            private static final ModelStorage instance = new ModelStorage();
        }
    
        public void clearPicListBeans(){
            picListBeans.clear();
        }
    
        public List<PseriesModelPicListBean> getPicListBeans() {
            return picListBeans;
        }
    
        public void setPicListBeans(List<PseriesModelPicListBean> picListBeans) {
            this.picListBeans.clear();
            this.picListBeans.addAll(picListBeans);
        }
    }
1.6.2 创建序列化传递对象基类
  • 创建需要序列化传递对象的基类,可以看到在Model中序列化了一个int值
    public abstract class Model implements Parcelable {
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            int index= ModelStorage.getInstance().putModel(this);
            dest.writeInt(index);
        }
    
        public static final Creator<Model> CREATOR = new Creator<Model>() {
            @Override
            public Model createFromParcel(Parcel in) {
                int index = in.readInt();
                return ModelStorage.getInstance().getModel(index);
            }
    
            @Override
            public Model[] newArray(int size) {
                return new Model[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
    }
  • 来看一个简单的使用示例:将想要序列化的类继承Model
    public class User extends Model {
        //注意这里不需要将name和age序列化存储
        public String name;
        public int age;
        
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
  • 存取的方式如常规使用
    intent.putExtra("key",new User("yc",26));
    User user = getIntent().getParcelableExtra("key");
  • 通过以上方式的封装,可以看到无论需要序列化传递的对象有多大,在传值的时候只是传递了一个“int”而已。
    • 使用该这种方法时需要注意的一点是在数据的接收页面只能使用getIntent()获取一次该对象的值,因为我们在取完一次值后便将该对象从缓存区移除了。

02.intent和bundle的关系

2.1 为何要有bundle

  • 两个Activity之间怎么传递数据?
    • 基本数据类型可以通过Intent传递数据,把数据封装至intent对象中
      Intent intent = new Intent(content, MeActivity.class);
      intent.putExtra("goods_id", goods_id);
      content.startActivity(intent);
    • 把数据封装至bundle对象中,把bundle对象封装至intent对象中
      Bundle bundle = new Bundle();
      bundle.putString("malename", "李志");
      intent.putExtras(bundle);
      startActivity(intent);
  • intent和bundle有什么区别?
    • Intent传递数据和Bundle传递数据是一回事,Intent传递时内部还是调用了Bundle。
      public @NonNull Intent putExtra(String name, String value) {
          if (mExtras == null) {
              mExtras = new Bundle();
          }
          mExtras.putString(name, value);
          return this;
      }
  • 为什么有了intent还要设计bundle?
    • 两者比较
      • Bundle只是一个信息的载体,内部其实就是维护了一个Map<String,Object>。Intent负责Activity之间的交互,内部是持有一个Bundle的。
    • bundle使用场景
      • Fragment之间传递数据;比如,某个Fragment中点击按钮弹出一个DialogFragment。最便捷的方式就是通过Fragment.setArguments(args)传递参数。
      ServiceDialogFragment mainDialogFragment = new ServiceDialogFragment();
      Bundle bundle = new Bundle();
      bundle.putString("title", title);
      mainDialogFragment.setArguments(bundle);
      mainDialogFragment.show(activity.getSupportFragmentManager());
      
      @Override
      public void onCreate(Bundle savedInstanceState) {
          setLocal(Local.CENTER);
          super.onCreate(savedInstanceState);
          Bundle bundle = getArguments();
          if (bundle != null) {
              title = bundle.getString("title");
          }
      }

03.intent数据传递的限制

3.1 为何不能直接传map

  • https://blog.csdn.net/u013173289/article/details/44924247

06.PendingIntent分析

6.1 作用是什么呢

  • PendingIntent基本说明
    • PendingIntent 是一种特殊的 Intent ,字面意思可以解释为延迟的 Intent ,用于在某个事件结束后执行特定的 Action 。从上面带 Action 的通知也能验证这一点,当用户点击通知时,才会执行。
    • PendingIntent是Android系统管理并持有的用于描述和获取原始数据的对象的标志(引用)。也就是说,即便创建该PendingIntent对象的进程被杀死了,这个PendingItent对象在其他进程中还是可用的。日常使用中的短信、闹钟等都用到了 PendingIntent。
  • PendingIntent三种获取方式
    //获取一个用于启动 Activity 的 PendingIntent 对象public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags);
    //获取一个用于启动 Service 的 PendingIntent 对象public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags);
    //获取一个用于向 BroadcastReceiver 广播的 PendingIntent 对象public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)

其他介绍

01.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

02.关于我的博客

  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yczbj/activities
  • 简书:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
  • 开源中国:https://my.oschina.net/zbj1618/blog
  • 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e
贡献者: yangchong211
上一篇
2.10Fragment实践
下一篇
3.1Paint和Canvas