第一行代碼讀書(shū)筆記 2 -- 探究活動(dòng)

本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):

  • 顯示、隱式 Intent 的相關(guān)內(nèi)容;
  • 活動(dòng) Activity 的生命周期;
  • 活動(dòng) Activity 的啟動(dòng)模式;
  • 活動(dòng)的最佳實(shí)踐:活動(dòng)管理類、啟動(dòng)活動(dòng)的最佳寫(xiě)法。
圖片來(lái)源于網(wǎng)絡(luò)

2.1 活動(dòng)是什么

活動(dòng)(activity)是一種可以包含用戶界面的組件,主要用于和用戶進(jìn)行交互。

2.2 使用 Intent 在活動(dòng)之間穿梭

Intent 大致可分為兩種:顯示 Intent 和 隱式 Intent

2.2.1 使用顯示 Intent

顯示 Intent 有多個(gè)構(gòu)造函數(shù)的重載,其中一個(gè)是 Intent(Context packageContext,Class<?>cls)

  • 第一個(gè)參數(shù) Context 要求提供一個(gè)啟動(dòng)活動(dòng)的上下文。
  • 第二個(gè)參數(shù) Class 則是指定想要啟動(dòng)的目標(biāo)活動(dòng)。

代碼如下所示:

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

2.2.2 使用隱式 Intent

隱式 Intent 并不明確指出想要啟動(dòng)哪一個(gè)活動(dòng),而是指定了一系列更為抽象的 actioncategory 等信息,然后交由系統(tǒng)去分析這個(gè) Intent,并找出合適的活動(dòng)去啟動(dòng)。

用法:打開(kāi) AndroidManifest.xml,添加如下代碼:

<activity android:name=".inquiry_activity.SecondActivity">   
      <intent-filter>       
            <!-- 指定活動(dòng)能響應(yīng)的action和category -->        
            <action android:name="com.wonderful.ACTION_START"/>       
            <category android:name="android.intent.category.DEFAULT"/>   
      </intent-filter>
</activity>

此時(shí)在 activity 中的代碼修改為:

// 隱式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
startActivity(intent);

只有 <action><category> 中的內(nèi)容同時(shí)能夠匹配上 Intent 中指定的 actioncategory 時(shí),活動(dòng)才能響應(yīng)該 Intent

上例中 android.intent.category.DEFAULT 是一種默認(rèn)的 category,若 Intent 中增加一個(gè) category

// 隱式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
intent.addCategory("com.wonderful.MY_CATEGORY");//增加一個(gè)category
startActivity(intent);

此時(shí)在 <intent-filter> 標(biāo)簽中應(yīng)再添加一個(gè) category 聲明:

<activity android:name=".inquiry_activity.SecondActivity">   
      <intent-filter>       
            <!-- 指定活動(dòng)能響應(yīng)的action和category -->        
            <action android:name="com.wonderful.ACTION_START"/>       
            <category android:name="android.intent.category.DEFAULT"/>   
            <!-- 新添加的category -->
            <category android:name="android.intent.category.MY_CATEGORY"/>   
      </intent-filter>
</activity>

2.2.3 更多隱式 Intent 的用法

使用隱式 Intent,還可以啟動(dòng)其他程序的活動(dòng),比如調(diào)用系統(tǒng)的瀏覽器來(lái)打開(kāi)某個(gè)網(wǎng)頁(yè):

// 隱式Intent:打開(kāi)網(wǎng)頁(yè)
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);

比如調(diào)用系統(tǒng)的撥號(hào)界面:

// 隱式Intent:打開(kāi)系統(tǒng)撥號(hào)界面
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

2.2.4 向下一個(gè)活動(dòng)傳遞數(shù)據(jù)

Intent 中提供了一系列 putExtra() 方法的重載,可把要傳遞的數(shù)據(jù)暫存在 Intent 中,啟動(dòng)了另一個(gè)活動(dòng)后再?gòu)?Intent 中取出。

FirstActivity 中傳遞一個(gè)字符串到 SecondActivity 中,FirstActivity 中的代碼為:

String data = "hello SecondActivity";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);

SecondActivity中的代碼如下:

Intent intent = getIntent();
// String:getStringExtra(); int:getIntExtra(); 布爾型:getBooleanExtra() ...以此類推
String data = intent.getStringExtra("extra_data");
Log.d(TAG, data);

2.2.5 返回?cái)?shù)據(jù)給上一個(gè)活動(dòng)

