任務(wù)棧
先來一段來自官網(wǎng)的介紹
A task is a collection of activities that users interact with when performing a certain job. The activities are arranged in a stack—the back stack)—in the order in which each activity is opened. For example, an email app might have one activity to show a list of new messages. When the user selects a message, a new activity opens to view that message. This new activity is added to the back stack. If the user presses the Back button, that new activity is finished and popped off the stack...(當然后面還有)
大致意思是:任務(wù)是用戶與Activity交互時的集合,Activity被放置于棧--回退棧--按照Activity打開的順序坛缕。舉個例子墓猎,一個電子郵件的app可以有一個Activity去展示郵件的列表,當用戶點擊了一個郵件赚楚,會打開一個關(guān)于這個郵件Activity毙沾。這個新的Activity會被加到回退棧中。如果用戶按下了返回鍵宠页,這個新的Activity會被關(guān)閉并彈出回退棧(出棧)
英文不太好左胞,大家可以去官網(wǎng)看看(官網(wǎng)task)
個人理解
你可以理解為處于棧頂?shù)腁ctivity就是我們看到的頁面,頁面的切換都是通過入棧出椌倩В控制的烤宙。
比如一個app,進入到主頁a(此時a處于棧頂)敛摘,然后打開新的頁面b门烂,這時候頁面b入棧,即頁面b處于棧頂,頁面a處于頁面b下方屯远。按下返回鍵蔓姚,從頁面b回到頁面a,此時頁面b出棧慨丐,頁面a就處于了棧頂坡脐,也就是此時我們看到的是頁面a
任務(wù)棧中Activity的順序永遠不會重新排列
遵守后進先出(Last In First Out)原則
為什么需要LaunchMode
譬如qq的新消息通知,如果我之前已經(jīng)在這個聊天頁面了房揭,那我點擊這個消息通知备闲,總不能又新創(chuàng)建一次聊天頁面吧,這個時候LaunchMode(啟動模式)就起到至關(guān)重要的作用捅暴。目前有4種啟動模式:standard恬砂、singleTop、singleTask和singleInstance蓬痒。在AndroidManifest清單文件中<activity>標簽中的LaunchMode中申明泻骤,譬如
<activity
android:name=".MainActivity"
android:launchMode="standard"/>
standard
標準模式,同時也是系統(tǒng)默認的模式梧奢,就算不像上面那樣指定LaunchMode狱掂,系統(tǒng)也會默認給你設(shè)置為standard。在該模式下亲轨,會創(chuàng)建新的Activity實例并入棧趋惨,舉個例子:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@BindView(R.id.button)
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
button.setOnClickListener(v->startActivity(new Intent(this,MainActivity.class)));
}
點擊button后,每次都會打開新的MainActivity 惦蚊。
singleTop
棧頂復(fù)用模式器虾。在該模式下,如果頁面處于棧頂养筒,那么不會創(chuàng)建新的實例曾撤,同時它的onNewIntent方法會被回調(diào)端姚,通過該方法的參數(shù)可以取出當前請求的信息晕粪,但是onCreate這些生命周期不會被回調(diào)。
public class WeclomeActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: ");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startActivity(new Intent(this,MainActivity.class)
.putExtra("key","key"));
}
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@BindView(R.id.button)
Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main4);
ButterKnife.bind(this);
btn.setOnClickListener(v -> startActivity(new Intent(this, MainActivity.class)
.putExtra("name", "str")));
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent: "+getIntent().getStringExtra("key"));
Log.d(TAG, "onNewIntent: "+intent.getStringExtra("name"));
Log.d(TAG, "onNewIntent: "+intent.getStringExtra("key"));
setIntent(new Intent(this,MainActivity.class)
.putExtra("name","newName")
.putExtra("key","newKey"));
Log.d(TAG, "onNewIntent: "+getIntent().getStringExtra("key"));
Log.d(TAG, "onNewIntent: "+intent.getStringExtra("name"));
Log.d(TAG, "onNewIntent: "+intent.getStringExtra("key"));
}
}
點擊button后輸出如下:
D/MainActivity: onNewIntent: key
onNewIntent: str
onNewIntent: null
onNewIntent: newKey
onNewIntent: str
onNewIntent: null
如果不設(shè)置setIntent()渐裸,那么getIntent()獲取到的值還是WeclomeActivity傳遞過來的值巫湘。
但是singleTop只適用于被設(shè)置了該LaunchMode的Activity處于棧頂?shù)臅r候不會被重新創(chuàng)建實例,如果該Activity未處于棧頂昏鹃,還是會被創(chuàng)建實例的尚氛。譬如我頁面A設(shè)置了singleTop,然后A->B->C->A洞渤,這個時候阅嘶,棧內(nèi)就會有兩個A元素,即ABCA
應(yīng)用場景:上面我們說到的qq新消息推送,打開聊天頁面就可以應(yīng)用這種模式
singleTask
棧內(nèi)復(fù)用模式讯柔。這是一種單實例的模式抡蛙,該模式下,只要棧內(nèi)存在Activity實例魂迄,那么啟動該Activity都不會重新創(chuàng)建新的實例粗截,并會清空該Activity上所有棧元素,同時也會回調(diào)onNewIntent捣炬。以上指的同一個app中啟動該Activity熊昌,如果是其他應(yīng)用以singleTask模式啟動了這個Activity,那么他會創(chuàng)建新的任務(wù)棧湿酸。但是如果這個app已經(jīng)被啟動過了婿屹,這個app的任務(wù)棧此時處于后臺,那么此時app的任務(wù)棧會從后臺移動到前臺推溃,官網(wǎng)給的示例如下:
有點抽象选泻?我們來舉個例子,比如我此時正在看簡書app美莫,這時候qq推送了一條新的消息页眯,你點進去后進到了qq的聊天頁面,按回退鍵回到qq主頁面厢呵,再按回退鍵就回到了簡書app里面窝撵,這就與上面的圖片的示例一致了。現(xiàn)在再回過頭來看官網(wǎng)給的圖片襟铭,Activity2啟動了ActivityY(啟動模式為singleTask)碌奉,所以Activity Y所在Task被切換到前臺。如果Activity2啟動了Activity X(啟動模式為singleTask)寒砖,則Activity Y也會被出棧赐劣,即棧內(nèi)僅有1、2哩都、X3個頁面魁兼。
singleInstance
單實例模式。加強版singleTask漠嵌,該模式下咐汞,會為指定的Activity單獨開啟一個任務(wù)棧,并且棧內(nèi)只有該Activity儒鹿。
應(yīng)用場景:譬如支付寶的支付頁面化撕。我從某個應(yīng)用開啟了支付寶的支付頁面,這個時候我取消支付约炎,則會回到當前應(yīng)用而非支付寶的其他頁面植阴。
Intent Flag啟動模式
除卻上面在AndroidManifest中指定啟動模式蟹瘾,也可通過設(shè)置Intent的Flag指定啟動模式。下面介紹下常見的Flag:
Intent.FLAG_ACTIVITY_NEW_TASK
如果需要啟動的Activity的棧已存在則不會新建棧掠手,否則會創(chuàng)建新的Task來啟動Activity热芹。該Flag通常可使用在Service中啟動Activity的場景惨撇,因為Service中不存在Activity棧伊脓,所以需要使用該Flag來創(chuàng)建新的Activity棧。雖然官網(wǎng)說與android:launchMode="singleTask"
效果一致魁衙,但是我感覺還是有點差別的报腔。是否建新任務(wù)棧的效果是一致,但是singleTask不會重復(fù)創(chuàng)建實例剖淀,而該Flag是會重復(fù)創(chuàng)建的纯蛾。FLAG_ACTIVITY_SINGLE_TOP
與android:launchMode="singleTop"
效果一致FLAG_ACTIVITY_CLEAR_TOP
與android:launchMode="singleTask"
效果一致FLAG_ACTIVITY_NO_HISTORY
以該模式啟動Activity,該Activity啟動了別的Activity的時候就會自動消失纵隔,不會出現(xiàn)在棧內(nèi)翻诉。
以上兩種方式均可指定LaunchMode,方法一與方法二同時存在時捌刮,方法二Intent Flag可以覆蓋方法一指定的LaunchMode碰煌,即優(yōu)先級高于方法一
再來看看官網(wǎng)給我們的其他一些屬性
- taskAffinity
- allowTaskReparenting
- clearTaskOnLaunch
- alwaysRetainTaskState
- finishOnTaskLaunch
以上說的LaunchMode都是基于同一個應(yīng)用的,上面的singleTask介紹中也說了這一段绅作,如果是其他應(yīng)用以singleTask模式啟動了指定的Activity芦圾,那么會開啟一個新的任務(wù)棧,這個其實也是singleTask的特性之一俄认。指定了singleTask模式的Activity會先去尋找是否有指定了的任務(wù)棧个少,如果沒有則創(chuàng)建新的任務(wù)棧,如果有該任務(wù)棧再去找是否有該Activity實例的存在眯杏,有則復(fù)用并清空該Activity上所有棧元素夜焦,沒有則創(chuàng)建。那么如何指定任務(wù)棧呢岂贩?
taskAffinity
任務(wù)親和性茫经。可以用于指定一個Activity更愿意依附哪一個任務(wù)棧河闰。默認情況下科平,taskAffinity即為應(yīng)用的包名(以下說的指定taskAffinity的屬性值都與包名不一致)。所以指定singleTask+taskAffinity可以啟用新的任務(wù)棧姜性,舉個例子
<activity android:name=".MainActivity"
android:taskAffinity="com.asd.asd"
android:launchMode="singleTask">
清空返回棧
如何用戶將任務(wù)切換到后臺之后過了很長一段時間,系統(tǒng)會將這個任務(wù)中除了最底層的那個Activity之外的其它所有Activity全部清除掉髓考。當用戶重新回到這個任務(wù)的時候部念,最底層的那個Activity將得到恢復(fù)。這個是系統(tǒng)默認的行為,因為既然過了這么長的一段時間儡炼,用戶很有可能早就忘記了當時正在做什么妓湘,那么重新回到這個任務(wù)的時候,基本上應(yīng)該是要去做點新的事情了乌询。
alwaysRetainTaskState
如果將最底層的那個Activity的這個屬性設(shè)置為true榜贴,那么上面所描述的默認行為就將不會發(fā)生,任務(wù)中所有的Activity即使過了很長一段時間之后仍然會被繼續(xù)保留妹田。
clearTaskOnLaunch
如果將最底層的那個Activity的這個屬性設(shè)置為true唬党,那么只要用戶離開了當前任務(wù),再次返回的時候就會將最底層Activity之上的所有其它Activity全部清除掉鬼佣。簡單來講驶拱,就是一種和alwaysRetainTaskState完全相反的工作模式,它保證每次返回任務(wù)的時候都會是一種初始化狀態(tài)晶衷,即使用戶僅僅離開了很短的一段時間蓝纲。
finishOnTaskLaunch
這個屬性和clearTaskOnLaunch是比較類似的,不過它不是作用于整個任務(wù)上的晌纫,而是作用于單個Activity上税迷。如果某個Activity將這個屬性設(shè)置成true,那么用戶一旦離開了當前任務(wù)锹漱,再次返回時這個Activity就會被清除掉翁狐。
IntentFilter匹配規(guī)則
眾所周知,啟動Activity可以分為隱式啟動與顯示啟動凌蔬。
顯示Intent
用于知曉需要跳轉(zhuǎn)的目標組件名稱的前提下露懒,一般應(yīng)用于同一個應(yīng)用程序內(nèi)
//寫法一
Intent intent = new Intent();
intent.setClass(FirstActivity.this, SecondActivity.class);
startActivity(intent);
//寫法二,當然兩種寫法是一樣的
startActivity(new Intent(FirstActivity.this, SecondActivity.class))
隱式Intent
不同于顯示Intent砂心,隱式Intent不關(guān)心接收者是誰懈词,只需要匹配到目標組件IntentFilter中設(shè)置的過濾規(guī)則,即可啟動對應(yīng)的組件辩诞,一般用于不同應(yīng)用程序之間坎弯,也可用于同一個應(yīng)用程序中,譬如最常見的撥打電話:
//權(quán)限問題這里就先不講了
startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse("tel:10086")));
IntentFilter中的過濾信息有action译暂、category抠忘、data,可以有多個action外永、category崎脉、data,但是必須能匹配到一組對應(yīng)的過濾信息才能啟動對應(yīng)的Activity或其他組件伯顶。另外組件也可擁有多個IntentFilter囚灼,一個Intent只要能匹配到一組IntentFilter即可啟動對應(yīng)的Activity或其他組件骆膝。
action匹配的規(guī)則
action是一個字符串常量,一個<intent-filter>標簽必須包含一個或多個action灶体,區(qū)分大小寫阅签,如果有多個<action>只需匹配其中一個即可成功啟動對應(yīng)組件,如果不包含action蝎抽,則無法啟動該組件
//AndroidManifest.xml
<intent-filter>
<action android:name="com.asd.string"/>
<action android:name="com.asd.str"/>
</intent-filter>
//Activity
startActivity(new Intent("com.asd.string"));
如上啟動政钟,會拋出
ActivityNotFoundException
,其實只配置action是不夠的
category匹配的規(guī)則
category同樣是一個字符串樟结,系統(tǒng)預(yù)定義了一些category养交,同時我們也可以定義自己的category。匹配規(guī)則與action大致相同狭吼,可以有多個category层坠,至少有一個匹配。但是由于在startActivity()或startActivityForResult()的時候會默認為Intent附加"android.intent.category.DEFAULT"這個category刁笙,所以隱式Intent啟動的時候破花,必須申明
<category android:name="android.intent.category.DEFAULT"/>
否則會向上面那樣拋出ActivityNotFoundException
data的匹配規(guī)則
同樣類似于action的匹配規(guī)則,但是這個data允許沒有疲吸,data的結(jié)構(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由兩部分組成座每,URI與mimeType。mimeType指的媒體類型摘悴,例如image/jpeg峭梳、audio/mpeg4-generic和video/*等,可以表示圖片蹂喻,文本葱椭,視頻等不同的媒體類型,而URI就是mimeType上面的那些參數(shù)口四。
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]
舉個例子
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/serach/info
scheme
URI的模式孵运,譬如http、file蔓彩、content等治笨,如果URI中沒有指定該參數(shù),則整個URI的其他參數(shù)均無效赤嚼,等同于該URI無效
Host
主機名旷赖,如果URI中沒有指定該參數(shù),則整個URI的其他參數(shù)均無效更卒,等同于該URI無效等孵。如果需要匹配多個子域,可以使用通配符 *
逞壁,且*
需要在第一位流济,譬如*.google.com
可以匹配www.google.com
锐锣,.google.com
和developer.google.com
Port
端口號腌闯,只有指定了scheme與Host绳瘟,該參數(shù)才有意義
path、pathPrefix姿骏、pathPattern
path :表示完整的路徑糖声,譬如上方第二個例子,這里就需寫/serach/info
pathPrefix:路徑的前綴分瘦,從/開始到/serach/info均可匹配蘸泻,譬如/,/s嘲玫,/ser悦施,/serach此類均可匹配,不像path必須完全一致
pathPattern:與path一樣,表示完整的路徑去团,但是允許使用通配符
“” 用來匹配0次或更多抡诞,如:“a” 可以匹配“a”、“aa”土陪、“aaa”…
“.” 用來匹配任意字符昼汗,如:“.” 可以匹配“a”、“b”鬼雀,“c”…
“ * ” 就是用來匹配任意字符0次或更多
需要注意的是由于正則表達式的規(guī)范顷窒,如果想表達真實的字符串,那么“ * ”需要寫成“\*”源哩,“\”需要寫成“ \ \ \ \”(這一段來自官網(wǎng)鞋吉,但是自己實踐的時候發(fā)現(xiàn)反而不用加轉(zhuǎn)義才能匹配到。励烦。谓着。我覺得是哪里出了問題但是我沒有證據(jù)XD)
這三個參數(shù)只要滿足匹配其中一個即可啟動對應(yīng)的Activity
舉個例子吧
btn.setOnClickListener(v -> {
startActivity(new Intent("com.asd.string")
.addCategory("android.intent.category.DEFAULT")
.setDataAndType(Uri.parse("https://www.baidu.com:8080/zxc/qwe/asd.html"),"image/jpeg"));
});
<activity android:name=".intentFilter.IntentFilterActivity">
<intent-filter>
<action android:name="com.asd.string"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https"
android:host="www.baidu.com"
android:port="8080"
android:path="/zxc/qwe/asd.html"
android:pathPrefix="/"
//以下3中均可匹配
android:pathPattern=".*"
android:pathPattern="/.*/.*"
android:pathPattern="/.*/.*/.*"
android:mimeType="image/jpeg"
/>
</intent-filter>
這里還需要注意,date這里的參數(shù)設(shè)置官方給了我們?nèi)齻€方法崩侠,我們來看看
public @NonNull Intent setDataAndType(@Nullable Uri data
, @Nullable String type) {
mData = data;
mType = type;
return this;
}
//此時mineType被賦null
public @NonNull Intent setData(@Nullable Uri data) {
xmData = data;
mType = null;
return this;
}
//此時uri被賦null
public @NonNull Intent setType(@Nullable String type) {
mData = null;
mType = type;
return this;
}
嗯漆魔,不知道為什么這排版看起來有點丑XD∪匆簦可以看到改抡,如果你指定了uri沒有指定mimeType,你可以使用setData()系瓢,如果你指定了minmeType而沒有指定uri阿纤,你可以使用setType(),如果你兩者都指定了夷陋,則需要使用setDataAndType()欠拾。
總結(jié)
比較枯燥的一章
參考資料
Android任務(wù)和返回棧完全解析胰锌,細數(shù)那些你所不知道的細節(jié)
Android官網(wǎng)
Android開發(fā)藝術(shù)探索
隨筆
這一章寫了有一個禮拜。藐窄。资昧。其實上是寫了兩個下午,中間差了一個禮拜荆忍,hhh格带,可能有點問題畢竟隔的有點久,希望如果有人看到了這篇文章還是能自己親自去動手試試刹枉。good luck叽唱,boy~