Android 筆記之 Activity的四種啓動模式和Flags標記位

standard:標準模式

    standard是活動默認的啓動模式,在不進行顯式指定的情況下,所有活動都會自動使用這種啓動模式。每當啓動一個新的活動,它就會在任務棧中入棧,並處於棧頂的位置。對於使用standard模式的活動,系統不會在乎這個活動是否已經在返回棧中存在,每次啓動都會創建該活動的一個新的實例。
    在standard模式(即默認情況)下,誰啓動了這個Activity,那麼這個Activity 就會運行在啓動它的那個Activity 所在的棧中。比如Activity A 啓動了Activity B(B 是標準模式),則 B 就會進入到 A 所在的棧中。當我們用 ApplicationContext 去啓動 standard 模式的 Activity 時,會拋下面的異常:
 android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the  FLAG_ACTIVITY_NEW_TASK flag.
    Tips:當我們在Adapter或者其他地方(如Service、BroadCastReceiver、Widget等)中使用startActivity()時,也會拋該異常
    這是因爲 standard 模式的Activity 默認會進入啓動它的Activity 所屬的任務棧,但是由於非Activity 類型的 Context(如ApplicationContext)並沒有任務棧,所以就有問題了。
    解決辦法就是爲待啓動 Activity 指定 FLAG_ACTIVITY_NEW_TASK 標記位,這樣啓動的時候就會爲它創建一個新的任務棧,這個時候待啓動 Activity 實際上是以 singleTask 模式啓動的。
    我們將會一個Activity,命名爲FirstActivity,來演示一下標準的啓動模式。FirstActivity代碼如下
public class FirstActivity
        extends AppCompatActivity
{

    private static final String TAG = "FirstActivity";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState != null) {
            String test = savedInstanceState.getString("extra_test");
            Log.e(TAG, "[onCreate] restore extra_test:" + test);
        }
        TextView textView = findViewById(R.id.tast_id1);
        textView.setText(this.toString() + "\n" + "current task id: " + this.getTaskId());
        findViewById(R.id.first_btn2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(), SecondActivity.class));
            }
        });
        findViewById(R.id.first_btn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(), FirstActivity.class));
            }
        });
    }


}
    我們FirstActivity界面中的TextView用於顯示當前Activity實例的序列號,Button用於跳轉到下一個FirstActivity界面。
然後我們連續點擊幾次按鈕,將會出現下面的現象:



    我們注意到都是FirstActivity的實例,但序列號不同,並且我們需要連續按後退鍵兩次,才能回到第一個FristActivity。standard模式的原理如下圖所示:

    如圖所示,每次跳轉系統都會在task中生成一個新的FirstActivity實例,並且放於棧結構的頂部,當我們按下後退鍵時,才能看到原來的FirstActivity實例。
    這就是standard啓動模式,不管有沒有已存在的實例,都生成新的實例。


singleTop:棧頂複用模式,解決重複創建棧頂活動的問題。

    適用場景:適合接收通知啓動的內容顯示頁面。例如,某個新聞客戶端的新聞內容頁面,如果收到10個新聞推送,每次都打開一個新聞內容頁面是很麻煩的。從外界可能多次跳轉到一個界面
    當活動的啓動模式指定爲singleTop,在啓動活動時如果發現任務棧的棧頂已經是該活動,則認爲可以直接使用它,不會再創建新的活動實例,同時它的onNewIntent 方法會被回調,通過此方法的參數我們可以取出當前請求的信息。
    注意:這個Activity 的onCreate、onStart 不會被系統調用,因爲它並沒有發生改變。如果新Activity 的實例已存在但不是位於棧頂,那麼新Activity 仍然會重新重建。


    我們看到這個結果跟standard有所不同,2個序列號是相同的,也就是說使用的都是同一個FirstActivity實例;如果按一下後退鍵,程序立即退出,說明當前棧結構中只有一個Activity實例。singleTop模式的原理如下圖所示:


    這是棧內只有一個Activity,如果是多個Activity怎麼辦,如果不是在棧頂會如何?我們接下來再通過一個示例來證實一下大家的疑問。
    我們再新建一個Activity命名爲SecondActivity,如下:

