Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Activity啟動模式與任務棧(Task)全面深入記錄(上)

Activity啟動模式與任務棧(Task)全面深入記錄(上)

編輯:關於Android編程

任務棧簡單入門

??最近又把兩本進階書看了一遍,但總感覺好記性不如爛筆頭,所以還是決定通過博客記錄一下,我們將分兩篇來全面深入地記錄Activity 啟動模式與任務棧的內容。

android任務棧簡單了解

1. android任務棧又稱為Task,它是一個棧結構,具有後進先出的特性,用於存放我們的Activity組件。
2. 我們每次打開一個新的Activity或者退出當前Activity都會在一個稱為任務棧的結構中添加或者減少一個Activity組件,因此一個任務棧包含了一個activity的集合, android系統可以通過Task有序地管理每個activity,並決定哪個Activity與用戶進行交互:只有在任務棧棧頂的activity才可以跟用戶進行交互。
3. 在我們退出應用程序時,必須把所有的任務棧中所有的activity清除出棧時,任務棧才會被銷毀。當然任務棧也可以移動到後台, 並且保留了每一個activity的狀態. 可以有序的給用戶列出它們的任務, 同時也不會丟失Activity的狀態信息。
4. 需要注意的是,一個App中可能不止一個任務棧,某些特殊情況下,單獨一個Actvity可以獨享一個任務棧。還有一點就是一個Task中的Actvity可以來自不同的App,同一個App的Activity也可能不在一個Task中。

??嗯,目前android任務棧的概念我們就大概了解到這。下面我們主要還是來聊聊android的4種啟動模式。

Activity的啟動模式

為什麼需要Activity的啟動模式?

??我們在開發項目的過程中,一般都需要在本應用中多個Activity組件之間的跳轉,也可能需要在本應用中打開其它應用的可復用的Activity。如我們可能需要跳轉到原來某個Activity實例,此時我們更希望這個Activity可以被重用而不是創建一個新的 Activity,但根據Android系統的默認行為,確實每次都會為我們創建一個新的Activity並添加到Task中,這樣android系統是不是很傻?還有一點就是在我們每開啟一次頁面加入到任務棧Task中後,一個Activity的數據和信息狀態都將會被保留,這樣會造成數據冗余, 重復數據太多, 最終還可能導致內存溢出的問題(OOM)。為了解決這些問題,android系統提供了一套Activity的啟動模式來修改系統Activity的默認啟動行為。目前啟動模式有四種,分別是standard,singleTop,singTask和singleInstance,接下來我們將分別介紹這四種模式。

Activity的4種啟動模式

Standard 模式

??又稱為標准模式,也是系統的默認模式(可以不指定),在這樣模式下,每啟動一個Activity都會重新創建一個Activity的新實例,並且將其加入任務棧中,而且完全不會去考慮這個實例是否已存在。我們通過圖解來更清晰地了解Standard模式:

\

??通過上圖,我們可以發現,這個過程中,在standard模式下啟動了三次MainActivity後,都生成了不同的新實例,並添加到同一個任務棧中。這個時候Activity的onCreate、onStart、onResume方法都會被調用。

singleTop 模式

??又稱棧頂復用模式,顧名思義,在這種模式下,如果有新的Activity已經存在任務棧的棧頂,那麼此Activity就不會被重新創建新實例,而是復用已存在任務棧棧頂的Activity。這裡重點是位於棧頂,才會被復用,如果新的Activity的實例已存在但沒有位於棧頂,那麼新的Activity仍然會被重建。需要注意的是,Activity的onNewIntent方法會被調用,方法原型如下:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
}

??通過此方法的參數,我們可以獲取當前請求的相關信息,此時Activity的onCreate、onStart方法不會被調用,因為Activity並沒有被重建。同理,我們通過圖解來協助我們更清晰的理解singleTop模式:

\

??從上圖我們可以看出,當需要新創建的MainActivity位於棧頂時,MainActivity並沒有重新創建。下面我們再來看看新創建的MainActivity沒有位於棧頂的情況。
\
??嗯,這就是singTop模式。這種模式通常比較適用於接收到消息後顯示的界面,如qq接收到消息後彈出Activity界面,如果一次來10條消息,總不能一次彈10個Activity,是吧?再比如新聞客戶端收到了100個推送,你每次點一下推送他都會進入某個activiy界面(顯示新聞只用一個activity,只是內容不同而已),這時也比較適合使用singleTop模式。

