閱讀郭林《第一行代碼》的筆記——第2章 先從看得到的入手,探究活動

一、活動是什麼,活動的基本用法

活動(Activity)是最容易吸引到用戶的地方了,它是一種可以包含用戶界面的組件,主要用於和用戶進行交互。一個應用程序中可以包含零個或多個活動,但不包含任何活動的應用程序很少見,誰也不想讓自己的應用永遠無法被用戶看到吧?
Android程序的設計講究邏輯和視圖分離,最好每一個活動都能對應一個佈局,佈局就是用來顯示界面內容的。
創建和加載佈局

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      //setContentView()方法來給當前的活動加載一個佈局,而在
      //setContentView()方法中,我們一般都會傳入一個佈局文件的id。
        setContentView(R.layout.first_layout);
    }

在AndroidManifest文件中註冊

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity">
            <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

可以看到,活動的註冊聲明要放在標籤內,這裏是通過標籤來對活動進行註冊的。首先我們要使用android:name來指定具體註冊哪一個活動,那麼這裏填入的.FirstActivity是什麼意思呢?其實這不過就是com.example.activitytest.FirstActivity的縮寫而已。由於最外層的標籤中已經通過package屬性指定了程序的包名是com.example.activitytest,因此在註冊活動時這一部分就可以省略了,直接使用.FirstActivity就足夠了。然後我們使用了android:label指定活動中標題欄的內容,標題欄是顯示在活動最頂部的,待會兒運行的時候你就會看到。需要注意的是,給主活動指定的label不僅會成爲標題欄中的內容,還會成爲啓動器(Launcher)中應用程序顯示的名稱。之後在標籤的內部我們加入了標籤,並在這個標籤裏添加了和這兩句聲明。這個我在前面也已經解釋過了,如果你想讓FirstActivity作爲我們這個程序的主活動,即點擊桌面應用程序圖標時首先打開的就是這個活動,那就一定要加入這兩句聲明。另外需要注意,如果你的應用程序中沒有聲明任何一個活動作爲主活動,這個程序仍然是可以正常安裝的,只是你無法在啓動器中看到或者打開這個程序。這種程序一般都是作爲第三方服務供其他的應用在內部進行調用的,如支付寶快捷支付服務。
隱藏標題欄
隱藏的方法非常簡單,打開 FirstActivity,在onCreate()方法中添加如下代碼:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);//其中requestWindowFeature(Window.FEATURE_NO_TITLE) 的意思就是不在活動中顯示標題欄,注意這句代碼一定要在setContentView()之前執行,不然會報錯。
    setContentView(R.layout.first_layout);
}

在活動中使用Toast
Toast是Android系統提供的一種非常好的提醒方式,在程序中可以使用它將一些短小的信息通知給用戶,這些信息會在一段時間後自動消失,並且不會佔用任何屏幕空間。
Button button1 = (Button) findViewById(R.id.button_1);//獲取到在佈局文件中定義的元素,findViewById()方法返回的是一個View對象,我們需要向下轉型將它轉成Button對象。

 button1.setOnClickListener(new OnClickListener() {//爲按鈕註冊一個監聽器
        @Override
        public void onClick(View v) {
            Toast.makeText(FirstActivity.this, "You clicked Button 1",Toast.LENGTH_SHORT).show();//如下
        }
    });

Toast的用法非常簡單,通過靜態方法makeText()創建出一個Toast對象,然後調用show()將Toast顯示出來就可以了。這裏需要注意的是,makeText()方法需要傳入三個參數。第一個參數是Context,也就是Toast要求的上下文,由於活動本身就是一個Context對象,因此這裏直接傳入FirstActivity.this即可。第二個參數是Toast顯示的文本內容,第三個參數是Toast顯示的時長,有兩個內置常量可以選擇Toast.LENGTH_SHORT和Toast.LENGTH_LONG。
在活動中使用Menu
在main.xml中添加如下代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:id="@+id/add_item"
        android:title="Add"/>
    <item
        android:id="@+id/remove_item"
        android:title="Remove"/>
</menu>

這裏我們創建了兩個菜單項,其中標籤就是用來創建具體的某一個菜單項,然後通過android:id給這個菜單項指定一個唯一標識符,通過android:title給這個菜單項指定一個名稱。

然後打開FirstActivity,重寫onCreateOptionsMenu()方法,代碼如下所示:

