顯式Intent和隱式Intent解析
Android中的Intent分為兩種類型:
顯式 Intent
:按名稱(完全限定類名)指定要啟動(dòng)的組件。 通常,您會(huì)在自己的應(yīng)用中使用顯式 Intent 來(lái)啟動(dòng)組件萄传,這是因?yàn)槟酪獑?dòng)的 Activity 或服務(wù)的類名枫吧。例如躺苦,啟動(dòng)新 Activity 以響應(yīng)用戶操作,或者啟動(dòng)服務(wù)以在后臺(tái)下載文件诗越。隱式 Intent
:不會(huì)指定特定的組件,而是聲明要執(zhí)行的常規(guī)操作息堂,從而允許其他應(yīng)用中的組件來(lái)處理它嚷狞。 例如,如需在地圖上向用戶顯示位置荣堰,則可以使用隱式 Intent床未,請(qǐng)求另一具有此功能的應(yīng)用在地圖上顯示指定的位置。
顯示Intent啟動(dòng)當(dāng)前應(yīng)用組件
顯式Intent一般是在當(dāng)前應(yīng)用中調(diào)用振坚,用來(lái)啟動(dòng)當(dāng)前應(yīng)用的指定組件薇搁。下面展示了幾種常見的顯式Intent啟動(dòng)實(shí)例:
// 顯式Intent調(diào)用——構(gòu)造方法傳入Component
Intent intent = new Intent(this, TestActivity.class);
startActivity(intent);
// 顯式Intent調(diào)用——setComponent
ComponentName componentName = new ComponentName(this, TestActivity.class);
Intent intent = new Intent();
intent.setComponent(componentName);
startActivity(intent);
// 顯式Intent調(diào)用——setClass
Intent intent = new Intent();
intent.setClass(this, TestActivity.class);
startActivity(intent);
// 顯式Intent調(diào)用——setClassName(packageContext, className)
Intent intent = new Intent();
//context, String
intent.setClassName(this, "com.tiny.demo.firstlinecode.test.view.TestActivity");
startActivity(intent);
// 顯式Intent調(diào)用——setClassName(packageName, className)
Intent intent = new Intent();
//String, String
intent.setClassName("com.tiny.demo.firstlinecode", "com.tiny.demo.firstlinecode.test.view.TestActivity");
startActivity(intent);
顯示Intent啟動(dòng)其他應(yīng)用組件
先看下錯(cuò)誤示范:
目標(biāo)Activity配置:不做任何額外配置。
<activity
android:name=".TestExplicitIntentActivity"
android:label="TestExplicitIntentActivity" />
// 啟動(dòng)其他應(yīng)用的Activity屡拨,目標(biāo)Activity不做任何配置只酥,會(huì)報(bào)SecurityException錯(cuò)誤
Intent intent = new Intent();
//String, String
intent.setClassName("com.tinytongtong.dividerviewdemo", "com.tinytongtong.dividerviewdemo.TestExplicitIntentActivity");
startActivity(intent);
具體錯(cuò)誤如下:
2019-08-06 10:02:23.355 7230-7230/com.tiny.demo.firstlinecode E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.tiny.demo.firstlinecode, PID: 7230
java.lang.SecurityException: Permission Denial: starting Intent { cmp=com.tinytongtong.dividerviewdemo/.TestExplicitIntentActivity } from ProcessRecord{2fe990c 7230:com.tiny.demo.firstlinecode/u0a397} (pid=7230, uid=10397) not exported from uid 10398
...
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.am.ActivityStackSupervisor.checkStartAnyActivityPermission(Landroid/content/Intent;Landroid/content/pm/ActivityInfo;Ljava/lang/String;IIILjava/lang/String;ZZLcom/android/server/am/ProcessRecord;Lcom/android/server/am/ActivityRecord;Lcom/android/server/am/ActivityStack;)Z(libmapleservices.so:4243605)
...
這個(gè)SecurityException異常是完全可以避免的褥实,我們給目標(biāo)Activity設(shè)置android:exported="true"
屬性。
<activity
android:name=".TestExplicitIntentActivity"
android:exported="true"
android:label="TestExplicitIntentActivity" />
然后再運(yùn)行裂允,就成功打開目標(biāo)Activity了损离。
當(dāng)然了,我們還有另一種方式打開其他應(yīng)用的Activity绝编,我們需要給目標(biāo)Activity設(shè)置一個(gè)不相關(guān)的<intent-filter>僻澎。具體配置如下:
<activity
android:name=".TestExplicitIntent1Activity"
android:label="TestExplicitIntent1Activity">
<intent-filter>
<action android:name="com.tinytongtong.dividerviewdemo.action.TestExplicitIntent1Activity" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.tinytongtong.dividerviewdemo.category.TestExplicitIntent1Activity" />
<data
android:host="www.tiny.com"
android:mimeType="text/plain"
android:port="8080"
android:scheme="http" />
</intent-filter>
</activity>
啟動(dòng)代碼:
// 啟動(dòng)其他應(yīng)用的Activity,目標(biāo)Activity需要設(shè)置一個(gè)不相關(guān)的Intent-Filter
Intent intent = new Intent();
//String, String
intent.setClassName("com.tinytongtong.dividerviewdemo", "com.tinytongtong.dividerviewdemo.TestExplicitIntent1Activity");
startActivity(intent);
說(shuō)了這么多十饥,其實(shí)就是為了證明顯式Intent是可以啟動(dòng)其他應(yīng)用的Activity的窟勃。
官方是不推薦使用顯式Intent啟動(dòng)其他應(yīng)用的Activity的,我們一般也不會(huì)這么寫逗堵。因?yàn)槲覀儐?dòng)使用的Intent#setClassName方法的兩個(gè)參數(shù)均是String類型秉氧,目標(biāo)應(yīng)用的包名和目標(biāo)應(yīng)用的全路徑都是以String類型體現(xiàn)的,這就是我們應(yīng)該盡力避免的硬編碼了蜒秤。一旦目標(biāo)Activity修改了類名汁咏、修改了包名或者移動(dòng)了位置,那么我們之前寫的啟動(dòng)代碼都會(huì)失敗作媚,這明顯不符合我們的代碼規(guī)范攘滩。
Intent#setClassName源碼:
public @NonNull Intent setClassName(@NonNull String packageName, @NonNull String className) {
mComponent = new ComponentName(packageName, className);
return this;
}
所以說(shuō),啟動(dòng)其他應(yīng)用的組件時(shí)纸泡,應(yīng)該使用隱式Intent漂问,具體來(lái)說(shuō)就是使用Intent-Filter進(jìn)行匹配。
隱式Intent啟動(dòng)實(shí)例
隱式Intent不會(huì)指定特定的組件女揭,而是聲明要執(zhí)行的常規(guī)操作蚤假,系統(tǒng)會(huì)根據(jù)Intent的內(nèi)容去匹配對(duì)應(yīng)的Activity并啟動(dòng)。
官網(wǎng)上是這么介紹的:
創(chuàng)建隱式 Intent 時(shí)田绑,Android 系統(tǒng)通過(guò)將 Intent 的內(nèi)容與在設(shè)備上其他應(yīng)用的清單文件中聲明的 Intent-Filter 進(jìn)行比較勤哗,從而找到要啟動(dòng)的相應(yīng)組件。 如果 Intent 與 Intent-Filter 匹配掩驱,則系統(tǒng)將啟動(dòng)該組件芒划,并向其傳遞 Intent 對(duì)象。 如果多個(gè) Intent 過(guò)濾器兼容欧穴,則系統(tǒng)會(huì)顯示一個(gè)對(duì)話框民逼,支持用戶選取要使用的應(yīng)用。
所以說(shuō)隱式Intent既可以啟動(dòng)當(dāng)前應(yīng)用的組件涮帘,也可以啟動(dòng)其他應(yīng)用的組件拼苍。下面會(huì)給出兩個(gè)最簡(jiǎn)單的隱式Intent啟動(dòng)Activity實(shí)例。
1调缨、啟動(dòng)當(dāng)前應(yīng)用組件的示例如下:
目標(biāo)activity配置:
<activity android:name=".kfysts.chapter01.intent.implicit.ImplicitIntentTestAActivity">
<intent-filter>
<action android:name="com.tiny.demo.firstlinecode.kfysts.chapter01.intent.implicit.action.a" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Intent代碼:
// 啟動(dòng)當(dāng)前應(yīng)用的Activity
Intent intent = new Intent();
//action
intent.setAction("com.tiny.demo.firstlinecode.kfysts.chapter01.intent.implicit.action.a");
//Category可以不設(shè)置疮鲫,因?yàn)橐话阍贏ndroidManifest.xml會(huì)設(shè)置Default吆你,startActivity方法中也會(huì)默認(rèn)添加Default。
if (intent.resolveActivity(getPackageManager()) != null) {
LogUtils.e("match success");
startActivity(intent);
} else {
LogUtils.e("match failure");
}
2俊犯、啟動(dòng)其他應(yīng)用組件的示例如下:
目標(biāo)activity配置(其他應(yīng)用):
<activity
android:name=".TestImplicitIntentActivity"
android:label="TestImplicitIntentActivity">
<intent-filter>
<action android:name="com.tinytongtong.dividerviewdemo.action.a" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Intent代碼:
// 啟動(dòng)其他應(yīng)用的Activity
Intent intent = new Intent();
//action
intent.setAction("com.tinytongtong.dividerviewdemo.action.a");
//Category可以不設(shè)置妇多,因?yàn)橐话阍贏ndroidManifest.xml會(huì)設(shè)置Default,startActivity方法中也會(huì)默認(rèn)添加Default燕侠。
if (intent.resolveActivity(getPackageManager()) != null) {
LogUtils.e("match success");
startActivity(intent);
} else {
LogUtils.e("match failure");
}
IntentFilter匹配規(guī)則
隱式Intent調(diào)用分為兩部分者祖,一部分是AndroidManifest中組件的<intent-filter>配置,一部分是Intent對(duì)象的構(gòu)建绢彤。
只有當(dāng)我們構(gòu)建的Intent對(duì)象符合目標(biāo)組件的<intent-filter>配置的時(shí)候七问,才能成功啟動(dòng)目標(biāo)組件。
那么如何才能匹配上<intent-filter>的配置呢茫舶?這個(gè)就是我們要說(shuō)的IntentFilter的匹配規(guī)則械巡。
<intent-filter>中的過(guò)濾信息有三種,分別是action奇适、category坟比、data。下面是一個(gè)過(guò)濾規(guī)則的實(shí)例:
<activity
android:name=".IActivity"
android:label="IActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name=“com.tinytongtong.dividerviewdemo.action.11" />
<action android:name=“com.tinytongtong.dividerviewdemo.action.22" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name=“com.tinytongtong.dividerviewdemo.category.11" />
<data
android:host="www.tiny.com"
android:mimeType="text/plain"
android:port="8080"
android:scheme="http" />
</intent-filter>
</activity>
匹配規(guī)則
為了匹配過(guò)濾列表嚷往,需要同時(shí)匹配過(guò)濾列表中的action、category柠衅、data信息皮仁,否則匹配失敗。
一個(gè)過(guò)濾列表中的action菲宴、category和data可以有多個(gè)贷祈,所有的action、category喝峦、data分別構(gòu)成不同類別势誊,同一類別的信息共同約束當(dāng)前類別的匹配過(guò)程。
只有一個(gè)Intent同時(shí)匹配action谣蠢、category粟耻、data才算完全匹配,只有完全匹配才能成功啟動(dòng)Activity眉踱。
另外一點(diǎn)挤忙,一個(gè)activity中可以有多個(gè)intent-filter,一個(gè)Intent只要能匹配任何一組Intent-filter即可成功啟動(dòng)對(duì)應(yīng)的activity谈喳。
action
action是一個(gè)字符串册烈,該字符串區(qū)分大小寫。系統(tǒng)預(yù)定義了一些action婿禽,同時(shí)我們也可以在應(yīng)用中定義自己的action赏僧。
一個(gè)<intent-filter>中可以有多個(gè)action大猛,此時(shí)Intent中的action能夠和<intent-filter>中的任何一個(gè)action相同即可匹配成功。
另外淀零,<intent-filter>中的action和Intent中的action都是必須的挽绩,就是說(shuō)<intent-filter>中至少指定一個(gè)action,同理Intent中也必須設(shè)置action窑滞,否則就沒有任何意義了琼牧。
category
category也是一個(gè)字符串,也區(qū)分大小寫哀卫。系統(tǒng)預(yù)定義了一些category巨坊,同時(shí)我們也可以在應(yīng)用中定義自己的category。
我們一般說(shuō)category有默認(rèn)值此改,是由于系統(tǒng)在調(diào)用startActivity或者startActivityForResult的時(shí)候會(huì)默認(rèn)為Intent加上“android.intent.category.DEFAULT”這個(gè)category趾撵。
因此,我們的<intent-filter>配置中必須添加對(duì)應(yīng)的配置共啃,不然會(huì)匹配失敗占调。
<intent-filter>
...
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
Intent中我們可以不設(shè)置category,因?yàn)橄到y(tǒng)默認(rèn)給我們添加了“android.intent.category.DEFAULT”移剪。如果我們要添加category的話究珊,這個(gè)category就必須跟</intent-filter>的任意一個(gè)匹配,否則會(huì)匹配失敗纵苛。
data
data語(yǔ)法
data語(yǔ)法如下所示:
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
data由兩部分組成剿涮,mimeType和URI。
mimeType指媒體類型攻人,比如image/jpeg
取试、audio/mpeg4-generic
和video/*
等,可以表示圖片怀吻、文本瞬浓、視頻等不同的媒體格式。
URI包含的數(shù)據(jù)比較多蓬坡,結(jié)構(gòu)如下所示:
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]
具體示例如下所示:
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
接下來(lái)介紹每一個(gè)數(shù)據(jù)的含義:
①android:scheme
URI的模式猿棉,比如http、file渣窜、content等铺根。如果URI中沒有指定scheme,那么整個(gè)URI的其他參數(shù)無(wú)效乔宿,這也意味著URI是無(wú)效的位迂。
②android:host
URI的主機(jī)名,比如www.baidu.com。如果host未指定掂林,那么整個(gè)URI中的其他參數(shù)無(wú)效臣缀,這也意味著URI是無(wú)效的。
③Android:port
URI中的端口號(hào)泻帮,比如80精置,僅當(dāng)URI中指定了scheme和host參數(shù)的時(shí)候port參數(shù)才是有意義的。
④android:path锣杂、android:pathPrefix脂倦、android:pathPattern
這三個(gè)參數(shù)表述路徑信息,其中path表示完整的路徑信息元莫;
pathPrefix表示路徑的前綴信息赖阻;
pathPattern也表示完整的路徑信息,但是它里面可以包含通配符“*”
踱蠢,“*”
表示0個(gè)或多個(gè)任意字符火欧,需要注意的事,由于正則表達(dá)式的規(guī)范茎截,如果想表示真實(shí)的字符串苇侵,那么“*”
要寫成“\\*”
,“\”
要寫成“\\\\”
企锌。
另外榆浓,data有兩種特殊寫法:下面兩種寫法是等價(jià)的。
<intent-filter . . . >
<data android:scheme="something" android:host="project.example.com" />
. . .
</intent-filter>
<intent-filter . . . >
<data android:scheme="something" />
<data android:host="project.example.com" />
. . .
</intent-filter>
data的匹配規(guī)則
data是非必須的撕攒,可以不設(shè)置哀军。但是如果在</intent-filter>定義了data,那么Intent中也必須設(shè)置可匹配的data打却。
再來(lái)看看data內(nèi)部:
</intent-filter>的URI有默認(rèn)值file和content,如果設(shè)置了URI谎倔,則默認(rèn)值就失效柳击。
</intent-filter>的mimeType可以不設(shè)置。
data的匹配意味著mimeType和URI同時(shí)匹配片习。
綜合以上所有情況捌肴,這里分幾種情況:
①data中只配置了mimeType:
<intent-filter>
...
<data android:mimeType="image/*" />
</intent-filter>
由于這里只配置了mimeType,所以會(huì)使用默認(rèn)的URI藕咏,默認(rèn)的URI的scheme為file或content状知。
所以使用下面這兩段代碼可以匹配:
intent.setDataAndType(Uri.parse("content://maolegemi"), "image/jpeg");
// 下面這段在api大于24的版本上會(huì)報(bào)錯(cuò)FileUriExposedException,需要將file替換為content
intent.setDataAndType(Uri.parse("file://maolegemi"), "image/jpeg");
②data中只配置了URI:
<intent-filter>
...
<data
android:host="www.tiny.com"
android:port="8080"
android:scheme="http" />
</intent-filter>
對(duì)應(yīng)匹配代碼如下:
intent.setDataAndType(Uri.parse("http://www.tiny.com:8080/abcdefg"), null);
③data中同時(shí)配置了mimeType和URI:
<intent-filter>
...
<data
android:host="www.tiny.com"
android:port="8080"
android:mimeType="text/plain"
android:scheme="http" />
</intent-filter>
對(duì)應(yīng)的匹配代碼如下:
intent.setDataAndType(Uri.parse("http://www.tiny.com:8080/abcdefg"), "text/plain");
總結(jié)
綜上所述孽查,對(duì)<intent-filter>而言饥悴,必不可少的配置是<cation>和默認(rèn)的category。
對(duì)Intent而言,必不可少的是action西设,因?yàn)槟J(rèn)的category會(huì)添加瓣铣。
如果<intent-filter>定義了data,不管mimeType是否設(shè)置贷揽,Intent中都必須設(shè)置uri棠笑,因?yàn)閡ri有默認(rèn)值。
參考
Android開發(fā)藝術(shù)探索
https://developer.android.com/guide/components/intents-filters?hl=zh-cn
https://developer.android.com/guide/topics/manifest/data-element