Android-Activity 啓動模式解析

1.Standard 標準模式

  • Activity的默認啓動模式,在不顯示指定啓動模式的情況下,Activity都以這種方式進行啓動。
  • 在Standard模式下,每啓動一個新的Activity,就會在返回棧中進行入棧,並處於棧頂的位置。
  • 對於使用Standard模式啓動的Activity,系統不檢查該Activity之前是否已經在返回棧中存在,每次啓動都會創建一個該Activity的新的實例對象。

代碼測試:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());

        setContentView(R.layout.activity_main);

        mStandardModeButton = (Button) this.findViewById(R.id.btn_standard_mode);
        mStandardModeButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MainActivity.class);
                startActivity(intent);
            }
        });
    }

我們在這裏所做的事情是,添加一個按鈕,點擊該按鈕在MainActivity的基礎上,再次跳轉啓動MainActivity。

然後運行程序,假設我們連續點擊點擊該按鈕。查看LogCat,發現得到如下輸入信息:
tsr

這裏可以發現,每次跳轉時,就會有一個全新的MainActivity的實例對象被壓入返回棧的棧頂,無論之前是否已經有MainActivity活動的實例存在於棧中。
所以,自然而然的,當我們想要退出整個程序時,就需要連續點擊三次back鍵,以達到目的。

2.SingleTop 棧頂唯一模式

  • SingleTop,顧名思義,最直接的或許就可以理解爲,單棧頂模式。
  • 所以,其與Standard啓動模式最大的不同之處正是在於:當一個Activity已經位於棧頂時,想要再啓動一個該Activity,那麼系統會直接複用位於棧頂的該Activity,而不會再去創建新的實例對象。
  • 與此同時,需要注意的就是,SingleTop模式的上述特性,僅僅只針對於正處於棧頂的活動對象。如果一個活動並非位於棧頂位置,那麼當再次啓動該Activity時,仍然會創建新的對象。

所以,如果我們在AndroidManifest.xml將MainActivity的啓動模式改爲SingleTop之後,再次運行我們在Standard模式裏用到的代碼,則會發現:
除了當MainActivity第一次被啓動時,會創建一次實例對象,之後無論再點擊多少次跳轉按鈕,都不會再創建新的實例對象。所以,只點擊一次back鍵,就可以退出應用。

那麼,下面我們修改代碼:

MainActivity.java

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());

        setContentView(R.layout.activity_main);

        mSingleTopModeButton =  (Button) this.findViewById(R.id.btn_singletop_mode);
        mSingleTopModeButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);

            }
        });
    }

SecondActivity.java

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());

        setContentView(R.layout.activity_second);

        mTurnButton = (Button) this.findViewById(R.id.btn_turn);
        mTurnButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SecondActivity.this,MainActivity.class);
                startActivity(intent);
            }
        });
    }

這次,我們創建了兩個Activity。我們測試的思路如下:
1.當程序初次運行時,MainAcitivty會被初次創建對象。
2.當從MainActivity跳轉到SecondActivity,SecondActivity會被創建對象。
3.當在SecondActivity點擊按鈕,再次跳轉到MainActivity。這時,雖然返回棧裏已經有MainActivity的對象存在了,但是因爲此時位於棧頂的是SecondActivity,所以仍然會再次創建一個全新的MainActivity對象壓入棧內。

而當我們運行程序,得到的日誌打印情況也印證了這一點:
這裏寫圖片描述

3.SingleTask 棧唯一模式

上面我們說到,通過SingleTop模式,我們可以解決重複創建棧頂活動的問題。但是,當我們再次啓動的活動並不位於棧頂的時候,則就還是不能避免重複創建多個活動實例的問題。這個時候,就可以通過SingleTask來解決。

SingleTask的不同之處在於:

  • 當再次啓動一個Activity時,首先會檢查,在當前返回棧中,是否已經存在該Activity的實例對象。
  • 如果已經存在,則會直接複用該對象,並且將棧中位於該Activity之上的活動全部清除出棧。
  • 如果當前還不存在,則會創建一個全新的Activity對象,並壓入棧頂。