public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);/*通過getMenuInflater()方法能夠得到MenuInflater對象,再調用它的inflate()方法就可以給當前活動創建菜單了。inflate()方法接收兩個參數,第一個參數用於指定我們通過哪一個資源文件來創建菜單,這裏當然傳入R.menu.main,第二個參數用於指定我們的菜單項將添加到哪一個Menu對象當中,這裏直接使用onCreateOptionsMenu()方法中傳入的menu參數。*/

return true;/*給這個方法返回true,表示允許創建的菜單顯示出來,如果返回了false,創建的菜單將無法顯示。*/
}

當然,僅僅讓菜單顯示出來是不夠的,我們定義菜單不僅是爲了看的,關鍵是要菜單真正可用才行,因此還要再定義菜單響應事件。在FirstActivity中重寫onOptionsItemSelected()方法:

 public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add_item:
                Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }

在onOptionsItemSelected()方法中,通過調用item.getItemId()來判斷我們點擊的是哪一個菜單項,然後給每個菜單項加入自己的邏輯處理。
銷燬一個活動
只要按一下Back鍵就可以銷燬當前的活動了。不過如果你不想通過按鍵的方式,而是希望在程序中通過代碼來銷燬活動,當然也可以,Activity類提供了一個finish()方法,我們在活動中調用一下這個方法就可以銷燬當前活動了。

二、使用Intent在活動之間穿梭

使用顯式Intent
Intent是Android程序中各組件之間進行交互的一種重要方式,它不僅可以指明當前組件想要執行的動作,還可以在不同組件之間傳遞數據。Intent一般可被用於啓動活動、啓動服務、以及發送廣播等場景。
Intent的用法大致可以分爲兩種,顯式Intent和隱式Intent,我們先來看一下顯式Intent如何使用。
Intent有多個構造函數的重載,其中一個是Intent(Context packageContext, Class

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);

使用隱式Intent
相比於顯式Intent,隱式Intent則含蓄了許多,它並不明確指出我們想要啓動哪一個活動,而是指定了一系列更爲抽象的action和category等信息,然後交由系統去分析這個Intent,並幫我們找出合適的活動去啓動。什麼叫做合適的活動呢?簡單來說就是可以響應我們這個隱式Intent的活動。
通過在標籤下配置的內容,可以指定當前活動能夠響應的action和category,打開AndroidManifest.xml,添加如下代碼:

<activity android:name=".SecondActivity" >
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

在標籤中我們指明瞭當前活動可以響應com.example.activitytest.ACTION_ START這個action,而標籤則包含了一些附加信息,更精確地指明瞭當前的活動能夠響應的Intent中還可能帶有的category。只有和中的內容同時能夠匹配上Intent中指定的action和category時,這個活動才能響應該Intent。
修改FirstActivity中按鈕的點擊事件,代碼如下所示:

button1.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent("com.example.activitytest.ACTION_START");
            startActivity(intent);
        }
    });

可以看到,我們使用了Intent的另一個構造函數,直接將action的字符串傳了進去,表明我們想要啓動能夠響應com.example.activitytest.ACTION_START這個action的活動。那前面不是說要和同時匹配上才能響應的嗎?怎麼沒看到哪裏有指定category呢?這是因爲android.intent.category.DEFAULT是一種默認的category,在調用startActivity()方法的時候會自動將這個category添加到Intent中。
每個Intent中只能指定一個action,但卻能指定多個category。
更多隱式Intent的用法
使用隱式Intent,我們不僅可以啓動自己程序內的活動,還可以啓動其他程序的活動,這使得Android多個應用程序之間的功能共享成爲了可能。比如說你的應用程序中需要展示一個網頁,這時你沒有必要自己去實現一個瀏覽器(事實上也不太可能),而是只需要調用系統的瀏覽器來打開這個網頁就行了。

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));

