前言
上篇文章介紹了Activity的生命周期岂昭,Activity還有一個重要的知識點(diǎn)冠王,就是Activity的啟動模式九孩,面對形形色色的啟動模式搅荞,需要夯實(shí)啟動模式的基礎(chǔ)才能正確使用項(xiàng)目所需要的啟動模式躏惋。
Activity的LaunchMode
你是否試過多次啟動同一個Activity幽污,如果你這樣做了的話,會發(fā)現(xiàn)你創(chuàng)建了多個一模一樣的Activity簿姨,這在大部分app中是沒有意義的距误,但為什么會出現(xiàn)這個現(xiàn)象呢?這就與Activity的啟動模式和任務(wù)棧有關(guān)系了扁位。先說任務(wù)棧准潭,就是一個后進(jìn)先出的棧結(jié)構(gòu),當(dāng)你每次按下返回鍵時贤牛,就會將棧頂部的Activity彈出棧惋鹅。這個比較好理解,但啟動模式卻有四種:standard殉簸,singleTop闰集,singleTask,singleInstance般卑。下面分別說明這四種啟動模式:
standard:標(biāo)準(zhǔn)模式武鲁,也是多實(shí)例模式,是Activity的默認(rèn)啟動模式蝠检,當(dāng)要啟動的Activity為這種模式時沐鼠,會創(chuàng)建這個Activity并將該Activity放入開啟該Activity的任務(wù)棧中。舉例來說叹谁,比如Activity A 開啟了Activity B饲梭,Activity的啟動模式是stardard,那么會調(diào)用響應(yīng)的生命周期方法焰檩,并將Activity B放入Activity A 所在的任務(wù)棧中憔涉。
singleTop:棧頂復(fù)用模式,當(dāng)將要開啟的Activity在任務(wù)棧的棧頂?shù)臈m斘錾唬瑒t不會重新創(chuàng)建新的Activity兜叨,只是會調(diào)用onNewIntent方法來接收新的數(shù)據(jù),不會調(diào)用Activity創(chuàng)建時的相關(guān)生命周期方法衩侥。如果當(dāng)前任務(wù)棧的棧頂沒有該Activity的實(shí)例国旷,則創(chuàng)建新的Activity并調(diào)用相關(guān)的生命周期方法。
舉例來說茫死,如果當(dāng)前的任務(wù)棧里有ABCD(D在棧頂)跪但,此時開啟Activity D,且D的啟動模式為singleTop則D不會再次被創(chuàng)建璧榄,而是調(diào)用newIntent方法特漩,當(dāng)前的任務(wù)棧還是ABCD吧雹。如果D的啟動模式為standard,則會創(chuàng)建新的Activity D 的實(shí)例到任務(wù)棧涂身,則任務(wù)棧里為ABCDD雄卷。
singleTask:棧內(nèi)復(fù)用模式,如果有當(dāng)前Activity需要的任務(wù)棧且整個棧里有要開啟的Activity實(shí)例蛤售,則不會創(chuàng)建新的Activity實(shí)例丁鹉,而是調(diào)用newIntent方法。如果該實(shí)例沒有處在棧頂?shù)脑掋材埽瑫⑵渖系膶?shí)例全部出棧揣钦,而當(dāng)前如果沒有Activity所需要的任務(wù)棧,則會創(chuàng)建這個任務(wù)棧漠酿,并創(chuàng)建該Activity的實(shí)例冯凹,將該實(shí)例放入任務(wù)棧中,此時新創(chuàng)建的任務(wù)棧為為前臺任務(wù)棧炒嘲,之前的任務(wù)棧為后臺任務(wù)宇姚。舉例來說當(dāng)前的的棧為ABC且該任務(wù)棧是C的任務(wù)棧,現(xiàn)在要開啟C夫凸,則不會創(chuàng)建C的新實(shí)例浑劳,調(diào)用其onNewIntent方法。如果當(dāng)前的棧為ABCD且該任務(wù)棧是C的任務(wù)棧 夭拌,現(xiàn)在要開啟C魔熏,則會將D出棧,C調(diào)用onNewIntent方法鸽扁,此時任務(wù)棧為ABC蒜绽。如果當(dāng)前棧為ABC但不是D所需要的任務(wù)棧,則會創(chuàng)建D需要的任務(wù)棧桶现,并創(chuàng)建D的實(shí)例放入到該任務(wù)棧滓窍,此時該任務(wù)棧里只有D,為前臺任務(wù)棧巩那,原先的任務(wù)棧仍為ABC,為后臺任務(wù)棧此蜈。
singleInstance:單實(shí)例模式即横,屬于singleTask的威力加強(qiáng)版,除了擁有singleTask的特性外裆赵,此種啟動模式下的任務(wù)棧中只有一個Activity實(shí)例东囚。這就是說如果一個Activity是singleInstance啟動模式,當(dāng)該Activity啟動時會創(chuàng)建一個新的任務(wù)棧并將該實(shí)例單獨(dú)放到這個任務(wù)棧中战授,后續(xù)開啟的該Activity均不會創(chuàng)建新的實(shí)例页藻,除非該任務(wù)棧被銷毀了桨嫁。
現(xiàn)在需要指出一個狀況,假設(shè)有兩個任務(wù)棧 份帐,一個任務(wù)棧里有AB璃吧,為前臺任務(wù)棧,一個任務(wù)棧里有CD废境,為后臺任務(wù)棧畜挨,此時要開啟D,因?yàn)槿蝿?wù)棧里有了D噩凹,所以不會再創(chuàng)建D額實(shí)例巴元,任務(wù)棧CD為前臺任務(wù)棧,任務(wù)棧AB為后臺任務(wù)棧驮宴,Activity的后退列表為ABCD(D為棧頂)逮刨。
如果開啟的是C,因?yàn)槿蝿?wù)棧里已經(jīng)有C堵泽,所以不會創(chuàng)建C的實(shí)例修己,將D出棧,此時任務(wù)棧C為前臺任務(wù)棧落恼,任務(wù)棧AB為后臺任務(wù)棧箩退,Activity的后退列表為ABC(C為棧頂)。
還有一點(diǎn)需要說的是任務(wù)棧佳谦,任務(wù)棧的指定需要一個屬性taskAffinity戴涝,該值為一個字符串,該屬性決定了activity所需要的任務(wù)棧的名字钻蔑,在默認(rèn)情況下啥刻,任務(wù)棧的名字為包名。如果制定了Activity的taskAffinity咪笑,任務(wù)棧的名字則為指定的字符串可帽。該屬性主要與singleTask和allowTaskReparenting配合使用,在其他場合下該屬性沒有意義窗怒。當(dāng)與singleTask配合使用時映跟,他是Activity所需要的任務(wù)棧名字,開啟的Activity將在taskAffinity所指定的任務(wù)棧中扬虚。有了這些知識鋪墊我們對上面的例子進(jìn)行一下代碼演示努隙。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhqy.activitylifecycledemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
/>
<activity
android:name=".ThirdActivity"
android:launchMode="singleTask"
android:taskAffinity="com.zxl"
/>
<activity android:name=".FourthActivity"
android:launchMode="singleTask"
android:taskAffinity="com.zxl"
></activity>
</application>
</manifest>
MainActivity(記為A)和SecondActivity(記為B)的啟動模式為standrad模式,任務(wù)棧為默認(rèn)的任務(wù)棧辜昵,任務(wù)棧名字為包名荸镊,ThirdActivity(記為C)和FourthActivity(記為D)的啟動模式為singleTask,任務(wù)棧為com.zxl。其中A打開B躬存,B打開C张惹,C打開D,D打開A岭洲,下面顯示的是Activity后退列表:
首先A打開B宛逗,前臺任務(wù)棧里有AB,當(dāng)B打開C的時候钦椭,因?yàn)镃的任務(wù)棧為com.zxl,而該任務(wù)棧還沒有創(chuàng)建拧额,所以會創(chuàng)建名字為com.zxl的任務(wù)棧,并創(chuàng)建C的實(shí)例放入該任務(wù)棧彪腔,此時任務(wù)棧com.zxl為前臺任務(wù)棧侥锦,默認(rèn)任務(wù)棧為后臺任務(wù)棧。打開D德挣,因?yàn)镃與D的啟動模式和任務(wù)棧都相同恭垦,所以創(chuàng)建D的實(shí)例后會直接進(jìn)入com.zxl的任務(wù)棧鹦蠕。當(dāng)D啟動A時探遵,由于A的啟動方式是standard模式,該模式創(chuàng)建的實(shí)例會進(jìn)入啟動該Activity的任務(wù)棧笆搓,所以A會進(jìn)入名為com.zxl的任務(wù)棧屯掖。所以前臺任務(wù)棧中為CDA玄柏,后臺任務(wù)棧為AB,所以Activity的后退列表為ABCDA(最右邊為棧頂)贴铜,上述分析與試驗(yàn)結(jié)果一致粪摘。
Activity的Flags
Activity的flags有很多,現(xiàn)在主要介紹一些常用的啟動模式的Flag绍坝,大部分情況下不需要給Activity設(shè)置flag,對這些flag理解即可徘意。
FLAG_ACTIVITY_NEW_TASK
這個標(biāo)記位的作用是為Activity指定“singleTask”模式,該模式與xml設(shè)置的一致轩褐。但需要與FLAG_ACTIVITY_CLEAR_TOP一起使用椎咧。
FLAG_ACTIVITY_SINGLE_TOP
這個標(biāo)記位的作用是為Activity指定“singleTop”模式,該模式與xml設(shè)置的一致把介。
FLAG_ACTIVITY_CLEAR_TOIOP
具有此標(biāo)志位的Activity勤讽,當(dāng)它啟動時,在同一個任務(wù)棧中的在之上的所有Activity都要出棧拗踢,該標(biāo)志位需要與FLAG_ACTIVITY_NEW_TASK一起使用地技,這這種情況下如果該Activity的實(shí)例已經(jīng)存在,那么系統(tǒng)會調(diào)用他的onNewIntent方法秒拔。那么如果該Activity的啟動模式為standard,則連同它及其之上的所有Activity實(shí)例全部出棧,并創(chuàng)建一個新的Activity實(shí)例放入堆棧砂缩。
**FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有這個標(biāo)志位的Activity不會出現(xiàn)在歷史Activity列表中作谚,當(dāng)我們不希望用戶通過歷史列表會到我們的Activity的時候這個標(biāo)記比較有用。它等同于在xml指定Activity的屬性 android:excludeFromRecents="true"
IntentFilter的匹配規(guī)則
我們知道Activity的啟動分為兩種方式:顯示啟動和隱式啟動兩種方式庵芭。顯示啟動需要明確啟動的包名和類名等組件信息妹懒,而隱式啟動則不要需要組件信息。顯示啟動和隱式啟動不能同時使用双吆,如果同時使用的話眨唬,顯示啟動優(yōu)先于隱式啟動。這里主要介紹一下隱式啟動好乐,隱式啟動主要是Intent能夠匹配目標(biāo)組件中的IntentFilter中的信息匾竿,如果不能匹配IntentFilter中的信息則無法啟動該Activity。IntentFilter的過濾信息主要由action蔚万,category岭妖,data。實(shí)例如下:
<intent-filter>
<action android:name="com.zxl.action"></action>
<category android:name="com.zxl.category"></category>
<data android:mimeType="text/plain"></data>
</intent-filter>
為了匹配過濾列表反璃,需要同時匹配過濾列表中的action昵慌,category,data信息淮蜈,否則匹配失敗斋攀。一個過濾列表中能夠與多個action,category梧田,data淳蔼。只有一個Intent同時匹配action,category和data類別才算完全匹配柿扣。只有完全匹配才能啟動目標(biāo)activity肖方。一個Activity能有多個IntentFilter,Intent只要能夠匹配一個IntentFilter就能成功開啟目標(biāo)Activity未状。
action匹配規(guī)則
action是一個字符串俯画,系統(tǒng)預(yù)設(shè)了一些action,當(dāng)然用戶也可以自定義一些action司草。一個過濾列表可以有多個action艰垂,而Intent只要完全匹配一個action即可,這里說的完全配置指的是action字符串完全一致埋虹。另外需要注意猜憎,action區(qū)分大小寫,如果Intent沒有設(shè)置action搔课,則會匹配失敗胰柑。
category匹配規(guī)則
category是一個字符串,系統(tǒng)已經(jīng)預(yù)設(shè)了一些category,當(dāng)然用戶也可以自定義柬讨。洗個過濾列表可以有對個category崩瓤。Intent如果設(shè)置了category則需要與過濾列表中的一個category匹配。當(dāng)然Intent也可以指定category踩官,那么則會走默認(rèn)的category却桶,前提是IntentFilter中有默認(rèn)的category ="android.intent.category.DEFAULT"。
data匹配規(guī)則
如果IntentFilter設(shè)置了data蔗牡,則Intent必須有一個data與IntentFillter設(shè)置的一致颖系。data由一下成分構(gòu)成
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string">
</data>
data由uri和mimetype組成,現(xiàn)在通過一個實(shí)際的例子來說明uri
http://www.baidu.com:8080/search/search.info辩越,
scheme:URI的模式嘁扼,比如http,file,content,如果沒有指定scheme区匣,該uri無效
host:URI的主機(jī)名偷拔,www.baidu.com,如果沒有指定host亏钩,該uri無效
port:URI的端口號,8080,僅當(dāng)URI指定了scheme和host時才有效
path,pathPattern,pathPrefix:表示uri路徑信息莲绰,其中path表示完整路徑信息,pathPattern也表示完整的路徑信息姑丑,但是他可以有通配符“*”蛤签,表示一個或多個字符,pathPrefix表示路徑前綴信息
mimeType:指定媒體類型例如“image/png”,如果沒有指定uri則有默認(rèn)值栅哀,默認(rèn)值是uri的scheme必須為file和content開頭的uri才能匹配震肮。data的實(shí)例如下: intent.setDataAndType(Uri.parse("http://www.baidu.com:8080/search/search.info"),"image/png");其中setDataAndType會同時設(shè)置URI和MimeType,只設(shè)置URI或MimeType會清除設(shè)置的另一個數(shù)據(jù)留拾。
下面以實(shí)例說明Activity的隱式啟動
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhqy.activitylifecycledemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity">
<intent-filter>
<action android:name="com.zxl.cation"></action>
<category android:name="com.zxl.category"></category>
<data
android:scheme="http"
android:host="www.baidu.com"
android:port="80"
android:path="/search"
android:mimeType="image/png"
></data>
</intent-filter>
</activity>
</application>
</manifest>
SecondActivity設(shè)置了一個IntentFilter戳晌,當(dāng)MainActivity開啟SecondActivity需要滿足IntentFilter的過濾條件,由于沒有設(shè)置默認(rèn)的category痴柔,所以category也需要設(shè)置沦偎。
MainActivity的代碼如下
package com.zhqy.activitylifecycledemo;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_first=findViewById(R.id.btn_first);
btn_first.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,SecondActivity.class);
intent.setAction("com.zxl.action");
intent.addCategory("com.zxl.category");
intent.setDataAndType(Uri.parse("http://www.baidu.com/search/search.info"),"image/png");
startActivity(intent);
}
});
Log.e(TAG, "onCreate: ");
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart: ");
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume: ");
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG, "onPause: ");
}
@Override
protected void onStop() {
super.onStop();
Log.e(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: ");
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.e(TAG, "onSaveInstanceState: ");
outState.putInt("data",123);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.e(TAG, "onRestoreInstanceState: ");
int data=savedInstanceState.getInt("data");
Log.e(TAG, "恢復(fù)出來的數(shù)據(jù)"+data);
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
}
測試結(jié)果:
當(dāng)滿足了IntentFilter的過濾添加后可以正確打開SecondActivity