Android開發(fā) - Activity的啟動(dòng)模式和最佳實(shí)踐

Activity的啟動(dòng)模式

在實(shí)際項(xiàng)目中需要根據(jù)特定的需求為每一個(gè)Activity指定恰當(dāng)?shù)膯?dòng)模式。

啟動(dòng)模式一共有4種,分別是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通過給<activity>標(biāo)簽指定android:launchMode屬性選擇啟動(dòng)模式

  • standard

standard是Activity默認(rèn)的啟動(dòng)模式,在不進(jìn)行顯示指定的情況下,所有Activity都使用這種啟動(dòng)模式。由于Android是使用返回棧來管理Activity的,在standard(即默認(rèn)情況)下,每當(dāng)啟動(dòng)一個(gè)新的Activity,它就會(huì)在返回棧中入棧,并處于棧頂?shù)奈恢谩?duì)于使用standard模式的Activity,系統(tǒng)不會(huì)在乎這個(gè)Activity是否已經(jīng)在返回棧中存在,每次啟動(dòng)都會(huì)創(chuàng)建該Activity的一個(gè)新的實(shí)例

在代碼中觀察一下:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", this.toString());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }

    // 運(yùn)行結(jié)果
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@bf411c9
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@a01da18
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@c439963

代碼看起來有點(diǎn)奇怪,在FirstActivity的基礎(chǔ)上啟動(dòng)FirstActivity。從邏輯上講沒什么意義,但對(duì)于研究standard模式會(huì)很有用,通過在onCreate()添加打印運(yùn)行的結(jié)果來看,每次點(diǎn)擊按鈕都會(huì)創(chuàng)建一個(gè)新的FirstActivity實(shí)例。此時(shí)返回棧也會(huì)存在3個(gè)FirstActivity的實(shí)例,因此需要連續(xù)按3次Back鍵才能退出程序

standard模式的原理示意圖:

standard模式的原理示意圖.png
  • singleTop

可能會(huì)覺得standard模式不太合理,命名已經(jīng)在棧頂了為什么啟動(dòng)的時(shí)候還要?jiǎng)?chuàng)建一個(gè)新的實(shí)例呢,這只是系統(tǒng)默認(rèn)的一種啟動(dòng)模式而已,完全可以根據(jù)自己的需要進(jìn)行修改。當(dāng)Activity的啟動(dòng)模式指定為singleTop,在啟動(dòng)Activity時(shí)如果發(fā)現(xiàn)返回棧的棧頂已經(jīng)是該活動(dòng),則認(rèn)為可以直接使用它,不會(huì)再創(chuàng)建新的Activity實(shí)例

先修改一下AndroidManifrst.xml中FirstActivity的啟動(dòng)模式,如下所示:

<activity android:name=".FirstActivity"
            android:label="This is FirstActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

觀察按鈕點(diǎn)擊后的輸出

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", this.toString());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }

    // 打印結(jié)果
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@77e7e6c

運(yùn)行結(jié)果顯示不管點(diǎn)擊多少次按鈕都不會(huì)再有新的信息出現(xiàn),因?yàn)榇藭r(shí)FirstActivity已經(jīng)處于返回棧棧頂,每當(dāng)想再啟動(dòng)一個(gè)FirstActivity的時(shí)候都會(huì)直接使用棧頂?shù)腁ctivity,因此FirstActivity也只會(huì)有一個(gè)實(shí)例,僅按一次Back鍵即可退出程序。

不過當(dāng)FirstActivity并未處于棧頂位置的時(shí)候,這時(shí)再啟動(dòng)FirstActivity,還是會(huì)創(chuàng)建新的實(shí)例,可以通過以下代碼進(jìn)行演示。

創(chuàng)建SecondActivity,讓FirstActivity點(diǎn)擊按鈕進(jìn)入SecondActivity,在SecondActivity中的按鈕點(diǎn)擊又進(jìn)入FirstActivity

FirstActivity中:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", this.toString());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

在SecondActivity中

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("SecondActivity", this.toString());
        setContentView(R.layout.second_layout);
        Button button2 = (Button)findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intet = new Intent(SecondActivity.this, FirstActivity.class);
                startActivity(intet);
            }
        });
    }

運(yùn)行結(jié)果:

// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@ee8ec35
// D/SecondActivity: com.zntq.xietao.activitytest.SecondActivity@ecd00ce
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@a01da18