這裏我們首先指定了Intent的action是Intent.ACTION_VIEW,這是一個Android系統內置的動作,其常量值爲android.intent.action.VIEW。然後通過Uri.parse()方法,將一個網址字符串解析成一個Uri對象,再調用Intent的setData()方法將這個Uri對象傳遞進去。setData()部分:它接收一個Uri對象,主要用於指定當前Intent正在操作的數據,而這些數據通常都是以字符串的形式傳入到Uri.parse()方法中解析產生的。
在標籤中再配置一個標籤,用於更精確地指定當前活動能夠響應什麼類型的數據。標籤中主要可以配置以下內容。
1.android:scheme :用於指定數據的協議部分,如上例中的http部分。 除了http協議外,我們還可以指定很多其他協議,比如geo表示顯示地理位置、tel表示撥打電話。
2.android:host :用於指定數據的主機名部分,如上例中的www.baidu.com部分。
3.android:port:用於指定數據的端口部分,一般緊隨在主機名之後。
4.android:path :用於指定主機名和端口之後的部分,如一段網址中跟在域名之後的內容。
5.android:mimeType :用於指定可以處理的數據類型,允許使用通配符的方式進行指定。
只有標籤中指定的內容和Intent中攜帶的Data完全一致時,當前活動才能夠響應該Intent。不過一般在標籤中都不會指定過多的內容,如上面瀏覽器示例中,其實只需要指定android:scheme爲http,就可以響應所有的http協議的Intent了。
eg.

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));

向下一個活動傳遞數據
在啓動活動時傳遞數據的思路很簡單,Intent中提供了一系列putExtra()方法的重載,可以把我們想要傳遞的數據暫存在Intent中,啓動了另一個活動後,只需要把這些數據再從Intent中取出就可以了。
FirstActivity:

String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("extra_data", data);/*收兩個參數,第一個參數是鍵,用於後面從Intent中取值,第二個參數纔是真正要傳遞的數據。*/
   startActivity(intent);

SecondActivity:

Intent intent = getIntent();//通過getIntent()方法獲取到用於啓動SecondActivity的Intent
String data = intent.getStringExtra("extra_data");/*調用getStringExtra()方法,傳入相應的鍵值,就可以得到傳遞的數據了。*/
Log.d("SecondActivity", data);

返回數據給上一個活動
返回上一個活動只需要按一下Back鍵就可以了,並沒有一個用於啓動活動Intent來傳遞數據。通過查閱文檔你會發現,Activity中還有一個startActivityForResult()方法也是用於啓動活動的,但這個方法期望在活動銷燬的時候能夠返回一個結果給上一個活動。
startActivityForResult()方法接收兩個參數,第一個參數還是Intent,第二個參數是請求碼,用於在之後的回調中判斷數據的來源。
修改FirstActivity中按鈕的點擊事件,代碼如下所示:

button1.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        startActivityForResult(intent, 1);//啓動SecondActivity,請求碼只要是一個唯一值就可以了,這裏傳入了1。
    }
});

接下來我們在SecondActivity中給按鈕註冊點擊事件,並在點擊事件中添加返回數據的邏輯,代碼如下所示:

public class SecondActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.second_layout);
    Button button2 = (Button) findViewById(R.id.button_2);
    button2.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
    Intent intent = new Intent();/*構建了一個Intent,只不過這個Intent僅僅是用於傳遞數據而已,它沒有指定任何的“意圖”。*/

    intent.putExtra("data _return", "Hello FirstActivity");
    setResult(RESULT_OK, intent);/*把要傳遞的數據存放在Intent中,然後調用了setResult()方法。這個方法非常重要,是專門用於向上一個活動返回數據的。setResult()方法接收兩個參數,第一個參數用於向上一個活動返回處理結果,一般只使用RESULT_OK或RESULT_CANCELED這兩個值,第二個參數則是把帶有數據的Intent傳遞回去。*/
            finish();//銷燬當前活動。
        }
    });
}

由於我們是使用startActivityForResult()方法來啓動SecondActivity的,在SecondActivity被銷燬之後會回調上一個活動的onActivityResult()方法,因此我們需要在FirstActivity中重寫這個方法來得到返回的數據,如下所示:

//onActivityResult()方法帶有三個參數,
//第一個參數requestCode,即我們在啓動活動時傳入的請求碼。
//第二個參數resultCode,即我們在返回數據時傳入的處理結果。
//第三個參數data,即攜帶着返回數據的Intent。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case 1:
        if (resultCode == RESULT_OK) {
            String returnedData = data.getStringExtra("data_return");
            Log.d("FirstActivity", returnedData);
        }
        break;
    default:
    }
}