弄明白了SingleTask啓動模式的特點之後,我們在測試SingleTop模式的代碼的基礎上,加以修改。

首先,在MainActivity.java中加上:

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }

接着,在SecondActivity.java中加上:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

最後,在AndroidManifest.xml將MainActivity的啓動模式改爲SingleTask。

再次運行代碼,發現在LogCat得到的日誌信息爲:
這裏寫圖片描述

通過日誌,我們也驗證了SingleTask啓動模式的特徵:
1.當我們初次啓動程序時,返回棧還是空的,所以系統會創建一個全新的MainActivity對象,壓入棧頂。
2.當我們點擊按鈕,跳轉到SecondActivity。此時,返回棧中只有一個MainActivity對象,所以這次會創建一個SecondActivity的全新對象,並壓入棧頂。
3.當我們在SecondActivity點擊跳轉按鈕,想要返回到MainActivity。此時,系統檢查到返回棧中已經存在MainActivity的對象了,但它並不位於棧頂。於是則會直接使用該對象,放到棧頂的位置,於是我們看到了MainActivity的生命週期方法onRestart得到了執行;
4.於此同時,除了將MainAcitivity壓入棧頂之外,我們前面說到,還會降位於其之上的活動對象清除出棧,所以SecondActivity對象被清除,於是我們還看到了SecondActivity的onDestroy方法得到了執行。
4.最後,當我們點擊back鍵,會發現直接就退出應用了。

實際應用場景模擬:
藉助一些情景模擬,可以幫助我們進行更好的理解。
假設現在有這樣一種情況,例如現在我們的應用中有一個聊天界面。在聊天界面中,我們可以點擊當前某個按鈕,查看聊天對象的個人資料。
當進入到資料查看界面後,例如,又有用戶相冊,我們點擊進入到相冊界面。以此類推……經過一系列操作後,我們已經跳轉了多個Activity了。
此時,我們想要返回到聊天界面,回覆聊天消息。這個時候,應當提供給用戶應當怎麼的操作方式?

首先,當然可以通過點擊系統回退鍵,一步一步回退到聊天界面,但這樣的操作未免過於枯燥和麻煩和操蛋。
所以,我們可以提供一個按鈕“返回聊天”,直接從當前界面跳轉回聊天界面。
也就是比如點擊該按鈕通過”startActivity(AlbumActivity.this,ChatActivity.class);”的方式來返回聊天界面。
那麼這裏,SingleTask就派上了用場。

試想一下,假如我們沒有對Activity的啓動模式做任何顯示聲明,其啓動模式爲默認的Standard模式。
那麼,雖然當用戶點擊按鈕跳轉到聊天界面後,帶來的不妥顯而易見,我們來加以分析一下:

  1. 首先,這樣做系統會爲我們創建一個全新的”ChatActivity”的實例。那麼也就是說,我們將從新進行一次Activity的生命初始化。並且之前的一些臨時緩存數據,也將丟失。
  2. 雖然我們看似達到了目的,直接“回到了”聊天界面。但是,這個時候,從用戶的思考角度出發,會理所應當的認爲:在回到聊天界面之後,他再次點擊回退鍵,應用則應該退回到一個他最初進入到聊天界面之前的界面。例如用戶最初是在聯繫人列表界面,點擊某個好友的頭像進入到了聊天界面。那麼,正確的邏輯則會爲,此次點擊back鍵盤之後,應該回到的,則是這個聯繫人列表界面。
  3. 但實際上,Standard啓動模式下,應用卻會回到之前的“相冊界面”。而想要回到最初的聯繫人列表界面,則又回到了我們上面說的老問題,需要經歷一些列的back按鍵操作。那麼可能最後導致的就是,用戶暈了!