從運(yùn)行結(jié)果可以看出創(chuàng)建了兩個(gè)不同的FirstActivity實(shí)例,這是由于在SecondActivity中再次啟動(dòng)FirstActivity時(shí),棧頂Activity是SecondActivity,因此會(huì)創(chuàng)建一個(gè)新的FirstActivity實(shí)例。現(xiàn)在按下Back返回鍵會(huì)返回到SecondActivity,再次按下Back鍵又返回FirstActivity,再按一次Back鍵才會(huì)退出程序。

singleTop模式的原理示意圖:

singleTop模式的原理示意圖.png
  • singleTask

使用singleTop模式可以很好的解決重復(fù)創(chuàng)建棧頂Activity的問題,但是正如上邊看到的,如果該Activity并沒有處于棧頂還是會(huì)創(chuàng)建多個(gè)Activity實(shí)例的,那么有沒有可以讓某個(gè)Activity在整個(gè)應(yīng)用程序的上下文中只存在一個(gè)實(shí)例呢?這就需要借助singleTask模式來實(shí)現(xiàn)。

當(dāng)Activity的啟動(dòng)模式指定為singleTask的時(shí)候,每次啟動(dòng)該Activity時(shí)系統(tǒng)首先會(huì)在棧中檢查是否存在該Activity的實(shí)例,如果發(fā)現(xiàn)已經(jīng)存在則直接使用這個(gè)實(shí)例,并把在這個(gè)Activity上的所有Activity統(tǒng)統(tǒng)出棧,如果沒有發(fā)現(xiàn)就會(huì)創(chuàng)建一個(gè)新的Activity實(shí)例。還是通過下邊的代碼來看一下

修改AndroidManifest.xml中FirstActivity的啟動(dòng)模式:

<activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

然后在FirstActivity中添加onRestart()方法,并打印日志:

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

最后在SecondActivity中添加onDestroy()方法,并打印日志:

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

重新運(yùn)行程序,在FirstActivity界面點(diǎn)擊按鈕進(jìn)入SecondActivity,然后在SecondActivity界面點(diǎn)擊按鈕又會(huì)重新進(jìn)入到FirstActivity

// 打印結(jié)果
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@ee8ec35
    // D/SecondActivity: com.zntq.xietao.activitytest.SecondActivity@ecd00ce
    // D/FirstActivity: onRestart
    // D/SecondActivity: onDestroy

從打印結(jié)果可以看出,在SecondActivity中啟動(dòng)FirstActivity時(shí),會(huì)發(fā)現(xiàn)返回棧中已經(jīng)存在一個(gè)FirstActivity的實(shí)例了,并且是在SecondActivity的下面,于是SecondActivity會(huì)從返回棧中出棧,而FirstActivity重新成為棧頂Activity,因此FirstActivity的onRestart()方法和SecondActivity的onDestroy()方法會(huì)得到執(zhí)行。現(xiàn)在返回棧中支撐下一個(gè)FirstActivity的實(shí)例了,按一下Back鍵就可以退出程序。

singleTask模式的原理示意圖:

singleTask模式的原理示意圖.png
  • singleInstance

singleInstance模式是4種啟動(dòng)模式中最特殊也是最復(fù)雜的一個(gè)。不同于以上3種啟動(dòng)模式,指定為singleInstance模式的Activity會(huì)啟用一個(gè)新的返回棧來管理這個(gè)Activity。這樣做的意義在于當(dāng)程序中有一個(gè)Activity是允許其他程序調(diào)用的,那么想實(shí)現(xiàn)其他程序和我們的程序可以共享這個(gè)Activity的實(shí)例,就可以通過singleInstance模式來實(shí)現(xiàn)了,而其他3種模式均無法做到,因?yàn)槊總€(gè)應(yīng)用程序都有自己的返回棧,同一個(gè)Activity在不同的返回棧中入棧時(shí)必然是創(chuàng)建了新的實(shí)例。singleInstance這種模式會(huì)有一個(gè)單獨(dú)的返回棧來管理這個(gè)Activity,不管是哪個(gè)應(yīng)用程序來訪問這個(gè)Activity,都共用同一個(gè)返回棧,也就解決了共享Activity實(shí)例的問題。同樣通過以下代碼來驗(yàn)證。

修改AndroidManifest.xml中SecondActivity的啟動(dòng)模式,同時(shí)創(chuàng)建ThirdActivity

<activity android:name=".SecondActivity"
            android:launchMode="singleInstance">
            
</activity>

修改FirstActivity中onCreate()方法的代碼,打印當(dāng)前返回棧的id

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", "Task id is" + this.getTaskId());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

