【笔记】【从Android Guide温习Android 三】意图 (Intent)
是什么
直译过来很贴切"意图". 主要用于启动Activity,Service以及Broadcast。
分类
- 显式Intent
明确你意图的目标。即指明你要通知的对象,是哪个Activity或是Service - 隐式Intent
你的意图不明确,但需要符合你相应的条件。比如发送Broadcast.
创建Intent
包含下面几大部分
前四种(Component name. Action, Data, Category),系统通过这些属性判断启动哪个组建(Component)。
- Component name
目标组建, 注意这个参数是 ComponentName 类,该类由包名和类名组成。
你通常调用如下方法:
// case 1
Intent intent = new Intent(this, IntentActivity.class);
// case 2
Intent intent = new Intent();
intent.setComponent(new ComponentName(stringOfPackage, stringOfClass));
// case 3
Intent intent = new Intent();
intent.setClassName(stringOfPackage, stringOfClass)
- Action
"意图"执行的具体活动。可以理解为一种标识。每一种有自己的含义。Intent提供给我们一些公用的Action。
因为Intent可以携带数据,所以每种公用Action也代表是否有输入输出数据。
同时,Intent有两种分类,一种被Activity使用, 另一种为Broardcast所用。
下面举几个例子,文尾会附上所有Action。
Activity:
- ACTION_MAIN: 标识程序入口。没有数据返回。
- ACTION_VIEW: 展示用户数据。可设置输入的URI用来接受数据。
- ACTION_INSERT: 插入一条空数据到所提供的容器。输入的URI是待插入数据的目录。
- ACTION_EDIT: 为传入数据,提供明确的可修改的访问。输入的URI是待修改的数据。
最后两个或许看上不是很明白。
可以结合ContentProvider去理解。
插入或修改数据,需要告诉Provider一个URI.该URI标识出你要插入或修改的表或行数据。
所以你若启动一个Activity去完成此操作,灵活性的方法是给他待操作的URI。
BroadCast:
- ACTION_MEDIA_BAD_REMOVAL: 移动媒体到SD卡时,要移动的目标位置是unmounted的。
- ACTION_MEDIA_BUTTON: 点击"Media Button"
- ACTION_MEDIA_REMOVED: 媒体被删除。
- Data
指向该数据的URI或MIME类型。
- setData() 设置URI
- setType() 设置MIME
- 上述两函数互斥的,若需同时设置这两项,需要通过 setDataAndType()。
- Category
标识目标组建的分类。多数情况下不需要此参数,当然,需不需要肯定依赖于你AndroidManifest中的设置。 - Extras
需要携带的数据通过此属性存储。
通过putExtra() 存入, getXXXExtra()读取
在这里扫个盲,原来被某测试同学问了多次,关于putExtra和putExtras是否一样,估计是因为不清楚putExtra最终存到了哪里。直接上源码:
// putExtra() 和 putExtras() 是等价的。
public Intent putExtra(String name, boolean value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putBoolean(name, value);
return this;
}
public Intent putExtras(Bundle extras) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putAll(extras);
return this;
}
public Intent putExtras(Intent src) {
if (src.mExtras != null) {
if (mExtras == null) {
mExtras = new Bundle(src.mExtras);
} else {
mExtras.putAll(src.mExtras);
}
}
return this;
}
// Bundle putXXX
// mMap type is ArrayMap<String, Object>()
public void putLong(String key, long value) {
unparcel();
mMap.put(key, value);
}
//Bundle putAll method...
public void putAll(Bundle map) {
unparcel();
map.unparcel();
mMap.putAll(map.mMap);
// fd state is now known if and only if both bundles already knew
mHasFds |= map.mHasFds;
mFdsKnown = mFdsKnown && map.mFdsKnown;
}
- Flags
终于见到"万能的"flag了。
官网举了两个例子:
- 系统如何加载Activity, 如Activity应该加入到哪个任务中
- 加载后有什么操作, 如是否加入到最近的Activity.
隐式Intent如何确定目标组建的
影响因素有以下几点:
- Action
在Manifest中的intent-filter节点中,定义0个或多个action节点,如下.
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.VIEW" />
...
</intent-filter>
匹配原则:
查找包含的name属性是否与请求的intent相同,相同则进行下面的判断。
- Category
在Manifest中的intent-filter节点中,定义0个或多个category节点,如下.
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
...
</intent-filter>
匹配原则:
查找包含的name属性是否与请求的intent相同,相同则进行下面的判断。
当隐式intent是要启动某个Activity时,默认会添加 CATEGORY_DEFAULT 分类。
所以被启动的Activity要在intent-filter中加上"android.intent.category.DEFAULT"这个分类。
- Data
在Manifest中的intent-filter节点中,定义0个或多个data节点,如下.
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" ... />
<data android:mimeType="audio/mpeg" android:scheme="http" ... />
...
</intent-filter>
Data有两部分组成,一部分是 URI 另一部分是 MIME
URL由这些4大属性组成: scheme, host, port, path.
下面是官方的例子,URI与属性的对应图
content://com.example.project:200/folder/subfolder/etc
------- | -------------------|---|--------------------
scheme | host |port| path
4大属性依次依赖,前者若没有声明,后者的设置将被忽略。
URI的比较原则:仅比较intent的URI包含的部分。即若只包含scheme则匹配scheme,包含scheme和host则比较两者。
Path的模糊匹配:* 可以匹配所有。
匹配原则:比较复杂。
下表 - 标识未声明此属性,+ 标识已声明。 当四条均符合是,则匹配成功。
URI | MIME | 命中组建条件(URI) | 命中组建条件(MIME) |
- | - | - | - |
+ | - | + | - |
- | + | - | + |
+ | + | + | + |
还有很多细节,请参照官网,Note以下的部分。
匹配Intent
官网举了两点例子,第二点会结合我的代码说明。
- Android如何确认打开app时应选择那个组建首先启动。
- ACTION_MAIN
- CATEGORY_LAUNCHER
- 可以利用方法查找符合intent的组建。
PackageManager中的 query...() 和 resolve...()
//获取符合的intent并显示在ListView中。
public static final String INTENT_FILTER = "com.tangyu.component.demo.action";
// method start ...
mVList = (ListView) findViewById(R.id.demo_launcher_list);
List<ResolveInfo> resolveInfoList = getPackageManager().queryIntentActivities(new Intent(INTENT_FILTER, null), 0);
LinkedList<String> activities = new LinkedList<String>();
for (int i = 0; i < resolveInfoList.size(); ++i) {
String fullname = resolveInfoList.get(i).activityInfo.name;
Pattern pattern = Pattern.compile("\\.");
String[] split = pattern.split(fullname);
String activityName = split[split.length - 1];
activities.add(activityName);
}
mVList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, activities));
// method end.
PendingIntent
是Intent的包装类,获取外界APP的信任权限,并使用携带的intent启动他。内容还是比较多的。以后在系统复习。
例子
官网的例子很到位的说
- 显式Intent 用Download服务为例,传入URI进行download。
- 隐式Intent 要Share某些数据,若有匹配的Activity,则启动它。
- 其中也介绍了 App chooser 这个比较直观的体现隐式的含义。
- 接收显式Intent在AndroidManifest.xml中设置<intent-filter>, 下面是官网的例子
<activity android:name="MainActivity">
<!-- This activity is the main entry, should appear in app launcher -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 看!!是可以设置多个不同的intent-filter -->
<activity android:name="ShareActivity">
<!-- This activity handles "SEND" actions with text data -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.google.panorama360+jpg"/>
<data android:mimeType="image/*"/>
<data android:mimeType="video/*"/>
</intent-filter>
</activity>
下面是TYComponent项目的AndroidManifest.xml
<activity android:name=".demo.DemoLauncher">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- horizontal list view -->
<activity
android:name=".demo.HorizontalListViewDemo"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.tangyu.component.demo.action"/>
</intent-filter>
</activity>
<activity
android:name=".demo.HorizontalListViewDemo4Grid"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.tangyu.component.demo.action"/>
</intent-filter>
</activity>
<!-- remind service -->
<activity android:name=".demo.service.remind.ActDemoRemindService"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.tangyu.component.demo.action"/>
</intent-filter>
</activity>
除了Launcher以外,组建都会有<action android:name="com.tangyu.component.demo.action"/>
这是为了实现获取具有相同Action的组建,并把他们显示到列表中。学自于ApiDemo。
再者,若将com.tangyu.component.demo.action 换为 android.intent.action.MAIN 会不会有影响?
不会有影响,虽然组建都是程序的入口。但是由于第一个有<category android:name="android.intent.category.LAUNCHER"/>
,所以打开应用,仍是从demo.DemoLauncher启动。
在这里有一下几点需要注意
- 显式意图会直接送给目标,不会管该组建的Intent-filters
- 避免运行其他APP的组建,通常用显式意图启动组建
- 所有的Activity的intent-filters都要定义在AndroidManifest中,但是broadcast的filter可以动态绑定。通过 registerReceiver()) 和 unregisterReceiver());
- 如果你不想让其他APP启动你的组建,设置exported 为false
总结
- Intent作为启动组建而存在。他携带很多信息,目标,数据,过滤。
- Intent分为显式和隐式两种,代表作用目标是否明确。明确的用狙击枪,不明确的有冲锋枪。
- Intent的匹配规则很多,要合理使用。