public class SecondActivity
        extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        TextView textView = findViewById(R.id.tast_id2);
        textView.setText(this.toString() + "\n" + "current task id: " + this.getTaskId());
        findViewById(R.id.second_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SecondActivity.this, FirstActivity.class));
            }
        });
        findViewById(R.id.second_btn2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SecondActivity.this, SecondActivity.class));
            }
        });
    }
}

    然後將之前的FirstActivity跳轉代碼改爲:

  startActivity(new Intent(getApplicationContext(), SecondActivity.class));

    FirstActivity會跳轉到SecondActivity,SecondActivity又會跳轉到FirstActivity。演示結果如下:


    我們看到,兩個FirstActivity的序列號是不同的,證明從SecondActivity跳轉到FirstActivity時生成了新的FirstActivity實例。原理圖如下:




    我們看到,兩個FirstActivity的序列號是不同的,證明從SecondActivity跳轉到FirstActivity時生成了新的FirstActivity實例。原理圖如下:


    我們看到,當從SecondActivity跳轉到FirstActivity時,系統發現存在有FirstActivity實例,但不是位於棧頂,於是重新生成一個實例。這就是singleTop啓動模式,如果發現有對應的Activity實例正位於棧頂,則重複利用,不再生成新的實例。

singleTask:棧內複用模式,讓某個活動在整個應用程序的上下文中只存在一個實例。

    適合作爲程序入口點。例如瀏覽器的主界面。不管從多少個應用啓動瀏覽器,只會啓動主界面一次,其餘情況都會走onNewIntent,並且會清空主界面上面的其他頁面。之前打開過的頁面,打開之前的頁面就ok,不再新建
    這是一種單實例模式,在這種模式下,只要Activity 在一個棧中存在,那麼多次啓動此Activity 都不會重新創建實例,和 singleTop 一樣,系統也會回調其 onNewIntent。
    當活動的啓動模式指定爲singleTask,比如Activity A,系統首先會尋找是否存在A 想要的任務棧,如果不存在重新創建一個任務棧,然後創建 A 的實例後把 A 放到棧中。如果存在A 所需的任務棧,此時會查看任務棧中是否存在A 的實例,如果存在,系統就會把 A 調到棧頂並調用它的 onNewIntent 方法,將A 之上的其它 Activity 移出任務棧。如果 A 的實例不存在,就創建 A 的實例並把A 壓入棧中。
    我們修改FirstActivity的屬性android:launchMode="singleTask"。演示的結果如下:



    我們注意到,在上面的過程中,FirstActivity的序列號是不變的,SecondActivity的序列號卻不是唯一的,說明從SecondActivity跳轉到FirstActivity時,沒有生成新的實例,但是從FirstActivity跳轉到SecondActivity時生成了新的實例。singleTask模式的原理圖如下圖所示:


    在圖中的右半部分是SecondActivity跳轉到FirstActivity後的棧結構變化的結果,我們注意到,SecondActivity消失了,沒錯,在這個跳轉過程中系統發現有存在的FirstActivity實例,於是不再生成新的實例,而是將FirstActivity之上的Activity實例統統出棧,將FirstActivity變爲棧頂對象,顯示到幕前。也許朋友們有疑問,如果將SecondActivity也設置爲singleTask模式,那麼SecondActivity實例是不是可以唯一呢?在我們這個示例中是不可能的,因爲每次從SecondActivity跳轉到FirstActivity時,SecondActivity實例都被迫出棧,下次等FirstActivity跳轉到SecondActivity時,找不到存在的SecondActivity實例,於是必須生成新的實例。但是如果我們有ThirdActivity,讓SecondActivity和ThirdActivity互相跳轉,那麼SecondActivity實例就可以保證唯一。

    這就是singleTask模式,如果發現有對應的Activity實例,則使此Activity實例之上的其他Activity實例統統出棧,使此Activity實例成爲棧頂對象,顯示到幕前。