然后修改SecondActivity中的onCreate()方法,打印當(dāng)前返回棧的id,同時(shí)點(diǎn)擊按鈕啟動(dòng)ThirdActivity

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("SecondActivity", "Task id is" + this.getTaskId());
        setContentView(R.layout.second_layout);
        Button button2 = (Button)findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intet = new Intent(SecondActivity.this, ThirdActivity.class);
                startActivity(intet);
            }
        });
    }

最后修改ThirdActivity中的onCreate()方法,同樣打印當(dāng)前返回棧的信息

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("ThirdActivity", "Task id is" + this.getTaskId());
        setContentView(R.layout.third_layout);
    }

運(yùn)行結(jié)果

// 打印結(jié)果
    // D/FirstActivity: Task id is17
    // D/SecondActivity: Task id is18
    // D/ThirdActivity: Task id is17

通過運(yùn)行結(jié)果可以看到,SecondActivity的Task id不同于FirstActivity和ThirdActivity,這說明SecondActivity確實(shí)是存放在一個(gè)單獨(dú)的返回棧里,而且這個(gè)返回棧只有SecondActivity這一個(gè)Activity

然后按下Back鍵進(jìn)行反悔,會(huì)發(fā)現(xiàn)ThirdActivity竟然直接返回到了FirstActivity,再按下Back返回鍵又返回到SecondActivity,再按下Back鍵才會(huì)退出程序。這個(gè)原因其實(shí)也很簡(jiǎn)單,由于FirstActivity和ThirdActivity是存放在一個(gè)返回棧中的,當(dāng)在ThirdActivity中按下Back鍵,ThirdActivity從返回棧中出棧,那么FirstActivity久成為了棧頂Activity顯示在界面上,因此就出現(xiàn)了從ThirdActivity直接返回到FirstActivity的情況。然后在FirstActivity界面再次按下Back鍵,這時(shí)當(dāng)前的返回棧已經(jīng)空了,于是就顯示了另一個(gè)返回棧的棧頂Activity,即SecondActivity。最后再按下Back鍵,這時(shí)所有的返回棧都已經(jīng)空了,也就自然退出程序了。

singleInstance模式的原理示意圖:

singleInstance模式的原理示意圖.png

Activity的最佳實(shí)踐

知曉當(dāng)前是在哪一個(gè)Activity

這個(gè)技巧將會(huì)教會(huì)你如何根據(jù)程序當(dāng)前的界面就能判斷出是哪一個(gè)Activity
創(chuàng)建一個(gè)BaseActivity類,因?yàn)椴恍枰孊aseActivity在AndroidManifest.xml中注冊(cè),因此創(chuàng)建一個(gè)普通的Java類就可以了,然后讓BaseActivity繼承自AppCompatActivity,并重寫onCreate()方法,如下:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
    }
}

在BaseActivity中的onCreate()方法中獲取了當(dāng)前實(shí)例的名字,并通過Log打印出來。接下來讓FirstActivity和SecondActivity繼承自BaseActivity。由于BaseActivity又是繼承自AppCompatActivity的,所以項(xiàng)目中所有Activity的現(xiàn)有功能不會(huì)受影響,仍然完全繼承了Activity中的所有特性.

打印結(jié)果
D/BaseActivity: FirstActivity
D/BaseActivity: SecondActivity

現(xiàn)在每當(dāng)我們進(jìn)入到一個(gè)Activity的界面,該Activity的類名就被打印出來,這樣就可以時(shí)時(shí)知曉當(dāng)前界面對(duì)應(yīng)的哪一個(gè)Activity了。

隨時(shí)隨地退出程序

當(dāng)頁面停留比較深的時(shí)候,想退出程序需要按下多次Back鍵。按HOME鍵只是把程序掛起來,并沒有真正退出程序,因此需要有一個(gè)隨時(shí)隨地都能退出程序的方案,思路就是用一個(gè)專門的集合類對(duì)所有的Activity進(jìn)行管理就可以了。

新建一個(gè)ActivityController類作為Activity管理器,代碼如下:

public class ActivityController {
    public static List<Activity> activities = new ArrayList<>();
    
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }
    
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    
    public static void finishAll() {
        for (Activity activity: activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
}

在活動(dòng)管理器中,通過一個(gè)List來暫存活動(dòng),然后提供一個(gè)addActivity()方法用于向List中添加一個(gè)Activity,提供了一個(gè)removeActivity()方法用于從List中移除Activity,最后提供了一個(gè)finishAll()方法用于將List中存儲(chǔ)的活動(dòng)全部銷毀掉。

接下來修改BaseActivity中的代碼如下:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        ActivityController.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityController.removeActivity(this);
    }
}

在BaseActivity的onCreate()方法中調(diào)用了ActivityController的addActivity()方法,表明將當(dāng)前正在創(chuàng)建的活動(dòng)添加到活動(dòng)管理器里。然后在BaseActivity中重寫onDestroy()方法,并調(diào)用ActivityController的removeActivity()方法,表明將一個(gè)馬上要銷毀的Activity從Activity從活動(dòng)管理器中移除。

以后無論想在任何地方退出程序,只需要調(diào)用ActivityController的finishAll()方法就可以了,如在SecondActivity中想要退出程序,只需要寫如下代碼就可以了。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        // 給按鈕注冊(cè)點(diǎn)擊事件,并在點(diǎn)擊事件中添加返回?cái)?shù)據(jù)的邏輯
        Button button2 = (Button)findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ActivityController.finishAll();
            }
        });
    }

當(dāng)然還可以在銷毀所有的代碼后邊加上殺掉當(dāng)前進(jìn)程的代碼,以保證程序完全退出,殺掉進(jìn)程的代碼如下:

android.os.Process.killProcess(android.os.Process.myPid());

其中,killProcess()方法用于殺掉一個(gè)進(jìn)程,它接收一個(gè)進(jìn)程id參數(shù),我們可以通過myPid()方法來獲取當(dāng)前程序的進(jìn)程id。這個(gè)只能傻吊當(dāng)前程序的進(jìn)程,不能使用這個(gè)方法殺掉其他程序的進(jìn)程。

啟動(dòng)Activity的最佳寫法

啟動(dòng)Activity的方法之前已經(jīng)說過,首先通過Intent構(gòu)建出當(dāng)前的“意圖”,然后調(diào)用startActivity()或startActivityForResult()方法將Activity啟動(dòng)起來,如果有數(shù)據(jù)需要從一個(gè)Activity傳遞到另一個(gè)Activity,也可以借助Intent來完成。

假設(shè)SecondActivity中需要用到兩個(gè)非常重要的字符串參數(shù),在啟動(dòng)SecondActivity的時(shí)候必須要傳遞過來,那么在FirstActivity中很容易會(huì)寫下如下代碼:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button = (Button)findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                intent.putExtra("param1", "data1");
                intent.putExtra("param2", "data2");
                startActivity(intent);
            }
        });
    }

這樣寫是完全OK的,只是如果在真正的項(xiàng)目開發(fā)中遇到SecondActivity并不是由你開發(fā),但現(xiàn)在你負(fù)責(zé)的部分需要啟動(dòng)SecondActivity這個(gè)功能,而你卻不清楚啟動(dòng)這個(gè)Activity需要傳遞哪些數(shù)據(jù)。這時(shí),無非兩種方法,一是自己閱讀SecondActivity的代碼,二是詢問負(fù)責(zé)編寫SecondActivity的同事,這樣的話就會(huì)比較麻煩。現(xiàn)在換一種寫法來解決以上問題。

修改SecondActivity中的代碼:

public class SecondActivity extends BaseActivity {
    
    public static void actionStart(Context context, String data1, String data2) {
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1", data1);
        intent.putExtra("param2", data2);
        context.startActivity(intent);
    }
    ...

}

在SecondActivity中添加了一個(gè)actionStart()方法,這個(gè)方法中完成了Intent的構(gòu)建,另外所有SecondActivity中需要的數(shù)據(jù)都是通過actionStart()方法的參數(shù)傳遞過來的,然后把它們存儲(chǔ)到Intent中,最后調(diào)用startActivity()方法啟動(dòng)SecondActivity。

這樣寫的好處在于一目了然,SecondActivity所需要的數(shù)據(jù)在方法參數(shù)中全部體現(xiàn)出來了,這樣即使不用閱讀SecondActivity中的代碼,不去詢問負(fù)責(zé)SecondActivity開發(fā)的相關(guān)同事,也可以知道啟動(dòng)SecondActivity需要傳遞哪些數(shù)據(jù)。這樣還簡(jiǎn)化了啟動(dòng)Activity的代碼,現(xiàn)在在FirstActivity中只需要一行代碼就可以啟動(dòng)SecondActivity了。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button = (Button)findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
            }
        });
    }

養(yǎng)成良好的習(xí)慣,給編寫的每一個(gè)Activity都添加類似的啟動(dòng)方法,這樣就可以讓啟動(dòng)Activity變得非常簡(jiǎn)單,還可以節(jié)省不少有同事過來詢問你的時(shí)間。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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