由於在一個活動中有可能調用startActivityForResult()方法去啓動很多不同的活動,每一個活動返回的數據都會回調到onActivityResult()這個方法中,因此我們首先要做的就是通過檢查requestCode的值來判斷數據來源。確定數據是從SecondActivity返回的之後,我們再通過resultCode的值來判斷處理結果是否成功。最後從data中取值並打印出來,這樣就完成了向上一個活動返回數據的工作。
這時候你可能會問,如果用戶在SecondActivity中並不是通過點擊按鈕,而是通過按下Back鍵回到FirstActivity,這樣數據不就沒法返回了嗎?沒錯,不過這種情況還是很好處理的,我們可以通過重寫onBackPressed()方法來解決這個問題,代碼如下所示:

@Override
public void onBackPressed() {
    Intent intent = new Intent();
    intent.putExtra("data_return", "Hello FirstActivity");
    setResult(RESULT_OK, intent);
    finish();
}

這樣的話,當用戶按下Back鍵,就會去執行onBackPressed()方法中的代碼,我們在這裏添加返回數據的邏輯就行了。

三、活動的生命週期

返回棧
Android是使用任務(Task)來管理活動的,一個任務就是一組存放在棧裏的活動的集合,這個棧也被稱作返回棧(Back Stack)。棧是一種後進先出的數據結構,在默認情況下,每當我們啓動了一個新的活動,它會在返回棧中入棧,並處於棧頂的位置。而每當我們按下Back鍵或調用finish()方法去銷燬一個活動時,處於棧頂的活動會出棧,這時前一個入棧的活動就會重新處於棧頂的位置。系統總是會顯示處於棧頂的活動給用戶。
示意圖2.19展示了返回棧是如何管理活動入棧出棧操作的。

活動狀態
每個活動在其生命週期中最多可能會有四種狀態。
1.運行狀態 :當一個活動位於返回棧的棧頂時,這時活動就處於運行狀態。系統最不願意回收的就是處於運行狀態的活動,因爲這會帶來非常差的用戶體驗。
2.暫停狀態 :當一個活動不再處於棧頂位置,但仍然可見時,這時活動就進入了暫停狀態。你可能會覺得既然活動已經不在棧頂了,還怎麼會可見呢?這是因爲並不是每一個活動都會佔滿整個屏幕的,比如對話框形式的活動只會佔用屏幕中間的部分區域,你很快就會在後面看到這種活動。處於暫停狀態的活動仍然是完全存活着的,系統也不願意去回收這種活動(因爲它還是可見的,回收可見的東西都會在用戶體驗方面有不好的影響),只有在內存極低的情況下,系統纔會去考慮回收這種活動。
3.停止狀態 :當一個活動不再處於棧頂位置,並且完全不可見的時候,就進入了停止狀態。系統仍然會爲這種活動保存相應的狀態和成員變量,但是這並不是完全可靠的,當其他地方需要內存時,處於停止狀態的活動有可能會被系統回收。
4.銷燬狀態 :當一個活動從返回棧中移除後就變成了銷燬狀態。系統會最傾向於回收處於這種狀態的活動,從而保證手機的內存充足。
活動的生存期
Activity類中定義了七個回調方法,覆蓋了活動生命週期的每一個環節,下面我來一一介紹下這七個方法。
1.onCreate() :這個方法你已經看到過很多次了,每個活動中我們都重寫了這個方法,它會在活動第一次被創建的時候調用。你應該在這個方法中完成活動的初始化操作,比如說加載佈局、綁定事件等。
2.onStart() :這個方法在活動由不可見變爲可見的時候調用。
3.onResume() :這個方法在活動準備好和用戶進行交互的時候調用。此時的活動一定位於返回棧的棧頂,並且處於運行狀態。
4.onPause() :這個方法在系統準備去啓動或者恢復另一個活動的時候調用。我們通常會在這個方法中將一些消耗CPU的資源釋放掉,以及保存一些關鍵數據,但這個方法的執行速度一定要快,不然會影響到新的棧頂活動的使用。
5.onStop() :這個方法在活動完全不可見的時候調用。它和onPause()方法的主要區別在於,如果啓動的新活動是一個對話框式的活動,那麼onPause()方法會得到執行,而onStop()方法並不會執行。
6.onDestroy() :這個方法在活動被銷燬之前調用,之後活動的狀態將變爲銷燬狀態。
7.onRestart() :這個方法在活動由停止狀態變爲運行狀態之前調用,也就是活動被重新啓動了。
以上七個方法中除了onRestart()方法,其他都是兩兩相對的,從而又可以將活動分爲三種生存期。
1.完整生存期 :活動在onCreate()方法和onDestroy()方法之間所經歷的,就是完整生存期。一般情況下,一個活動會在onCreate()方法中完成各種初始化操作,而在onDestroy()方法中完成釋放內存的操作。
2.可見生存期 :活動在onStart()方法和onStop()方法之間所經歷的,就是可見生存期。在可見生存期內,活動對於用戶總是可見的,即便有可能無法和用戶進行交互。我們可以通過這兩個方法,合理地管理那些對用戶可見的資源。比如在onStart()方法中對資源進行加載,而在onStop()方法中對資源進行釋放,從而保證處於停止狀態的活動不會佔用過多內存。
3.前臺生存期 :活動在onResume()方法和onPause()方法之間所經歷的,就是前臺生存期。在前臺生存期內,活動總是處於運行狀態的,此時的活動是可以和用戶進行相互的,我們平時看到和接觸最多的也這個狀態下的活動。
爲了幫助你能夠更好的理解,Android官方提供了一張活動生命週期的示意圖,如圖2.20所示。

活動被回收了怎麼辦
Activity中還提供了一個onSaveInstanceState()回調方法,這個方法會保證一定在活動被回收之前調用,因此我們可以通過這個方法來解決活動被回收時臨時數據得不到保存的問題。onSaveInstanceState()方法會攜帶一個Bundle類型的參數,Bundle提供了一系列的方法用於保存數據,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型數據,以此類推。每個保存方法需要傳入兩個參數,第一個參數是鍵,用於後面從Bundle中取值,第二個參數是真正要保存的內容。
在MainActivity中添加如下代碼就可以將臨時數據進行保存:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you just typed";
    outState.putString("data_key", tempData);
}

數據是已經保存下來了,那麼我們應該在哪裏進行恢復呢?細心的你也許早就發現,我們一直使用的onCreate()方法其實也有一個Bundle類型的參數。這個參數在一般情況下都是null,但是當活動被系統回收之前有通過onSaveInstanceState()方法來保存數據的話,這個參數就會帶有之前所保存的全部數據,我們只需要再通過相應的取值方法將數據取出即可。
修改MainActivity的onCreate()方法,如下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null) {
        String tempData = savedInstanceState.getString("data_key");
        Log.d(TAG, tempData);
    }
    ……
}

取出值之後再做相應的恢復操作就可以了,比如說將文本內容重新賦值到文本輸入框上,這裏我們只是簡單地打印一下。

四、活動的啓動模式

在實際項目中我們應該根據特定的需求爲每個活動指定恰當的啓動模式。啓動模式一共有四種,分別是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通過給標籤指定android:launchMode屬性來選擇啓動模式。
standard
standard是活動默認的啓動模式,在不進行顯式指定的情況下,所有活動都會自動使用這種啓動模式。在standard模式(即默認情況)下,每當啓動一個新的活動,它就會在返回棧中入棧,並處於棧頂的位置。對於使用standard模式的活動,系統不會在乎這個活動是否已經在返回棧中存在,每次啓動都會創建該活動的一個新的實例。
standard模式的原理示意圖,如圖2.31所示。

singleTop
當活動的啓動模式指定爲singleTop,在啓動活動時如果發現返回棧的棧頂已經是該活動,則認爲可以直接使用它,不會再創建新的活動實例。

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

singleTop模式的原理示意圖,如圖2.34所示。

singleTask
使用singleTop模式可以很好地解決重複創建棧頂活動的問題,但是正如你在上一節所看到的,如果該活動並沒有處於棧頂的位置,還是可能會創建多個活動實例的。那麼有沒有什麼辦法可以讓某個活動在整個應用程序的上下文中只存在一個實例呢?這就要藉助singleTask模式來實現了。當活動的啓動模式指定爲singleTask,每次啓動該活動時系統首先會在返回棧中檢查是否存在該活動的實例,如果發現已經存在則直接使用該實例,並把在這個活動之上的所有活動統統出棧,如果沒有發現就會創建一個新的活動實例。

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

singleTask模式的原理示意圖,如圖2.36所示。

singleInstance
singleInstance模式應該算是四種啓動模式中最特殊也最複雜的一個了,你也需要多花點功夫來理解這個模式。不同於以上三種啓動模式,指定爲singleInstance模式的活動會啓用一個新的返回棧來管理這個活動(其實如果singleTask模式指定了不同的taskAffinity,也會啓動一個新的返回棧)。那麼這樣做有什麼意義呢?想象以下場景,假設我們的程序中有一個活動是允許其他程序調用的,如果我們想實現其他程序和我們的程序可以共享這個活動的實例,應該如何實現呢?使用前面三種啓動模式肯定是做不到的,因爲每個應用程序都會有自己的返回棧,同一個活動在不同的返回棧中入棧時必然是創建了新的實例。而使用singleInstance模式就可以解決這個問題,在這種模式下會有一個單獨的返回棧來管理這個活動,不管是哪個應用程序來訪問這個活動,都共用的同一個返回棧,也就解決了共享活動實例的問題。

<activity
    android:name=".SecondActivity"
    android:launchMode="singleInstance" >
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="com.example.activitytest.MY_CATEGORY" />
    </intent-filter>
</activity>

singleInstance模式的原理示意圖,如圖2.38所示。

五、活動的最佳實踐

在這裏我準備教你幾種關於活動的最佳實踐技巧,這些技巧在你以後的開發工作當中將會非常受用。
知曉當前是在哪一個活動
新建一個BaseActivity繼承自Activity,然後在BaseActivity中重寫onCreate()方法,如下所示:

public class BaseActivity extends Activity {

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

}

我們在onCreate()方法中獲取了當前實例的類名,並通過Log打印了出來。

接下來我們需要讓BaseActivity成爲ActivityTest項目中所有活動的父類。修改FirstActivity、SecondActivity和ThirdActivity的繼承結構,讓它們不再繼承自Activity,而是繼承自BaseActivity。雖然項目中的活動不再直接繼承自Activity了,但是它們仍然完全繼承了Activity中的所有特性。
隨時隨地退出程序
只需要用一個專門的集合類對所有的活動進行管理就可以了。
新建一個ActivityCollector類作爲活動管理器,代碼如下所示:

public class ActivityCollector {

    public static List<Activity> activities = new ArrayList<Activity>();//暫存活動

    public static void addActivity(Activity activity) {//向List中添加一個活動
        activities.add(activity);
    }

    public static void removeActivity(Activity activity) {//從List中移除活動
        activities.remove(activity);
    }

    public static void finishAll() {//將List中存儲的活動全部都銷燬掉
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }

}

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

public class BaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        ActivityCollector.addActivity(this);//表明將當前正在創建的活動添加到活動管理器裏
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);//表明將一個馬上要銷燬的活動從活動管理器裏移除
    }

}

從此以後,不管你想在什麼地方退出程序,只需要調用ActivityCollector.finishAll()方法就可以了。
當然你還可以在銷燬所有活動的代碼後面再加上殺掉當前進程的代碼,以保證程序完全退出。
啓動活動的最佳寫法
假設SecondActivity中需要用到兩個非常重要的字符串參數,在啓動SecondActivity的時候必須要傳遞過來,那麼我們很容易會寫出如下代碼:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);

這樣寫是完全正確的,不管是從語法上還是規範上,只是在真正的項目開發中經常會有對接的問題出現。比如SecondActivity並不是由你開發的,但現在你負責的部分需要有啓動SecondActivity這個功能,而你卻不清楚啓動這個活動需要傳遞哪些數據。這時無非就有兩種辦法,一個是你自己去閱讀SecondActivity中的代碼,二是詢問負責編寫SecondActivity的同事。你會不會覺得很麻煩呢?其實只需要換一種寫法,就可以輕鬆解決掉上面的窘境。
修改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中添加了一個actionStart()方法,在這個方法中完成了Intent的構建,另外所有SecondActivity中需要的數據都是通過actionStart()方法的參數傳遞過來的,然後把它們存儲到Intent中,最後調用startActivity()方法啓動SecondActivity。
這樣寫的好處在哪裏呢?最重要的一點就是一目瞭然,SecondActivity所需要的數據全部都在方法參數中體現出來了,這樣即使不用閱讀SecondActivity中的代碼,或者詢問負責編寫SecondActivity的同事,你也可以非常清晰地知道啓動SecondActivity需要傳遞哪些數據。另外,這樣寫還簡化了啓動活動的代碼,現在只需要一行代碼就可以啓動SecondActivity,如下所示:

button1.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
    }
});

養成一個良好的習慣,給你編寫的每個活動都添加類似的啓動方法,這樣不僅可以讓啓動活動變得非常簡單,還可以節省不少你同事過來詢問你的時間。

發佈了259 篇原創文章 · 獲贊 47 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章