singleTask 模式

?? 又稱為棧內復用模式。這是一種單例模式,與singTop點類似,只不過singTop是檢測棧頂元素是否有需要啟動的Activity,而singTask則是檢測整個棧中是否存在當前需要啟動的Activity,如果存在就直接將該Activity置於棧頂,並將該Activity以上的Activity都從任務棧中移出銷毀,同時也會回調onNewIntent方法。情況如下圖:

\

?? 從圖中可以看出,當我們再次啟動MainActivity時,由於MainActivity位於棧中,所以系統直接將其置於棧頂,並移除其上方的所有Activity。當然如果所需要的MainActivity不存在棧中,則會創建新的Activity並添加到棧中。singleTask 模式比較適合應用的主界面activity(頻繁使用的主架構),可以用於主架構的activity,(如新聞,側滑,應用主界面等)裡面有好多fragment,一般不會被銷毀,它可以跳轉其它的activity 界面再回主架構界面,此時其他Activity就銷毀了。當然singTask還有一些比較特殊的場景這個我們後面會一一通過情景代碼分析。

singleInstance 模式

??在singleInstance模式下,該Activity在整個android系統內存中有且只有一個實例,而且該實例單獨尊享一個Task。換句話說,A應用需要啟動的MainActivity 是singleInstance模式,當A啟動後,系統會為它創建一個新的任務棧,然後A單獨在這個新的任務棧中,如果此時B應用也要激活MainActivity,由於棧內復用的特性,則不會重新創建,而是兩個應用共享一個Activity的實例。如下圖所示:
\
??從圖中我們可以看到最終AB應用都共享一個singleInstance模式的MainActivity,也沒有去重新創建。到此Activity的四種啟動模式我們都介紹完了,下面我們接著來聊聊怎麼使用啟動模式。

Activity啟動模式的使用方式

??前面我們說了那麼多,那麼我們該如何給Activity指定啟動模式呢?事實上共有如下兩種方式:
1.通過AndroidMenifest.xml文件為Activity指定啟動模式,代碼如下:


2.通過在Intent中設置標志位(addFlags方法)來為Activity指定啟動模式,示例代碼如下:

Intent intent = new Intent();
intent.setClass(ActivityB.this,ActivityA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

那麼標志位是是什麼呢?接下來我們就來了解一些常用的標志位

Intent Flag 啟動模式

??這裡我們主要介紹一下一些常用的Activity的Flag,因為Activity的Flag比較多,我們知道一些常用的就夠了,遇到比較特殊的還是查查官網文檔吧。

Intent.FLAG_ACTIVITY_NEW_TASK

該標志位表示使用一個新的Task來啟動一個Activity,相當於在清單文件中給Activity指定“singleTask”啟動模式。通常我們在Service啟動Activity時,由於Service中並沒有Activity任務棧,所以必須使用該Flag來創建一個新的Task。我們來重現一下這個錯誤,創建一個Service服務,並在onCreate方法中啟動Activity,代碼如下:

public class ServiceT extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent i =new Intent(getApplicationContext(),ActivityD.class);
        startActivity(i);
    }
}

啟動應用並啟動Service服務,後報錯如下:
\
從異常信息我們可以看出,提示我們添加Intent.FLAG_ACTIVITY_NEW_TASK標志位,所以我們代碼必須改成如下:

public class ServiceT extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public void onCreate() {
        super.onCreate();
        Intent i =new Intent(getApplicationContext(),ActivityD.class);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(i);
    }
}

Intent.FLAG_ACTIVITY_SINGLE_TOP
??該標志位表示使用singleTop模式來啟動一個Activity,與在清單文件指定android:launchMode="singleTop"效果相同。

Intent.FLAG_ACTIVITY_CLEAR_TOP
???該標志位表示使用singleTask模式來啟動一個Activity,與在清單文件指定android:launchMode="singleTask"效果相同。

Intent.FLAG_ACTIVITY_NO_HISTORY
??使用該模式來啟動Activity,當該Activity啟動其他Activity後,該Activity就被銷毀了,不會保留在任務棧中。如A-B,B中以這種模式啟動C,C再啟動D,則任務棧只有ABD。

Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
??使用該標識位啟動的Activity不添加到最近應用列表,也即我們從最近應用裡面查看不到我們啟動的這個activity。與屬性android:excludeFromRecents="true"效果相同。