而SingleTask則正好可以完美解決上述的問題,假設我們將聊天界面的啓動模式設置爲了SingleTask。那麼:
1.當我們從相冊界面跳轉回聊天界面的時候,系統首先會檢測到返回棧中已經有一個聊天界面的對象。則會直接找到這個對象,將其放到棧頂。
2.而之前位於聊天界面之上的所有活動,從用戶操作的角度來說,既是用戶在通過聊天界面點擊按鈕進入到個人資料查看界面之後所跳轉的一系列Activity,都會被清除出棧。
3.這個時候,當用戶在跳轉回的聊天界面再次點擊back鍵的時候,整個返回棧中的情況就是,聊天界面的activity位於棧頂,其之下緊跟着的就是其最初進入聊天界面之前的activity,所以自然的,也就會清楚的回到用戶希望回到的界面。

4.SingleInstance 實例唯一模式

  • 當Activity被設置爲該啓動模式後,會有一條新建的返回棧專門管理裏該Activity。所以,不管是哪個應用想要調用或訪問該Activity,都會在該條棧裏進行操作。
  • 那麼,顧名思義,我們也能夠發現:簡單的來說,如果說SingleTask能夠保證,在同一條返回棧裏,一個Activity永遠只會有唯一一個對應的實例的話。SingleInstance則能夠保證,在當前系統下,該Activity永遠只會有唯一的一個實例

SingleInstance模式,被認爲是最爲複雜的一種啓動模式。
怎麼說呢,之前我們說到的三種啓動模式,其Activity的管理都是在同一條返回棧當中進行的。
簡單的理解來說,當一個應用啓動,系統就會分配一條專門的返回棧來管理該應用的所有activity。
那麼這時,就出現了一種之前的3種啓動模式解決不了的情況:
假設,我們希望我們的應用能和另一個應用共享某個Activity的實例。

這樣說,可能很難以理解,那麼舉例來說:
1.現在我們的手機上有“某某地圖”以及我們自己的應用。
2.我們希望在我們的應用裏共享“某某地圖”當中的地圖功能界面。

那麼,假定的操作就應該爲,例如:
1.首先,假設我們在“某某地圖”裏已經做了一定操作,地圖界面被我們定位到了成都市。
2.我們返回了HOME界面,打開了自己的應用,在自己的應用裏打開了”某某地圖”的地圖界面。
3.那麼,所謂共享該Activity實例,我們要達到的效果就是,當我們打開地圖界面,此時地圖上顯示的位置就應該是我們之前定位到的成都市。而不是地圖的初始化顯示方位。

那麼,顯然,通過此前的3種啓動模式,我們是實現不了的。因爲:
我們最初在“某某地圖”中進行定位時,activity是位於該應用的返回棧裏的。
當我們在自己的應用再次調用地圖界面,系統的操作是,在我們自己的應用的返回棧裏新建一個地圖界面Activity的實例對象。
所以實際上兩個“地圖界面”是位於兩個不同應用的各自的返回棧裏的,兩個毫無關聯的Activity實例。

我們可以通過代碼,來驗證一下我們的猜想。

我們現在編寫兩個Activity,一個MainActivity用以代表我們自己的應用,一個SecondActivity存在於另一個應用。

SecondActivity.java:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);

        Log.d("SecondActivity", "Task ID ==>> "+ getTaskId() + ",class object ==>> "+this.toString());
        this.setContentView(R.layout.activity_second);
    }

MainActivity.java:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task ID ==>> "+getTaskId() + ",class object ==>> "+this.toString());

        setContentView(R.layout.activity_main);

        mButton = (Button) this.findViewById(R.id.btn_standard_mode);
        mButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent mIntent = new Intent();
                ComponentName comp = new ComponentName("com.tsr.test","com.tsr.test.SecondActivity");
                mIntent.setComponent(comp);
                mIntent.setAction("android.intent.action.MAIN");
                startActivity(mIntent);
            }
        });

    }

運行程序進行測試,得到如下輸出結果,分別是:
這裏寫圖片描述
這裏寫圖片描述

