一仰剿、顯式Intent和隱式Intent
參考
Android理解:顯式和隱式Intent
Android中Intent概述及使用
顯式的Intent:如果Intent中明確包含了要啟動(dòng)的組件的完整類名(包名及類名)谋竖,那么這個(gè)Intent就是explict的,即顯式的。使用顯式Intent最典型的情形是在你自己的App中啟動(dòng)一個(gè)組件,因?yàn)槟阕约嚎隙ㄖ雷约旱囊獑?dòng)的組件的類名。比如缆镣,為了響應(yīng)用戶操作通過顯式的Intent在你的App中啟動(dòng)一個(gè)Activity或啟動(dòng)一個(gè)Service下載文件。
隱式的Intent:如果Intent沒有包含要啟動(dòng)的組件的完整類名肌似,那么這個(gè)Intent就是implict的费就,即隱式的诉瓦。雖然隱式的Intent沒有指定要啟動(dòng)的組件的類名川队,但是一般情況下,隱式的Intent都要指定需要執(zhí)行的action睬澡。一般固额,隱式的Intent只用在當(dāng)我們想在自己的App中通過Intent啟動(dòng)另一個(gè)App的組件的時(shí)候,讓另一個(gè)App的組件接收并處理該Intent煞聪。例如斗躏,你想在地圖上給用戶顯示一個(gè)位置,但是你的App又不支持地圖展示昔脯,這時(shí)候你可以將位置信息放入到一個(gè)Intent中啄糙,然后給它指定相應(yīng)的action笛臣,通過這樣隱式的Intent請求其他的地圖型的App(例如Google Map、百度地圖等)來在地圖中展示一個(gè)指定的位置隧饼。隱式的Intent也體現(xiàn)了Android的一種設(shè)計(jì)哲學(xué):我自己的App無需包羅萬象所有功能沈堡,可以通過與其他App組合起來,給用戶提供很好的用戶體驗(yàn)燕雁。而連接自己的App與其他App的紐帶就是隱式Intent诞丽。
- 顯式Intent
第一個(gè)參數(shù)是活動(dòng)上下文,第二個(gè)是目標(biāo)活動(dòng)的class
FirstActivity:
button.setOnClickListener(new OnClickListener(){
public void onClick(View v){
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
- 隱式Intent
由系統(tǒng)分析Intent拐格,并找出合適的activity去啟動(dòng)僧免。
FirstActivity
button.setOnClickListener(new OnClickListener(){
public void onClick(View v){
Intent intent = new Intent("com.example.activitytest.ACTION_START");
//setAction方式是一樣的
//Intent intent = new Intent();
//intent.setAction("com.example.activitytest.ACTION_START");
intent.addCategory("com.example.activitytest.MY_CATEGORY");
startActivity(intent);
}
});
SecondActivity
<activity android:name="SecondActivity">
<intent-filter>
<!-- 意圖過濾器, 只要滿足了action(動(dòng)作)捏浊, 和 category(分類),那么就啟動(dòng)這個(gè)界面 -->
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="com.example.activitytest.MY_CATEGORY"/>
<category android:name="android.intent.category.DEFAULT"/>
//default category一定要有懂衩,這是默認(rèn)的
</intent-filter>
通過設(shè)置Action字符串,表明自己的意圖金踪,即我想干嘛勃痴,需要由系統(tǒng)解析,找到能夠處理這個(gè)Intent的Activity并啟動(dòng)热康。比如我想打電話沛申,則可以設(shè)置Action為"android.intent.action.DIAL"字符串,表示打電話的意圖姐军,系統(tǒng)會找到能處理這個(gè)意圖的Activity铁材,例如調(diào)出撥號面板。
有幾點(diǎn)需要注意:
1奕锌、這個(gè)Activity其他應(yīng)用程序也可以調(diào)用著觉,只要使用這個(gè)Action字符串。這樣應(yīng)用程序之間交互就很容易了,例如手機(jī)QQ可以調(diào)用QQ空間黄绩,可以調(diào)用騰訊微博等冤荆。因?yàn)槿绱耍瑸榱朔乐箲?yīng)用程序之間互相影響肄鸽,一般命名方式是包名+Action名,例如命名"abcdefg"就很不合理了油啤,就應(yīng)該改成"com.example.app016.MyTest"典徘。
2、當(dāng)然益咬,你可以在自己的程序中調(diào)用其他程序的Action逮诲。
例如可以在自己的應(yīng)用程序中調(diào)用撥號面板:
Intent intent = new Intent(Intent.ACTION_DIAL);
// 或者Intent intent = new Intent("android.intent.action.DIAL");
// Intent.ACTION_DIAL是內(nèi)置常量,值為"android.intent.action.DIAL"
startActivity(intent);
3、一個(gè)Activity可以處理多種Action
只要你的應(yīng)用程序夠牛逼梅鹦,一個(gè)Activity可以看網(wǎng)頁裆甩,打電話,發(fā)短信齐唆,發(fā)郵件淑掌。。蝶念。當(dāng)然可以抛腕。
Intent的Action只要是其中之一,就可以打開這個(gè)Activity媒殉。
<activity
android:name="com.example.app016.SecondActivity">
<intent-filter>
<!-- 可以處理下面三種Intent -->
<action android:name="com.example.app016.SEND_EMAIL"/>
<action android:name="com.example.app016.SEND_MESSAGE"/>
<action android:name="com.example.app016.DAIL"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
對于一個(gè)Action字符串担敌,系統(tǒng)有可能會找到一個(gè)Activity能處理這個(gè)Action,也有可能找到多個(gè)Activity廷蓉,也可能一個(gè)都找不到全封。
1、找到一個(gè)Activity
很簡單桃犬,直接打開這個(gè)Activity刹悴。這個(gè)不需要解釋。
2攒暇、找到多個(gè)Acyivity
系統(tǒng)會提示從多個(gè)activity中選擇一個(gè)打開土匀。
例如我們自己開發(fā)一個(gè)撥號面板應(yīng)用程序,可以設(shè)置activity的<intent-filter>中Action name為"android.intent.action.DIAL"形用,這樣別的程序調(diào)用撥號器時(shí)就轧,用戶可以從Android自帶的撥號器和我們自己開發(fā)的撥號器中選擇。
<activity
android:name="com.example.app016.SecondActivity">
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
這也就是當(dāng)Android手機(jī)裝上UC瀏覽器后田度,打開網(wǎng)頁時(shí)會彈出選擇Android自帶瀏覽器還是UC瀏覽器妒御,可能都會遇到過。
3镇饺、一個(gè)Activity都沒找到
一個(gè)都沒找到的話乎莉,程序就會出錯(cuò),會拋出ActivityNotFoundException奸笤。比如隨便寫一個(gè)action字符串:
Intent intent = new Intent("asasasas");
startActivity(intent);
所以應(yīng)該注意try catch異常惋啃。
Intent intent = new Intent("asasasas");
try
{
startActivity(intent);
}
catch(ActivityNotFoundException e)
{
Toast.makeText(this, "找不到對應(yīng)的Activity", Toast.LENGTH_SHORT).show();
}
或者也可以使用Intent的resolveActivity方法判斷這個(gè)Intent是否能找到合適的Activity,如果沒有揭保,則不再startActivity肥橙,或者可以直接禁用用戶操作的控件魄宏。
Intent intent = new Intent(Intent.ACTION_DIAL);
if(intent.resolveActivity(getPackageManager()) == null)
{
// 設(shè)置控件不可用
}
注意resolveActivity方法返回值就是顯式Intent上面講到的ComponentName對象秸侣,一般情況下也就是系統(tǒng)找到的那個(gè)Activity。但是如果有多個(gè)Activity可供選擇的話,則返回的Component是com.android.internal.app.ResolverActivity味榛,也就是用戶選擇Activity的那個(gè)界面對應(yīng)的Activity椭坚,這里不再深究。
Intent intent = new Intent(Intent.ACTION_DIAL);
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null)
{
String className = componentName.getClassName();
Toast.makeText(this, className, Toast.LENGTH_SHORT).show();
}
當(dāng)創(chuàng)建了一個(gè)顯式Intent去啟動(dòng)Activity或Service的時(shí)候搏色,系統(tǒng)會立即啟動(dòng)Intent中所指定的組件善茎。
當(dāng)創(chuàng)建了一個(gè)隱式Intent去使用的時(shí)候,Android系統(tǒng)會將該隱式Intent所包含的信息與設(shè)備上其他所有App中manifest文件中注冊的組件的Intent Filters進(jìn)行對比過濾频轿,從中找出滿足能夠接收處理該隱式Intent的App和對應(yīng)的組件垂涯。如果有多個(gè)App中的某個(gè)組件都符合條件,那么Android會彈出一個(gè)對話框讓用戶選擇需要啟動(dòng)哪個(gè)App航邢。
Intent Filter耕赘,即Intent過濾器,一個(gè)組件可以包含0個(gè)或多個(gè)Intent Filter膳殷。Intent Filter是寫在App的manifest文件中的操骡,其通過設(shè)置action或uri數(shù)據(jù)類型等指明了組件能夠處理接收的Intent的類型。如果你給你的Activity設(shè)置了Intent Filter赚窃,那么這就使得其他的App有可能通過隱式Intent啟動(dòng)你的這個(gè)Activity册招。反之,如果你的Activity不包含任何Intent Filter勒极,那么該Activity只能通過顯式Intent啟動(dòng)是掰,由于我們一般不會暴露出我們組件的完整類名,所以這種情況下辱匿,其他的App基本就不可能通過Intent啟動(dòng)我們的Activity了(因?yàn)樗麄儾恢涝揂ctivity的完整類名)冀惭,只能由我們自己的App通過顯式Intent啟動(dòng)。
需要注意的是掀鹅,為了確保App的安全性散休,我們應(yīng)該總是使用顯式Intent去啟動(dòng)Service并且不要為該Service設(shè)置任何的Intent Filter。通過隱式的Intent啟動(dòng)Service是有風(fēng)險(xiǎn)的乐尊,因?yàn)槟悴淮_定最終哪個(gè)App中的哪個(gè)Service會啟動(dòng)起來以響應(yīng)你的隱式Intent戚丸,更悲催的是,由于Service沒有UI的在后臺運(yùn)行扔嵌,所以用戶也不知道哪個(gè)Service運(yùn)行了限府。
二、傳遞數(shù)據(jù)
- 向下一個(gè)活動(dòng)傳遞數(shù)據(jù)
FirstActivity:
button.setOnClickListener(new OnClickListener(){
public void onClick(View v){
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
***intent.putExtra("extra_data","Hello");***
startActivity(intent);
}
});
SecondActivity
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
//getIntExtra getBooleanExtra 取值方式與傳遞的數(shù)據(jù)要一致
}
- 返回?cái)?shù)據(jù)給上一個(gè)活動(dòng)
startActivityForResult方法也是用于啟動(dòng)活動(dòng)的痢缎,但這個(gè)方法期望在活動(dòng)銷毀時(shí)能夠返回結(jié)果給上一個(gè)活動(dòng)胁勺。
FirstActivity:
button.setOnClickListener(new OnClickListener(){
public void onClick(View v){
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);//請求碼
}
});
//使用startActivityForResult啟動(dòng)活動(dòng),會在銷毀后回調(diào)onActivityResult方法
protected void onActivityResult(int requestCode,int resultCode,Intent data){
switch(requestCode){
case 1:
if(resultCode == RESULT_OK){
String returnData = data.getStringExtra("data_return");
....
}
}
}
SecondActivity
Intent intent = new Intent();
***intent.putExtra("data_return","Hello");***
setResult(RESULT_OK,intent);//專門用于向上一個(gè)活動(dòng)返回?cái)?shù)據(jù)
}
利用Intent的Extra部分來存儲我們想要傳遞的數(shù)據(jù)独旷,可以傳送int, long, char等一些基礎(chǔ)類型,對復(fù)雜的對象就無能為力了署穗。
傳復(fù)雜對象參考
Android Bundle總結(jié)
Android五種數(shù)據(jù)傳遞方法匯總
Intent傳遞對象——Serializable和Parcelable區(qū)別
Android系統(tǒng)中Parcelable和Serializable的區(qū)別
1.使用Serializable很簡單,指定Person類implements Serializable即可寥裂。注意,要指定serialVersionUID案疲,否則反序列化會出問題封恰。
可參考如何讓Android Studio 自動(dòng)生成 serialVersionUID
Person person = new Person();
person.setName("tom");
person.setAge(20);
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);
SecondActivity:
Person person = (Person)getIntent().getSerializableExtra("person_data");
2.Parcelable稍微復(fù)雜一些,要實(shí)現(xiàn)幾個(gè)方法褐啡。
public class Person implements Parcelable{
private String name;
private int age;
public int describeContents(){
return 0;
}
public void writeToParcel(Parcel dest,int flags){
dest.writeString(name);
dest.writeInt(age);
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>(){
public Person createFromParcel(Parcel source){
Person person = new Person();
person.name = source.readString();
person.age = source.readInt();
return person;
}
public Person[] newArray(int size){
return new Person[size];
}
};
SecondActivity:
Person person = (Person)getIntent().getPacelableExtra("person_data");
三诺舔、Dart&Henson
參考
【譯】使用 Dart & Henson 改進(jìn) Android Intents
用Dart&Henson玩轉(zhuǎn)Activity跳轉(zhuǎn)
上面?zhèn)鬟f數(shù)據(jù)的方式很好地處理了組件的創(chuàng)建與通信,但仍然有一些問題需要注意:
- 目標(biāo)組件作為一個(gè)實(shí)體备畦,對輸入沒有任何控制權(quán)低飒。在我們的例子里,itemId 是必需的懂盐,但如果沒有傳遞它逸嘀,DetailActivity 最好的處理方式也只能是拋出異常。
- Intent 的創(chuàng)建(完全)不夠健壯允粤,并沒有對 extra 中的 key 或 value 做任何檢查崭倘。
解決這些問題的一個(gè)可能的方案是 Intent 工廠模式。它主要由一些工廠方法組成类垫,包含了應(yīng)用程序里用到的各種 Intent司光。比如像下面這樣的 Intent 工廠:
public class IntentFactory {
public Intent newDetailActivityIntent(Context context, String itemId, boolean showMap) {
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_ITEM_ID, itemId);
intent.putExtra(EXTRA_SHOW_MAP, showMap);
return intent;
}
...
}
然而,這種解決方案有一些局限悉患,并不是一個(gè)很好的辦法残家。
- Intent 工廠是一個(gè)集中類,這個(gè)類可能會變得很大且復(fù)雜售躁。
- 它違背了開放/閉合原則坞淮。對修改并沒有關(guān)閉,我們將總是需要給每個(gè)新的 Activity 添加一個(gè)新方法陪捷。
- 目標(biāo)組件應(yīng)該是唯一知曉參數(shù)細(xì)節(jié)與邏輯的地方回窘。
-
可選參數(shù)處理。同一個(gè)組件有不同的需求市袖,是否應(yīng)該寫不同的方法啡直?還是寫一個(gè)方法并使用默認(rèn)值?
它會誘使后續(xù)的開發(fā)人員模仿苍碟,進(jìn)而產(chǎn)生其他的 Intent 工廠酒觅,最終演變成大泥球模式,使得代碼越來越糟微峰。
有一個(gè)類似的策略可以分散這些工廠方法到各自的目標(biāo)組件舷丹。也就是指,每個(gè)組件可以包含一個(gè)(或多個(gè))靜態(tài)方法蜓肆,用于生成這些啟動(dòng)它自身的 Intent颜凯。這個(gè)辦法可以解決開放/閉合原則的問題谋币,分解 Intent 工廠,也許還可以避免大泥球模式装获。盡管如此瑞信,關(guān)于可選參數(shù)的問題依然存在厉颤。有人說 builder 模式可以穴豫?我們自己實(shí)現(xiàn)它?...
Dart 是一個(gè) Android 開源庫(Henson實(shí)際是Dart項(xiàng)目的子項(xiàng)目)逼友。它綁定 Activity 字段到 Intent extra精肃,Butter Knife 也是用類似的方案,關(guān)聯(lián) Activity 與 XML 布局中的View帜乞。
這里我們假設(shè)要從MainActivity跳轉(zhuǎn)到DetailActivity司抱,DetailActivity中要接受三個(gè)參數(shù)分別是String name,int age和User user。DetailActivity需要接受上述三個(gè)參數(shù)黎烈,僅僅通過@InjectExtra注解即可习柠,然后在onCreate
中執(zhí)行Dart.inject(this),詳細(xì)的代碼為:
public class DetailActivity extends AppCompatActivity {
@InjectExtra
String name = "default name";
@InjectExtra
int age = 0;
@Nullable
@InjectExtra
User user;
@BindView(R.id.tvName)
TextView tvName;
@BindView(R.id.tvAge)
TextView tvAge;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
ButterKnife.bind(this);
Dart.inject(this);
initView();
}
private void initView() {
// 使用name,age,user
}
}
String name = "default name";這句話給了name一個(gè)默認(rèn)值照棋,但是當(dāng)Dart.inject執(zhí)行后會被傳遞過來的數(shù)據(jù)覆蓋资溃。
@Nullable加在了user上說明這個(gè)數(shù)據(jù)可以不用傳遞。接受端就這么多代碼烈炭,下面讓我們看看發(fā)送端如果發(fā)送數(shù)據(jù)溶锭,如何跳轉(zhuǎn)到DetailActivity。
注意編寫上述代碼后符隙,我們要先編譯下項(xiàng)目趴捅,編譯好后Henson會通過注解處理器生成可以跳轉(zhuǎn)到DetailActivity的DSL(領(lǐng)域特定語言)段,方便其他組件對DetailActivity的跳轉(zhuǎn)霹疫,我們在MainActivity上只要寫上下列代碼拱绑,即可完成界面跳轉(zhuǎn)和數(shù)據(jù)傳遞:
User user = new User();
user.setAge(Integer.parseInt(age.getText().toString()));
user.setName(name.getText().toString());
startActivity(
Henson.with(this)
.gotoDetailActivity()
.age(27)
.name("jason")
.user(user)
.build()
);
當(dāng)你寫完Henson.with(this)后代碼提示會自動(dòng)彈出.gotoDetailActivity(),Henson幫助你提示你DetailActivity是可以被跳轉(zhuǎn)的丽蝎;隨后你繼續(xù)寫下.gotoDetailActivity()后欺栗,又自動(dòng)彈出.age()方法,提示你傳入一個(gè)int類型給age征峦,寫好后迟几,又自動(dòng)彈出.name()方法,以此類推栏笆,最后以一個(gè)build()收場类腮。
注意,這里你可以不寫.user()蛉加,因?yàn)樵贒etailActivity中我們指定它是nullable的蚜枢,可以不傳缸逃。但是.age()和.name()都是會強(qiáng)制彈出讓你填寫數(shù)據(jù)的。如果我偏向要對調(diào)name和age的傳入順序呢厂抽?發(fā)現(xiàn)不行~如果你寫完.gotoDetailActivity()后發(fā)現(xiàn)只有.age()的提示需频,卻沒有.name()的,即強(qiáng)制要求你先傳遞age筷凤。這里作者說明后才知道他們是按照字幕順序來排數(shù)據(jù)傳入的昭殉,目前沒有更好的方法。