啟動模式中singleTask的特殊情景

??前面我們在分析singleTask模式時,提到過singleTask模式有些比較特殊的場景,現在我們就來了解了解它們。
特殊情景一:現在我們假設有如下兩個Task棧,分別為前台任務棧和後台任務棧
\
??從圖中我們看出前台任務棧分別為AB兩個Activity,後台任務棧分別為CD兩個任務棧,而且其啟動模式均為singleTask,此時我們先啟動CD,然後再啟動AB,再有B啟動D,此時後台任務棧便會被切換到前台,而且這個時候整個後退列表就變成了ABCD,請注意我們這裡強調的是後退列表,而非棧合並。因此當用戶點擊back鍵時,列表中的Activity會依次按DCBA順序出棧,如下圖所示:
\
??這裡我們通過兩個應用ActivityTask和ActivityTask2來測試重現這個現象。因為兩個是不同的應用所以啟動時所在的棧也是不同。我們先啟動ActivityTask2的應用,其ActivityC和ActivityD都是singleTask模式,然後再啟動應用ActivityTask,此時ActivityC和ActivityD所在任務棧會被退居後台,而打開的ActivityA和ActivityB會在前台,而且都是默認模式。我們通過 adb shell dumpsys activity activities 命令查看此時棧的情況:
\
??我們可以看到由兩個棧,分別為id=222且棧名為“com.cmcm.activitytask”的任務棧其包含ActivityA和ActivityB(下面簡稱AB,棧名一般默認和包名相同),另外一個任務棧,id=221,棧名為“com.cmcm.activitytask2”,其包含ActivityC和ActivityD(下面檢測CD)。現在我們通過ActivityB去啟動ActivityD,然後按back鍵回退。B調用D代碼如下:

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by zejian
 * Time 16/7/23.
 * Description:
 */
public class ActivityB extends Activity {
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        btn= (Button) findViewById(R.id.main);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_MAIN);
                intent.addCategory(Intent.CATEGORY_LAUNCHER);
                ComponentName cn = new ComponentName("com.cmcm.activitytask2", "com.cmcm.activitytask2.ActivityD");
                intent.setComponent(cn);
                startActivity(intent);

            }
        });
    }
}

運行結果如下:
\
??我們可以看到包含CD的任務棧被提前的,雖然CD隔開了,但是我們從id和棧名可以發現他們是同一個棧,而AB所在的棧則在CD所在棧的後面,所以此時我們按back回退時,退出順序是這樣的D->C->B->A,動態圖如下:
\
??到這裡我們就應該更加清晰的了解情景一的現象了。了解這點有什麼用呢,這可以使用我們更好地去管理我們的任務棧,而不會導致棧混亂是進入一些用戶本來就不需要界面,影響用戶體驗。

特殊情景二:
??如果上面B不是請求啟動D而是請求啟動C,那麼又會是什麼情況呢?其實這個時候任務棧退出列表變成C->B->A,其實原因很簡單,singleTask模式的ActivityC切換到棧頂時會導致在他之上的棧內的Activity出棧。同樣我們還是使用上面的代碼,把B啟動D改為B啟動C,那麼此時B未啟動C時任務棧的情況如下:
\
??我們仍然可以看到兩個任務棧,分別為id=242,棧名“com.cmcm.activitytask”的Task,包含ActivityA和ActivityB;id=241,棧名“com.cmcm.activitytask2”的Task,包含ActivityC和ActivityD。此時我們通過B啟動C後棧的情況變成如下情況
\
因此,棧的退出列表就變成了C->B->A了,如下圖所示:
\
動態圖如下:

\
??到此我們對SingleTask模式又有了更深入的理解,但是我們發現上面的例子使用的是兩個應用,所以才會有不同的任務棧,那麼我們能不能在一個應用中存在多個不同的任務棧呢(暫時不考慮singleInstance 模式)?答案當然是肯定的啦,這就需要通過taskAffinity屬性來設置不同的任務棧名稱,不過這點將放在下篇來記錄,本篇就先到這裡告一段落哈。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved