Android Activity任務(wù)棧凝赛、啟動模式以及IntentFilter匹配規(guī)則

任務(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)給的示例如下:


diagram_backstack_singletask_multiactivity.png

有點抽象选泻?我們來舉個例子,比如我此時正在看簡書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.comdeveloper.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~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市微宝,隨后出現(xiàn)的幾起案子棺亭,更是在濱河造成了極大的恐慌,老刑警劉巖蟋软,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镶摘,死亡現(xiàn)場離奇詭異,居然都是意外死亡钟鸵,警方通過查閱死者的電腦和手機钉稍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棺耍,“玉大人贡未,你說我怎么就攤上這事∶膳郏” “怎么了俊卤?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長害幅。 經(jīng)常有香客問我消恍,道長,這世上最難降的妖魔是什么以现? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任狠怨,我火速辦了婚禮,結(jié)果婚禮上邑遏,老公的妹妹穿的比我還像新娘佣赖。我一直安慰自己,他們只是感情好记盒,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布憎蛤。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俩檬。 梳的紋絲不亂的頭發(fā)上萎胰,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音棚辽,去河邊找鬼技竟。 笑死,一個胖子當著我的面吹牛晚胡,可吹牛的內(nèi)容都是我干的灵奖。 我是一名探鬼主播嚼沿,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼估盘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骡尽?” 一聲冷哼從身側(cè)響起遣妥,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎攀细,沒想到半個月后箫踩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡谭贪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年境钟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俭识。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡慨削,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出套媚,到底是詐尸還是另有隱情缚态,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布堤瘤,位于F島的核電站玫芦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏本辐。R本人自食惡果不足惜桥帆,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望慎皱。 院中可真熱鬧老虫,春花似錦、人聲如沸宝冕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽地梨。三九已至菊卷,卻和暖如春缔恳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洁闰。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工歉甚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扑眉。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓纸泄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腰素。 傳聞我的和親對象是個殘疾皇子聘裁,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容