singleInstance:單實例模式,共享活動實例的問題。

    適合需要與程序分離開的頁面。例如鬧鈴提醒,將鬧鈴提醒與鬧鈴設置分離。singleInstance不要用於中間頁面,如果用於中間頁面,跳轉會有問題,比如:A -> B (singleInstance) -> C,完全退出後,在此啓動,首先打開的是B。某個應用中用到了google地圖,當退出該應用的時候,進入google地圖,還是剛纔的界面
      指定爲singleInstance模式的活動會啓用一個新的返回棧來管理這個活動(其實如果singleTask模式指定了不同的taskAffinity,也會啓動一個新的返回棧)。這是一種加強的 singleTask 模式,它除了具有singleTask 模式的所有特性外,還加強了一點,就是具有此種模式的Activity 只能單獨地位於一個任務棧中,比如Activity A 是singleInstance 模式,當A 啓動後,系統會爲它創建一個新的任務棧,然後A 肚子在這個新的任務棧中,由於棧內複用的特點,後續的請求都不會創建新的 Activity,除非這個獨特的任務棧被系統銷燬了。

    singleInstance 模式會啓用一個新的棧結構,將Acitvity放置於這個新的棧結構中,並保證不再有其他Activity實例進入。修改FirstActivity的launchMode="standard",SecondActivity的launchMode="singleInstance",AndroidManifest.xml如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.example.yhadmin.activitydemo2"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <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=".FirstActivity"
                  android:launchMode="standard">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity"
                  android:launchMode="singleInstance">
        </activity>
    </application>

</manifest>

  我們在每個Activity中顯示當前棧結構的id,每個Activity添加如下代碼:

        TextView textView = findViewById(R.id.tast_id1);
        textView.setText(this.toString() + "\n" + "current task id: " + this.getTaskId());

    然後我們再演示一下這個流程:



    我們發現這兩個Activity實例分別被放置在不同的棧結構中,關於singleInstance的原理圖如下:


    從FirstActivity跳轉到SecondActivity時,重新啓用了一個新的棧結構,來放置SecondActivity實例,然後按下後退鍵,再次回到原始棧結構;圖中下半部分顯示的在SecondActivity中再次跳轉到FirstActivity,這個時候系統會在原始棧結構中生成一個FirstActivity實例,然後回退兩次,注意,並沒有退出,而是回到了SecondActivity,爲什麼呢?是因爲從SecondActivity跳轉到FirstActivity的時候,我們的起點變成了SecondActivity實例所在的棧結構,這樣一來,我們需要“迴歸”到這個棧結構。

    如果我們修改FirstActivity的launchMode值爲singleTop、singleTask、singleInstance中的任意一個,流程將會如圖所示:如果我們修改FirstActivity的launchMode值爲singleTop、singleTask、singleInstance中的任意一個,流程將會如圖所示:     

Activity 的 Flags

FLAG_ACTIVITY_NEW_TASK

    爲Activity 指定 singleTask 啓動模式,效果和在 XML 中指定 singleTask 模式相同

FLAG_ACTIVITY_SINGLE_TOP

    爲Activity 指定 singleTop 啓動模式,效果和在 XML 中指定 singleTop 模式相同

FLAG_ACTIVITY_CLEAR_TOP

    具有此標記位的Activity,當它啓動時,在同一個任務棧中所有位於它上面的Activity 都要出棧。這個標記位一般會和 singleTask 啓動模式一起出現,在此種情況下,被啓動 Activity 實錄如果已經存在,系統則會調用它的 onNewIntent。如果被啓動的Activity 採用 standard 模式啓動,那麼它連同它之上的Activity 都要出棧,系統會創建新的Activity 實例並放入棧頂。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

    具有此標記位的Activity 不會出現在歷史Activity 的列表中,當某些情況下我們不希望用戶通過歷史列表回到我們的Activity 的時候這個標記比較有用。它等同於在XML 中指定Activity 的屬性 android:excludeFromRecents="true"

參考資料:https://blog.csdn.net/liuhe688/article/details/6754323

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章