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传递数据,把数据封装至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传递数据是一回事,Intent传递时内部还是调用了Bundle。
- 为什么有了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.关于博客汇总链接
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