Activity 中有個(gè) startActivityForResult() 方法用于啟動(dòng)活動(dòng),此方法在活動(dòng)銷毀時(shí)能返回一個(gè)結(jié)果給上個(gè)活動(dòng)。

startActivityForResult() 方法接收兩個(gè)參數(shù):Intent、請(qǐng)求碼(用作回調(diào)時(shí)判斷數(shù)據(jù)來(lái)源)。

修改 FirstActivity 中的代碼如下:

Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);//請(qǐng)求碼只要是唯一值就行了,這里傳入1

接著在 SecondActivity 中添加返回?cái)?shù)據(jù):

Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();//銷毀當(dāng)前活動(dòng)

SecondActivity 被銷毀后回調(diào)上一個(gè)活動(dòng)的 onActivityResult() 方法,因此還需要在 FirstActivity 中重寫(xiě)此方法來(lái)得到返回?cái)?shù)據(jù):

/** 
 * 處理得到的返回?cái)?shù)據(jù) 
 * @param requestCode  啟動(dòng)活動(dòng)時(shí)傳入的請(qǐng)求碼
 * @param resultCode  返回?cái)?shù)據(jù)時(shí)傳入的處理結(jié)果
 * @param data  攜帶返回?cái)?shù)據(jù)的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(TAG, returnedData);           
                 }            
                 break;                
            default:           
                 break;   
       }
}

若在 SecondActivity 是通過(guò)按下 Back 鍵回到 FirstActivity,可以在 SecondActivity 中重寫(xiě) onBackPressed() 方法:

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

2.3 活動(dòng)的生命周期

2.3.1 返回棧

Android 是使用任務(wù)(Task)來(lái)管理活動(dòng)的,一個(gè)任務(wù)就是一組存放在棧里的活動(dòng)的集合,這個(gè)棧也被稱作返回棧(Back Stack)。

棧是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),在默認(rèn)情況下,每當(dāng)啟動(dòng)了一個(gè)新的活動(dòng),它會(huì)在返回棧中入棧,并處于棧頂?shù)奈恢谩?/p>

每當(dāng)按下 Back 鍵或調(diào)用 finish() 方法去銷毀一個(gè)活動(dòng)時(shí),處于棧頂?shù)幕顒?dòng)會(huì)出棧,這時(shí)前一個(gè)入棧的活動(dòng)就會(huì)重新處于棧頂?shù)奈恢谩O到y(tǒng)總是會(huì)顯示處于棧頂?shù)幕顒?dòng)給用戶。

如圖所示:

返回棧工作示意圖

2.3.2 活動(dòng)狀態(tài)

每個(gè)活動(dòng)在其生命周期中最多可能會(huì)有四種狀態(tài)。

1. 運(yùn)行狀態(tài)

活動(dòng)位于返回棧的棧頂。系統(tǒng)最不愿意回收的就是處于運(yùn)行狀態(tài)的活動(dòng),因?yàn)檫@會(huì)帶來(lái)非常差的用戶體驗(yàn)。

2. 暫停狀態(tài)

活動(dòng)不再處于棧頂位置,但仍然可見(jiàn)。(并不是每一個(gè)活動(dòng)都會(huì)占滿整個(gè)屏幕的,如對(duì)話框形式的活動(dòng)只會(huì)占用屏幕中間的部分區(qū)域)。

處于暫停狀態(tài)的活動(dòng)仍然是完全存活著的,系統(tǒng)也不愿意去回收這種活動(dòng)(因?yàn)樗€是可見(jiàn)的,回收可見(jiàn)的東西都會(huì)在用戶體驗(yàn)方面有不好的影響),只有在內(nèi)存極低的情況下,系統(tǒng)才會(huì)去考慮回收這種活動(dòng)。

3. 停止?fàn)顟B(tài)

活動(dòng)不再處于棧頂位置,并且完全不可見(jiàn)的時(shí)候。系統(tǒng)仍然會(huì)為這種活動(dòng)保存相應(yīng)的狀態(tài)和成員變量,但這并不是完全可靠的,當(dāng)其他地方需要內(nèi)存時(shí),處于停止?fàn)顟B(tài)的活動(dòng)有可能會(huì)被系統(tǒng)回收。

4. 銷毀狀態(tài)

當(dāng)一個(gè)活動(dòng)從返回棧中移除后就變成了銷毀狀態(tài)。系統(tǒng)會(huì)最傾向于回收處于這種狀態(tài)的活動(dòng),從而保證手機(jī)的內(nèi)存充足。

2.3.3 活動(dòng)的生存期

Activity 類中定義了七個(gè)回調(diào)方法,覆蓋了活動(dòng)生命周期的每一個(gè)環(huán)節(jié):

1. onCreate()

在活動(dòng)第一次被創(chuàng)建的時(shí)候調(diào)用。在這個(gè)方法中完成活動(dòng)的初始化操作,如加載布局、綁定事件等。

2. onStart()

在活動(dòng)由不可見(jiàn)變?yōu)榭梢?jiàn)的時(shí)候調(diào)用。

3. onResume()

在活動(dòng)準(zhǔn)備好和用戶進(jìn)行交互的時(shí)候調(diào)用。此時(shí)的活動(dòng)一定位于返回棧的棧頂,并且處于運(yùn)行狀態(tài)。

4. onPause()

在系統(tǒng)準(zhǔn)備去啟動(dòng)或恢復(fù)另一個(gè)活動(dòng)的時(shí)候調(diào)用。(釋放一些消耗 CPU 的資源,保存一些關(guān)鍵數(shù)據(jù))。

5. onStop()

在活動(dòng)完全不可見(jiàn)的時(shí)候調(diào)用。它和 onPause() 方法的主要區(qū)別在于,若啟動(dòng)的新活動(dòng)是一個(gè)對(duì)話框式的活動(dòng),那么 onPause() 方法會(huì)得到執(zhí)行,而 onStop() 方法并不會(huì)執(zhí)行。

6. onDestroy()

在活動(dòng)被銷毀之前調(diào)用,之后活動(dòng)的狀態(tài)將變?yōu)殇N毀狀態(tài)。

7. onRestart()

在活動(dòng)由停止?fàn)顟B(tài)變?yōu)檫\(yùn)行狀態(tài)之前調(diào)用,也就是活動(dòng)被重新啟動(dòng)了。

以上七個(gè)方法中除了 onRestart() 方法,其他都是兩兩相對(duì)的,從而又可以將活動(dòng)分為三種生存期。

  • 完整生存期: 活動(dòng)在 onCreate() 方法和 onDestroy() 方法之間所經(jīng)歷的;
  • 可見(jiàn)生存期: 活動(dòng)在 onStart() 方法和 onStop() 方法之間所經(jīng)歷的;
  • 前臺(tái)生存期: 活動(dòng)在 onResume() 方法和 onPause() 方法之間所經(jīng)歷的。

Android 官方提供了一張活動(dòng)生命周期的示意圖,如圖所示:

活動(dòng)的生命周期

2.3.4 活動(dòng)被回收了怎么辦

Activity 中提供了一個(gè) onSaveInstanceState() 回調(diào)方法,這 個(gè)方法會(huì)保證一定在活動(dòng)被回收之前調(diào)用。
??
onSaveInstanceState() 方法會(huì)攜帶一個(gè) Bundle 類型的參數(shù),Bundle 提供了一系列的方法用于保存數(shù)據(jù),如用 putString() 方法保存字符串,用 putInt() 方法保存整型數(shù)據(jù), 以此類推。每個(gè)保存方法需要傳入兩個(gè)參數(shù),第一個(gè)參數(shù)是鍵,用于后面從 Bundle 中取值, 第二個(gè)參數(shù)是真正要保存的內(nèi)容。

Activity 中添加如下代碼就可以將臨時(shí)數(shù)據(jù)進(jìn)行保存:

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

Activity 被回收后的恢復(fù)操作,修改其 onCreate() 方法,如下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null) { // 若不為空,取出相應(yīng)的數(shù)據(jù)
        String tempData = savedInstanceState.getString("data_key"); 
        Log.d(TAG, tempData);
    }
}

2.4 活動(dòng)的啟動(dòng)模式

啟動(dòng)模式一共有四種,分別是 standardsingleTopsingleTasksingleInstance,可以在 AndroidManifest.xml 中通過(guò)給 <activity> 標(biāo)簽指定 android:launchMode 屬性來(lái)選擇啟動(dòng)模式。

2.4.1 standard

standard 是活動(dòng)默認(rèn)的啟動(dòng)模式,每當(dāng)啟動(dòng)一個(gè)新的活動(dòng),它就會(huì)在返回棧中入棧,并處于棧頂?shù)奈恢谩?duì)于使用 standard 模式的活動(dòng),系統(tǒng)不會(huì)在乎這個(gè)活動(dòng)是否已經(jīng)在返回棧中存在,每次啟動(dòng)都會(huì)創(chuàng)建該活動(dòng)的一個(gè)新的實(shí)例。
??
其原理示意圖如下:

standard模式示意圖

2.4.2 singleTop

當(dāng)活動(dòng)的啟動(dòng)模式指定為 singleTop,在啟動(dòng)活動(dòng)時(shí)如果發(fā)現(xiàn)返回棧的棧頂已經(jīng)是該活動(dòng),則認(rèn)為可以直接使用它,不會(huì)再創(chuàng)建新的活動(dòng)實(shí)例。

其原理示意圖如下:


singleTop模式示意圖

2.4.3 singleTask

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

其原理示意圖如下:


singleTask模式示意圖

2.4.4 singleInstance

不同于以上三種啟動(dòng)模式,指定為 singleInstance 模式的活動(dòng)會(huì)啟用一個(gè)新的返回棧來(lái)管理這個(gè)活動(dòng)(其實(shí)如果 singleTask 模式指定了不同的 taskAffinity,也會(huì)啟動(dòng)一個(gè)新的返回棧)。
??
其原理示意圖如下:

singleInstance模式示意圖

2.5 活動(dòng)的最佳實(shí)踐

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

用一個(gè)專門(mén)的集合類對(duì)所有的活動(dòng)進(jìn)行管理,新建一個(gè) ActivityCollector 類作為活動(dòng)管理器,代碼如下所示:

public class ActivityCollector {
    // 通過(guò)一個(gè)List來(lái)緩存活動(dòng)
    public static List<Activity> activities = new ArrayList<Activity>();
    // 用于向List中添加一個(gè)活動(dòng)
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }
    // 用于從List中移除活動(dòng)
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    // 將List中存儲(chǔ)的活動(dòng)全部銷毀掉
    public static void finishAll() {
        for (Activity activity : activities) {
           if (!activity.isFinishing()) {
               activity.finish();
           }
        }
    }
}

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

接下來(lái)修改 BaseActivity 中的代碼如下:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());// 知曉當(dāng)前是在哪一個(gè)活動(dòng)
        ActivityCollector.addActivity(this);// 將當(dāng)前正在創(chuàng)建的活動(dòng)添加到活動(dòng)管理期里
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);// 將一個(gè)馬上要銷毀的活動(dòng)從管理器里移除
    }
}

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

這樣,不管你想在什么地方退出程序,只需要調(diào)用 ActivityCollector.finishAll() 方法就可以了。

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

// killProcess()方法用于殺掉一個(gè)進(jìn)程,它接收一個(gè)進(jìn)程id參數(shù),可通過(guò)myPid()方法獲得當(dāng)前程序的進(jìn)程id
android.os.Process.killProcess(android.os.Process.myPid());

需注意:killProcess() 方法只能用于殺掉當(dāng)前程序的進(jìn)程,不能殺掉其他程序。

2.5.2 啟動(dòng)活動(dòng)的最佳寫(xiě)法

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

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

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

但此時(shí) SecondActivity 并不是由你開(kāi)發(fā)的,但現(xiàn)在你負(fù)責(zé)的部分需要有啟動(dòng) SecondActivity 這個(gè)功能,而你卻不清楚啟動(dòng)這個(gè)活動(dòng)需要傳遞哪些數(shù)據(jù)。這時(shí)要么你自己去閱讀 SecondActivity中的代碼,要么詢問(wèn)負(fù)責(zé)編寫(xiě) SecondActivity 的同事。而換一種寫(xiě)法,就可以輕松解決掉上面的窘境。

修改 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ù)都是通過(guò) actionStart() 方法的參數(shù)傳遞過(guò)來(lái)的,然后把它們存儲(chǔ)到 Intent 中,最后調(diào)用 startActivity() 方法啟動(dòng) SecondActivity

這樣 SecondActivity 所需要的數(shù)據(jù)全部都在方法參數(shù)中體現(xiàn)出來(lái)了,即使不用閱讀 SecondActivity 中的代碼,也可以非常清晰地知道啟動(dòng) SecondActivity 需要傳遞哪些數(shù)據(jù)。

另外,這樣寫(xiě)還簡(jiǎn)化了啟動(dòng)活動(dòng)的代碼,現(xiàn)在只需要一行代碼就可以啟動(dòng) SecondActivity, 如下:

SecondActivity.actionStart(FirstActivity.this, "data1", "data2");

好了,今天就到這,下篇文章將進(jìn)入第三章的學(xué)習(xí)--UI開(kāi)發(fā)。

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

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