Android的活動介紹
1. 活動是什麼
活動(Activity)是最容易吸引用戶的地方,它是一種可以包含用戶界面的組件,主要用於和用戶進行交互。一個應用程序中可以包含零個或多個活動,但不包含任何活動的應用程序很少見,誰也不想讓自己的應用永遠無法被用戶看到吧?
2. Intent 在活動中的使用
Intent
大致可分爲兩種:顯示 Intent 和 隱式 Intent
使用顯示 Intent
Intent 有多個構造函數的重載,其中一個是Intent(Context packageContext,Class<?>cls)
- 第一個參數Context要求提供一個啓動活動的上下文
- 第二個參數Class則是指定想要啓動的目標活動
代碼如下所示:
// 顯示Intent
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
使用隱式 Intent
隱式 Intent並不明確指出想要啓動哪一個活動,而是指定了一系列更爲抽象的action和category等信息,然後交由系統去分析這個Intent,並找出合適的活動去啓動。
用法:打開AndroidManifest.xml,添加如下代碼:
<activity android:name=".inquiry_activity.SecondActivity">
<intent-filter>
<!-- 指定活動能響應的action和category -->
<action android:name="com.yezijie.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
此時在activity中的代碼修改爲:
// 隱式Intent
Intent intent = new Intent("com.yezijie.ACTION_START");
startActivity(intent);
只有和中的內容同時能夠匹配上Intent中指定的action和category時,活動才能響應該Intent。上例中android.intent.category.DEFAULT
是一種默認的category,若Intent中增加一個category:
// 隱式Intent
Intent intent = new Intent("com.yezijie.ACTION_START");
intent.addCategory("com.yezijie.MY_CATEGORY");//增加一個category
startActivity(intent);
此時在 標籤中應再添加一個category聲明:
<activity android:name=".inquiry_activity.SecondActivity">
<intent-filter>
<!-- 指定活動能響應的action和category -->
<action android:name="com.yezijie.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<!-- 新添加的category -->
<category android:name="android.intent.category.MY_CATEGORY"/>
</intent-filter>
</activity>
更多隱式 Intent 的用法
使用隱式Intent,還可以啓動其他程序的活動,比如調用系統的瀏覽器來打開某個網頁:
// 隱式Intent:打開網頁
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
比如調用系統的撥號界面:
// 隱式Intent:打開系統撥號界面
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
在標籤中再配置一個標籤,用於更精確地指定當前活動能夠響應什麼類
型的數據。標籤中主要可以配置以下內容。
android:scheme
。用於指定數據的協議部分,如上例中的http部分、geo表示顯示地理位置、tel表示撥打電話。
android:host
。用於指定數據的主機名部分,如上例中的www.baidu.com部分。
android:port
。用於指定數據的端口部分,一般緊隨在主機名之後。
android:path
。用於指定主機名和端口之後的部分,如一段網址中跟在域名之後的內容。
android:mimeType
。用於指定可以處理的數據類型,允許使用通配符的方式進行指定。
只有標籤中指定的內容和Intent
中攜帶的Data
完全一致時,當前活動才能夠響應該Intent。不過一般在標籤中都不會指定過多的內容,如上面瀏覽器示例中,其實只需要指定android:scheme
爲http
,就可以響應所有的http
協議的Intent
了。
向下一個活動傳遞數據
Intent
中提供了一系列 putExtra()
方法的重載,可把要傳遞的數據暫存在Intent
中,啓動了另一個活動後再從Intent
中取出。如FirstActivity
中傳遞一個字符串到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);
返回數據給上一個活動
Activity
中有個startActivityForResult
() 方法用於啓動活動,此方法在活動銷燬時能返回一個結果給上個活動。startActivityForResult()
方法接收兩個參數:Intent、請求碼(用作回調時判斷數據來源)。
修改FirstActivity
中的代碼如下:
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);//請求碼只要是唯一值就行了,這裏傳入1
接着在SecondActivity
中添加返回數據:
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();//銷燬當前活動
在SecondActivity
被銷燬後回調上一個活動的onActivityResult()
方法,因此還需要在FirstActivity
中重寫此方法來得到返回數據:
/**
* 處理得到的返回數據
* @param requestCode 啓動活動時傳入的請求碼
* @param resultCode 返回數據時傳入的處理結果
* @param 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(TAG, returnedData);
}
break;
default:
break;
}
}
若在SecondActivity
是通過按下Back鍵回到FirstActivity
,可以在SecondActivity
中重寫onBackPressed()
方法:
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}
3. 活動的生命週期
返回棧
Android是使用任務(Task)來管理活動的,一個任務就是一組存放在棧裏的活動的集合,這個棧也被稱作返回棧(Back Stack)。棧是一種後進先出的數據結構,在默認情況下,每當我們啓動了一個新的活動,它會在返回棧中入棧,並處於棧頂的位置。而每當我們按下Back 鍵或調用finish()方法去銷燬一個活動時,處於棧頂的活動會出棧,這時前一個入棧的活動就會重新處於棧頂的位置。系統總是會顯示處於棧頂的活動給用戶。
活動狀態
每個活動在其生命週期中最多可能會有4種狀態。
-
運行狀態
當一個活動位於返回棧的棧頂時,這時活動就處於運行狀態。系統最不願意回收的就是處於運行狀態的活動,因爲這會帶來非常差的用戶體驗。 -
暫停狀態
當一個活動不再處於棧頂位置,但仍然可見時,這時活動就進入了暫停狀態。你可能會覺得既然活動已經不在棧頂了,還怎麼會可見呢?這是因爲並不是每一 個活動都會佔滿整個屏幕的,比如對話框形式的活動只會佔用屏幕中間的部分區域,系統也不願意去回收這種活動(因爲它還是可見的,回收可見的東西都會在用戶體驗方面有不好的影響),只有在內存極低的情況下,系統纔會去考慮回收這種活動。 -
停止狀態
當一個活動不再處於棧頂位置,並且完全不可見的時候,就進入了停止狀態。系統仍然會爲這種活動保存相應的狀態和成員變量,但是這並不是完全可靠的,當其他地方需要內存時,處於停止狀態的活動有可能會被系統回收。 -
銷燬狀態
當一個活動從返回棧中移除後就變成了銷燬狀態。系統會最傾向於回收處於這種狀態的活動,從而保證手機的內存充足。
活動的生存期
Activity類中定義了7個回調方法,覆蓋了活動生命週期的每一個環節。
-
onCreate()
。每個活動中都重寫了這個方法,它會在活動第一次被創建的時候調用。在這個方法中完成活動的初始化操作,比如說加載佈局、綁定事件等。 -
onStart()
。這個方法在活動由不可見變爲可見的時候調用。 -
onResume()
。這個方法在活動準備好和用戶進行交互的時候調用。此時的活動一定位於返回棧的棧頂,並且處於運行狀態。 -
onPause()
。這個方法在系統準備去啓動或者恢復另一個活動的時候調用。通常會在這個方法中將一些消耗CPU的資源釋放掉,以及保存一些關鍵數據,但這個方法的執行速度一定要快,不然會影響到新的棧頂活動的使用。 -
onStop()
。這個方法在活動完全不可見的時候調用。它和onPause()
方法的主要區別在於,如果啓動的新活動是一個對話框式的活動,那麼onPause()
方法會得到執行,而onStop()
方法並不會執行。 -
onDestroy()
。這個方法在活動被銷燬之前調用,之後活動的狀態將變爲銷燬狀態。 -
onRestart()
。這個方法在活動由停止狀態變爲運行狀態之前調用,也就是活動被重新啓動了。
以上7個方法中除了
onRestart()
方法,其他都是兩兩相對的,從而又可以將活動分爲3種生存期。
- 完整生存期。活動在
onCreate()
方法和onDestroy()
方法之間所經歷的,就是完整生存期。- 可見生存期。活動在
onStart()
方法和onStop()
方法之間所經歷的,就是可見生存期。- 前臺生存期。活動在
onResume()
方法和onPause()
方法之間所經歷的,就是前臺生存期。
活動被回收
Activity
中提供了一個 onSaveInstanceState()
回調方法,這 個方法會保證一定在活動被回收之前調用。onSaveInstanceState()
方法會攜帶一個 Bundle 類型的參數,Bundle 提供了一系列的方法用於保存數據,如用 putString()
方法保存字符串,用 putInt()
方法保存整型數據, 以此類推。
每個保存方法需要傳入兩個參數,
-
第一個參數是鍵,用於後面從 Bundle 中取值,
-
第二個參數是真正要保存的內容。
在Activity 中添加如下代碼就可以將臨時數據進行保存:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}
Activity 被回收後的恢復操作,修改其onCreate()
方法,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
// 若不爲空,取出相應的數據
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
}
注:
Intent
還可以結合Bundle
一起用於傳遞數據,首先可以把需要傳遞的數據都保存在Bundle
對象中,然後再將Bundle
對象存放在Intent
裏。到了目標活動之後先從Intent
中取出Bundle
,再從Bundle
中一一取出數據。
活動的啓動模式
啓動模式一共有4種,分別是 standard
、singleTop
、singleTask
和singleInstance
,可以在AndroidManifest.xml
中通過給標籤指定android:launchMode
屬性來選擇啓動模式。
standard
standard 是活動默認的啓動模式,在不進行顯式指定的情況下,所有活動都會自動使用這種啓動模式。在 standard 模式(即默認情況)下,每當啓動一個新的活動,它就會在返回棧中入棧,並處於棧頂的位置。對於使用 standard 模式的活動,系統不會在乎這個活動是否已經在返回棧中存在,每次啓動都會創建該活動的一個新的實例。
singleTop
當活動的啓動模式指定爲singleTop
,在啓動活動時如果發現返回棧的棧頂已經是該活動,則認爲可以直接使用它,不會再創建新的活動實例。不過當Activity
並未處於棧頂位置時,這時再啓動Activity
,還是會創建新的實例的。
singleTask
當活動的啓動模式指定爲singleTask
,每次啓動該活動時系統首先會在返回棧中檢查是否存在該活動的實例,如果發現已經存在則直接使用該實例,並把在這個活動之上的所有活動統統出棧,如果沒有發現就會創建一個新的活動實例。
singleInstance
singleInstance
模式應該算是4種啓動模式中最特殊也最複雜的一個了,不同於以上3種啓動模式,指定爲singleInstance
模式的活動會啓用一個新的返回棧來管理這個活動(其實如果singleTask
模式指定了不同的 taskAffinity
,也會啓動一個新的返回棧)。假設我們的程序中有一個活動是允許其他程序調用的,如果我們想實現其他程序和我們的程序可以共享這個活動的實例,應該如何實現呢?使用前面3種啓動模式肯定是做不到的,因爲每個應用程序都會有自己的返回棧,同一 個活動在不同的返回棧中入棧時必然是創建了新的實例。而使用singleInstance
模式就可以解決這個問題,在這種模式下會有一個單獨的返回棧來管理這個活動,不管是哪個應用程序來訪問這個活動,都共用的同一個返回棧,也就解決了共享活動實例的問題。
知曉當前是在哪一個活動
新建一個BaseActivity
類,然後讓BaseActivity
繼承自AppCompatActivity
,並重寫onCreate()
方法,如下所示:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
getClass().getSimpleName()
獲取了當前實例的類名。接下來我們需要讓BaseActivity
成爲Activity
項目中所有活動的父類。
隨時隨地退出程序
新建一個ActivityCollector
類作爲活動管理器。
public class ActivityCollector {
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();
}
}
activities.clear();
}
}
通過一個List來暫存活動,然後提供了一個addActivity()
方法用於向List中添加一個活動,提供了一個removeActivity()
方法用於從List中移除活動,最後提供了一個finishAll()
方法用於將List
中存儲的活動全部銷燬掉。
接下來修改BaseActivity
中的代碼
public class BaseActivity extends AppCompatActivity {
@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()
方法就可以了。
當然你還可以在銷燬所有活動的代碼後面再加上殺掉當前進程的代碼,以保證程序完全退出,殺掉進程的代碼如下所示:
android.os.Process.killProcess(android.os.Process.myPid());
其中,killProcess()
方法用於殺掉一個進程,它接收一個進程id參數,可以通過myPid()
方法來獲得當前程序的進程id。需要注意的是,killProcess()
方法只能用於殺掉當前程序的進程,不能使用這個方法去殺掉其他程序。
啓動活動的最佳寫法
動活動的方法:通過 Intent
構建出當前的“意圖”,然後調用 startActivity()
或 startActivityForResult()
方法將活動啓動起來,如果有數據需要從一個活 動傳遞到另一個活動,也可以藉助 Intent
來完成。假設 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
, 如下所示:
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
個人網站:分享客(https://sharerdiary.com/)
這個網站經常分享一些免費視頻、免費音樂、實用工具和各種福利,感興趣的朋友可以看看!