Intent这东西,在Android中的地位至关重要,甚至成为"Android第五大组件"。
在我们的常规业务开发中,各个页面的跳转,service的使用,打开相机,app内分享,广播的使用等等都必须用到它。在我们的framework层里,它的身影也随处可见。
一、什么是Intent?
Intent是Android系统用来抽象描述要执行的一个操作,也可以在不同组件之间进行沟通和消息传递。
Intent意图可以是明确的指定组件的名称,这样你可以精确的启动某个系统组件,比如启动一个Activity。它也可以是模糊的,没有指定组件名称,只要是能够匹配到这个Intent的应用都可以接收到,比如发送一个拍照Intent,所有的拍照应用都会响应。
二、Intent的分类
intent分为显式Intent和隐式Intent:
显式的Intent就是你已经知道要启动的组件名称,比如某个Activity的包名和类名,在Intent中明确的指定了这个组件(Activity),一般来说这种Intent经常用在一个应用中,因为你已经明确的知道要启动的组件名称。
隐式的Intent就是你不知道要启动的组件名称,只知道一个Intent动作要执行,比如:拍照,分享,打开相册,查看地图。一般来说这种Intent用在不同的应用之间传递信息。
当你创建一个显式Intent来启动一个Activity或者Service时,系统会立刻启动那个组件通过你的Intent对象。
当你创建一个隐式Intent,系统会根据manifest中的intent filter寻找匹配的组件,如果你发送的Intent匹配到一个intent filter,系统会把你的Intent传递给该filter对应的组件(Activity、Service等),并且启动它。如果找到多个匹配的intent filter对应的应用程序,则会弹出一个对话框让你选择哪个应用程序接受你的Intent。
三、intent的简单使用
1.跳转指定页面(显示)
mBinding.acLockSettingIcon.setOnClickListener {startActivity(Intent(this, SettingActivity::class.java))}
2.打开网页(隐式)
public void invokeWebBrowser(View view) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("http://www.google.com")); startActivity(intent);
}
3.打电话(隐式)
public void call(View view) { Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:110")); startActivity(intent);
}
4.发送短信(隐式)
// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type
5.分享图片
private void share(String content, Uri uri){Intent shareIntent = new Intent(Intent.ACTION_SEND);if(uri!=null){shareIntent.putExtra(Intent.EXTRA_STREAM, uri);shareIntent.setType("image/*");//当用户选择短信时使用sms_body取得文字shareIntent.putExtra("sms_body", content);}else{shareIntent.setType("text/plain");}shareIntent.putExtra(Intent.EXTRA_TEXT, content);//自定义选择框的标题startActivity(Intent.createChooser(shareIntent, "邀请好友"));//系统默认标题}
6.打开指定应用
String package_name="xx.xx.xx";
PackageManager packageManager = context.getPackageManager();
Intent it = packageManager.getLaunchIntentForPackage(package_name);
startActivity(it);
四、Intent的属性
在Intent里,可以配置以下属性
1.setAction:意图,一个字符串变量,用来指定Intent要执行的动作类别
2.setData:一个Uri对象,对应着一个数据,这个数据可能是MIME类型的。当创建一个intent时,除了要指定数据的URI之外,指定数据的类型(MIME type)也很重要,它能够帮助系统找到最合适的那个系统组件来处理你的intent请求。然而,MIME type有时能够通过URI来推测出来,特别是当data是content:的URI,这样的data表明在设备中由ContentProvider提供。
只设置数据的URI可以调用setData()方法,只设置MIME类型可以调用setType()方法,如果要同时设置这两个可以调用setDataAndType()。
3.Category:一个包含Intent额外信息的字符串,表示哪种类型的组件来处理这个Intent.
例如:
<receiverandroid:name=".receiver.BootBroadcastReceiver"android:exported="true"><intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></receiver>
- CATEGORY_LAUNCHER:表示这个Activity是Activity栈最初的Activity,应用程序的主Activity,可以在桌面应用程序列表找到并启动它。
- 这个receiver监听 Android手机开机后,会发送android.intent.action.BOOT_COMPLETED广播,监听这个广播就能监听开机。
- 当然,我在动态注册广播的时候,也可以通过
private void registerListener() {if (mContext != null) {IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_SCREEN_ON);filter.addAction(Intent.ACTION_SCREEN_OFF);filter.addAction(Intent.ACTION_USER_PRESENT);filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);//home键mContext.registerReceiver(mScreenReceiver, filter);}
这样去监听锁屏/熄屏/home键等操作
以上Intent的属性(component name、action、data and category)都是Intent的特征属性,通过这些属性Android系统可以找到哪个应用组件将会被启动。
还有以下属性:
1.Extras:Intent可以携带的额外key-value数据,你可以通过调用putExtra()方法设置数据,每一个key对应一个value数据。你也可以通过创建Bundle对象来存储所有数据,然后通过调用putExtras()方法来设置数据。对于数据key的名字要尽量用包名做前缀,然后再加上其他,这样来保证key的唯一性。
2.Flags:用来指示系统如何启动一个Activity(比如:这个Activity属于哪个Activity栈)和Activity启动后如何处理它(比如:是否把这个Activity归为最近的活动列表中)。例如我在点击通知栏的im信息后,我把flag设置为FLAG_ACTIVITY_CLEAR_TOP,那么在该activity之上的activity都会关闭,并且intent会传递给老的activity(现在在栈顶),设置为FLAG.ACTIVITY_NEW_TASK
标志主要用于确保当从一个非 Activity
类型的 Context
(例如,一个 Service
或 Application
)启动 Activity
时,该 Activity
会被放置在一个新的任务(task)栈中。
上面就是Intent的一些基本使用和介绍
五、Intent传递数据
在常规的开发中,我们通常会使用Intent传递一些简单的数据。如果数据过大,就会爆TransactionTooLargeException异常。
简单来说,Intent 传输数据的机制中,用到了 Binder。Intent 中的数据,会作为 Parcel 被存储在 Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输。而这个 Binder 事务缓冲区具有一个有限的固定大小,当前为 1MB。你可别以为传递 1MB 以下的数据就安全了,这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。也就是说 Intent 在 Activity 间传输数据,本身也不适合传递太大的数据。
在Intent的数据传输中:使用 Bundle 存储数据,是用的值传递(深拷贝),Intent 传输的数据,都存放在一个 Bundle 类型的对象 mExtras 中,Bundle 要求所有存储的数据,都是可被序列化的。在 Android 中,序列化数据需要实现 Serializable 或者 Parcelable。对于基础数据类型的包装类,本身就是实现了 Serializable,而我们自定义的对象,按需实现这两个序列化接口的其中一个即可。
为什么用的深拷贝?因为在Activity 之间传递数据,首先要考虑跨进程的问题,而 Android 中又是通过 Binder 机制来解决跨进程通信的问题。所以说这个传递数据的大小和binder传输数据相关。
所以:
- Intent 无法传递大数据是因为其内部使用了 Binder 通信机制,Binder 事务缓冲区限制了传递数据的大小。
- Binder 事务缓冲区的大小限定在 1MB,但是这个尺寸是共享的,也就是并不是传递 1MB 以下的数据就绝对安全,要视当前的环境而定。
- 不要挑战 Intent 传递数据大小的极限,对于大数据,例如长字符串、Bitmap 等,不要考虑 Intent 传递数据的方案。
其他的tips:
Intent传递数据和Bundle传递数据是一回事,Intent传递时内部还是调用了Bundle。
Bundle只是一个信息的载体,内部其实就是维护了一个Map<String,Object>。Intent负责Activity之间的交互,内部是持有一个Bundle的。但是我们不能直接传递Map对象哦。
六、PendingIntent
- 从字面意思上理解,PendingIntent 是一种延迟的 Intent,表示一种延迟执行的意图操作。
PendingIntent 的应用场景关键在于间接的 Intent 跳转需求, 即先通过一级 Intent 跳转到某个组件,在该组件完成任务后再间接地跳转到二级的 Intent。PendingIntent 中的单词 “pending” 指延迟或挂起,就是指它是延迟的或挂起的。例如,你在以下场景中就可以使用 PendingIntent:
- 场景 1 - 系统通知消息的点击操作(例如im消息跳转)
- 场景 2 - 桌面微件的点击操作
- 场景 3 - 系统闹钟操作
- 场景 4 - 第三方应用回调操作
在这些场景中,我们真正感兴趣的操作是挂起的,并且该操作并不是由当前应用执行,而是由某个外部应用来 “间接” 执行的。例如,我们在发送系统通知消息时,会通过 PendingIntent 构造一个系统通知 Notification
,并调用 NotificationManagerCompat.notify(…)
发送通知,此时并不会直接执行 PendingIntent。而是当系统显示通知,并且用户点击通知时,才会由系统通知这个系统应用间接执行 PendingIntent#send()
,而不是通过当前应用执行。
1. PendingIntent 和 Intent 的区别
1、执行进程不同 —— PendingIntent 在其他进程执行: Intent 通常会在创建进程中执行,而 PendingIntent 通常不会在创建进程中执行;
2、执行时间不同 —— PendingIntent 会延迟执行: Intent 通常会立即执行,而 PendingIntent 通常会延迟执行,延迟到其他进程完成任务后再执行,甚至延迟到创建进程消亡后。例如,在 场景 1 - 系统通知消息的点击操作 中,即使发送系统通知消息的进程已经消亡了,依然不妨碍二级 Intent 的跳转;
3、执行身份不同 —— PendingIntent 支持授权: PendingIntent 内部持有授权信息,支持其他应用以当前应用的身份执行,这有利于避免嵌套 Intent 存在的安全隐患。而直接使用 Intent 的话,一般只能以当前应用的身份执行(为什么说一般?因为有 Activity#startActivityAsUser() 这个 API,但一般你拿不到所需的参数)。
2.PendingIntent 的使用方法
// 启动 Activity
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
// 启动 Service
PendingIntent.Service(Context context, int requestCode, Intent intent, int flags)
// 启动 BroadcastReceiver(发送广播)
PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)
PendingIntent 的 4 个参数:
1、context: 当前应用的上下文,PendingIntent 将从中抽取授权信息;
2、requestCode: PendingIntent 的请求码,与 Intent 的请求码类似;
3、intent: 最终的意图操作;
4、flag: 控制标记位,通过标记位 FLAG_MUTABLE
和 FLAG_IMMUTABLE
控制 PendingIntent 可变或不可变。可变性意味着在消费 PendingIntent 时,可以针对其中包装的 Intent 进行修改,即使用 PendingIntent#send(Context, int, Intent) 进行修改。需要注意的是,这里的 Intent 参数并不会完全替换 PendingIntent 中包装的 Intent,而是将修改的信息填充到原有的 Intent 上。FLAG_UPDATE_CURRENT: 更新标记位 1,如果系统中已经存在相同的 PendingIntent,那么将保留原有 PendingIntent 对象,而更新其中的 Intent。即使不可变 PendingIntent,依然可以在当前应用更新;FLAG_CANCEL_CURRENT: 更新标记位 2,如果系统中已经存在相同的 PendingIntent,那么将先取消原有的 PendingIntent,并重新创建新的 PendingIntent。: 更新标记位 3,如果系统中已经存在相同的 PendingIntent,那么不会重新创建,而是直接返回 null;
FLAG_ONE_SHOT: 一次有效标记位,PendingIntent 被消费后不支持重复消费,即只能使用一次。