從輸出結果,我們可以看到:
MainActivity代表的應用,其返回棧ID爲4;SecondActivity代表的另一個應用,返回棧ID爲3。
當初次打開SecondActivity應用的時候,在Task“3”裏新建了一個實例對象@5271b580。
而當我們在MainActivity裏再次調用SecondActivity時,在我們自己的應用Task“4”裏再次新建了對象@526fbc4c。

從結果,我們可以看到,在標準的Standard模式下,無法實現在不同應用間共享某個Activity的需求。

那麼,我們將SecondActivity的啓動模式設爲SingleInstance,再對代碼進行測試,得到輸出結果:
這裏寫圖片描述
這裏寫圖片描述

這次,我們看到:
MainActivity位於Task“8”當中,當我們在自己的應用MainActivity當中調用SecondActivity,系統新建了返回棧Task“9”,並在該返回棧中放入一個全新的SecondActivity的實例對象。
此時,當我們再打開SecondActivity本身所在的應用,調用SecondActivity,系統則會複用Task“9”當中的對象,而不會去做其他操作了。
從而,也就是實現了我們的目的,在兩個應用間共享某個Activity。

另外,對於SingleInstance啓動模式,還有另一個特點值得注意。
以《第一行Android代碼》中的用例來說,假設現在有如下情況:

現在,我們的應用裏有三個Activity,分別爲:FirstActivity,SecondActivity,ThirdActivity。
它們之間的跳轉關係是:FirstActivity跳轉到SecondActivity,SecondActivity跳轉到ThirdActivity。
其中,SecondActivity的啓動模式被設定爲了SingleInstance。

那麼,當完成所有跳轉後,點擊回退鍵,發生的事情需要值得注意一下:
當在ThirdActivity點擊back後,回到的不是SecondActivity,而是FirstActivity。
而在FirstActivity再次點擊back鍵後,纔會回到SecondActivity,再次點擊纔會退出應用。

相信在上面的瞭解過後,你已經能夠分析出原因,這是因爲:
因爲SecondActivity被設置爲SingleInstance啓動模式,所以當跳轉時,系統會新開一條返回棧來管理它。
假設之前的FirstActivity存在於ID爲1的棧裏,那麼SecondActivity存在於ID爲2的棧裏。
當由SecondActivity跳轉至ThirdActivity時,ThirdActivity則仍然加入到ID位1的棧裏。

所以最後的情況是:
ID爲1的棧裏,由上到下,分別是ThirdActivity,FirstActivity。而:
ID爲2的棧裏,則單獨存放着SecondActivity。
所以當ThirdActivity出棧,緊接着進入棧頂的也就是FirstActivity。
而FirstActivity出棧後,ID爲1的棧被清空,才輪到ID爲2的棧裏的活動與用戶交互。

5.歸納總結

我們已經分別驗證和理解了Android當中,Activity4種不同啓動模式的特點。
最後我們加以總結,其實4種啓動模式的命名很吊,我們在加以理解之後,實際上根據其名字,就可以很直觀的記住它們的特點。

  • Standard 模式,默認的啓動模式。特點是:不管任何情況,只要調用啓動一次Activity,系統就會創建一個全新的實例對象,放入棧頂。
  • SingleTop模式,我們可以理解爲:棧頂唯一模式。所以特點就是:對於位於棧頂的活動,系統保證其對象唯一性。也就是說,只要調用啓動的目標Activity正位於棧頂,那麼系統就絕對不會再重複創建其實例對象。
  • SingleTask模式,我們可以理解爲:返回棧唯一模式。所以特點就是:在同一條返回棧裏,一個Activity無論重複調用、啓動多少次,它都只會有唯一的一個實例存在與該返回棧中。如果棧裏當前不存在,則新建。若存在,則將其放到棧頂,並將其之上的活動實例都清除出棧。
  • SingleInstance模式,我們可以理解爲:實例唯一模式。自然其特點則爲:在當前運行系統中,該Activity只會有唯一的一個實例。系統會在該Activity初次啓動時,新分配一條返回棧專門管理它。之後無論多少個不同的應用想要調用、啓動該Activity,都是共用該返回棧裏這個活動的唯一實例。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章