Activity 必知必會

1. Activity 的生命週期

1.1 分類

在講解生命週期的方法以前,先放上這張官方的圖:html

這張圖片講述了 Activity 的回調的方法,下表分類講解這些方法的做用。java

生命週期方法 做用
onCreate 表示 Activity 正在被建立
onRestart 表示 Activity 正在從新啓動
onStart 表示 Activity 正在被啓動
onResume 表示 Activity 已經可見
onPause 表示 Activity 正在中止
onStop 表示 Activity 即將中止
onDestroy 表示 Activity 即將被銷燬

1.2 各類狀況下生命週期的回調

如下總結一下各類狀況下,生命週期中的回調狀況(表中的 A,B 表明的是兩個 Activity):android

狀況 回調
第一次啓動 onCreate() -> onStart() -> onResume()
從 A 跳轉到不透明的 B A_onPause() -> B_onCreate() -> B_onStart() -> B_onResume() -> A_onStop()
從 A 跳轉到透明的 B A_onPause() -> B_onCreate() -> B_onStart() -> B_onResume()
從不透明的 B 再次回到 A B_onPause() -> A_onRestart() -> A_onStart() -> A_onResume() -> B_onStop()
從透明的 B 再次回到 A B_onPause() -> A_onResume() -> B_onStop() -> B_onDestroy()
用戶按 home 鍵 onPause() -> onStop()
按 home 鍵回後回到應用 onRestart() -> onStart() -> onResume()
用戶按 back 鍵回退 onPause() -> onStop() -> onDestroy()

1.3 onSaveInstanceState() 與 onRestoreInstanceState()

這兩個方法只有在應用遇到意外狀況下才會觸發。能夠用於保存一些臨時性的數據。shell

1.3.1 觸發場景

onSaveInstanceState():瀏覽器

  1. 橫豎屏切換
  2. 按下電源鍵
  3. 按下菜單鍵
  4. 切換到別的 Activity
  5. ....

onRestoreInstanceState():bash

  1. 橫豎屏切換
  2. 切換語言
  3. ....

2. Activity 之間的跳轉

2.1 相關API

2.1.1 startActivity()

怎麼用:

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
複製代碼

使用這個方法就能夠跳轉到 SecondActivity網絡

2.1.2 startActivityforResult()

怎麼用:

MainActivity.java:app

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
複製代碼

這裏第二個參數是一個 requestCode,這個參數會在 onActivityResult 回調回來。ide

SecondActivity.java:ui

setResult(2);
finish();
複製代碼

當 SecondActivity finish 後會回調 MainActivity 中的 onActivityResult 方法:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Log.e("chan", "==================onActivityResult main= " + requestCode + " " + resultCode);
}
複製代碼

打印結果:

E/chan: ==================onActivityResult main= 1 2
複製代碼

固然你也能夠不調用 setResult() 方法,這時回調過來的 resultCode 就是 0。

2.2 顯式啓動

顯示啓動分類:

  1. 直接在 Intent 構造方法啓動:
Intent intent = new Intent(this, SecondActivity.class);
複製代碼
  1. setComponent:
ComponentName componentName = new ComponentName(this, SecondActivity.class);
Intent intent = new Intent();
intent.setComponent(componentName);
複製代碼
  1. setClass / setClassName:
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class);
intent.setClassName(this, "com.example.administrator.myapplication.SecondActivity");
複製代碼

2.3 隱式啓動

隱式啓動就是要在該 Activity 中設置 IntentFilter 屬性,只要啓用的 Intent 匹配 IntentFilter 的條件就能夠啓動相應的 Activity。

要理解隱式啓動就必需要理解 IntentFilter 是如何使用的

2.3.1 IntentFilter 的使用

IntentFilter 有三個標籤分別是:

  1. action
  2. category
  3. data

這三個標籤都有對應的匹配規則,下面會說到。這裏來講下使用 IntentFilter 要注意的地方

  1. 一個 Activity 中能夠有多個 intent-filter
  2. 一個 intent-filter 同時能夠有多個 action,category,data
  3. 一個 Intent 只要能匹配任何一組 intent-filter 便可啓動對應 Activity
  4. 新建的 Activity 必須加上如下這句:
<category android:name="android.intent.category.DEFAULT"/>
複製代碼

不然就會出現以下錯誤:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.chan1 }
複製代碼

2.3.2 action 的匹配規則

action 的匹配規則就是隻要知足其中一個 action 就能夠啓動成功。

在 Manifest 定義一個 SecondActivity:

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.chan" />
        <action android:name="com.chan2" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <intent-filter>
    <action android:name="com.chan3" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
複製代碼

MainActivity:

Intent intent = new Intent();
intent.setAction("com.chan2");
startActivity(intent);
複製代碼

這樣就能夠啓動 SecondActivity,要注意的是 action 是區分大小寫的。

2.3.3 category 匹配規則

category 在代碼設置以下:

intent.addCategory("com.zede");
複製代碼

這句能夠添加也能夠不添加,由於代碼默認會爲咱們匹配 「android.intent. category.DEFAULT」。

2.3.4 data 匹配規則

data 主要是由 URI 和 mimeType 組成的。URI 的結構以下:

<scheme> :// <host> : <port> [<path>|<pathPrefix>|<pathPattern>]
複製代碼

這些值在 Manifest 文件中能夠定義,語法以下:

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:mimeType="string" />
複製代碼

如下用一個例子來講明:

Manifest:

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data
            android:host="zede"
            android:port="1010"
            android:scheme="chan" />
    </intent-filter>
</activity>
複製代碼

MainActivity:

Intent intent = new Intent();
intent.setData(Uri.parse("chan://zede:1010"));
startActivity(intent);
複製代碼

經過這個方法就能夠跳轉到 SecondActivity。

咱們也能夠建立一個 html 文件,來實現跳轉 SecondActivity。

test.html:

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
<a href="chan://zede:1010">跳轉至SecondActivity</a>
</body>
</html>
複製代碼

使用手機瀏覽器打開這個 html 文件,點擊這個超連接也能夠跳轉到 SecondActivity。

經過這個連接也能夠傳輸數據到 SecondActivity,代碼以下:

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
<a href="chan://zede:1010/mypath?user=admin&psd=123456">跳轉至SecondActivity</a>
</body>
</html>
複製代碼

在 SecondActivity 接收數據:

Intent intent = getIntent();
Uri uri = intent.getData();

Log.e("chan", "==================getScheme= " + intent.getScheme());
Log.e("chan", "==================getHost= " + uri.getHost());
Log.e("chan", "==================getPort= " + uri.getPort());
Log.e("chan", "==================getPath= " + uri.getPath());

Log.e("chan", "==================getQuery= " + uri.getQuery());

Set < String > names = uri.getQueryParameterNames();

Iterator < String > iterator = names.iterator();
while (iterator.hasNext()) {
    String key = iterator.next();
    uri.getQueryParameter(key);
    Log.e("chan", "==================getQueryParameter= " + uri.getQueryParameter(key));
}
複製代碼

打印結果:

07-19 10:47:54.969 19201-19201/com.example.administrator.myapplication E/chan: ==================getScheme= chan
07-19 10:47:54.970 19201-19201/com.example.administrator.myapplication E/chan: ==================getHost= zede
    ==================getPort= 1010
    ==================getPath= /mypath
    ==================getQuery= user=admin&psd=123456
    ==================getQueryParameter= admin
    ==================getQueryParameter= 123456
複製代碼

另外還須要注意另外一個屬性:android:mimeType,這個屬性就是說要傳遞什麼類型的數據,一般有 text/plain 或 image/jpeg。

能夠經過如下代碼來啓動 Activity:

intent.setType("text/plain");
複製代碼

不過須要注意的是,若是同時設置了 URI 和 mimeType 的話就必須使用以下代碼才能夠跳轉:

intent.setDataAndType(Uri.parse("chan://zede:1010"), "text/plain");
複製代碼

由於若是使用 setData() 或者 setType() 的話,分別會將相應 type 和 data 置爲 null。

3. Activity的啓動模式

3.1 分類:

啓動模式 做用
standard 每次啓動都會從新建立一個 Activity
singleTop 若是該棧頂上有所要啓動的 Activity,那麼就不會從新建立該 Activity,並會回調 onNewIntent()
singleTask 若是棧內已經有所要啓動的 Activity 就不會被建立,同時也會調用 onNewIntent()
singleInstance 建立該 Activity 系統會建立一個新的任務棧

這裏重點說下 singleTask。

3.2 singleTask 分析

singleTask 叫作棧內複用模式,這個啓動模式的啓動邏輯以下圖:

singleTask 邏輯

相信看了上面這個圖,你們也清楚 singleTask 的邏輯了,可是這個模式還有幾個須要注意的地方。

3.2.1 taskAffinity

前面提到 A 想要的任務棧,那什麼是 A 想要的任務棧呢?這就提到一個屬性 taskAffinity,如下詳細介紹這個屬性。

3.2.1.1 做用

標識一個 Activity 所須要的任務棧的名字。若是不設置這個屬性值,默認值是應用的包名。

3.2.1.2 taskAffinity 與 singleTask 配對使用

若是啓動了設置了這兩個屬性的 Activity,這個 Activity 就會在 taskAffinity 設置的任務棧中,下面用代碼來驗證下:

建立 SecondActvitiy,在 Mnifest 文件設置 SecondActvitiy,代碼以下:

<activity android:name=".SecondActivity"
    android:taskAffinity="com.chan"
    android:launchMode="singleTask" />
複製代碼

如今使用 MainActivity 啓動 SecondActvitiy,這裏的代碼就不展現了,咱們直接看看結果,在終端輸入如下命令:

adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'
複製代碼

這個命令能夠查看正在運行的 Activity,結果以下:

Running activities (most recent first):
    TaskRecord{762a040 #63 A=com.chan U=0 StackId=1 sz=1}
    Run #1: ActivityRecord{3881f68 u0 com.example.activitydemo/.SecondActivity t63}
    TaskRecord{351eb79 #62 A=com.example.activitydemo U=0 StackId=1 sz=1}
複製代碼

從打印結果能夠看出, MainActivity 和 SecondActivity 運行在不一樣的任務棧中。

3.2.1.3 taskAffinity 和 allowTaskReparenting 配對使用

allowTaskReparenting 這個屬性直接解釋的話,可能不少人都會聽得懵逼,下面直接使用例子來解釋:

如今建立兩個應用,一個應用的包名爲:com.example.activitydemo,如下成爲應用 A。另外一個應用的包名爲:com.example.hellodemo,如下稱爲應用 B。

在應用 A 的 MainActivtiy 中添加以下代碼:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    findViewById(R.id.start1).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        Intent intent=new Intent();
        intent.setClassName("com.example.hellodemo", "com.example.hellodemo.HelloActivity");
        startActivity(intent);
        }
    });

}
複製代碼

應用 B 中建立 HelloActivity,並在 Manifest 文件中設置 HelloActivity 的 allowTaskReparenting 爲 true,代碼以下:

<activity android:name=".HelloActivity"
    android:exported="true"
    android:allowTaskReparenting="true" />
複製代碼

而後根據如下步驟操做:

  1. 如今先運行應用 B,而後退出
  2. 再運行應用 A,點擊按鈕啓動
  3. 按 home 鍵,回到主界面
  4. 啓動應用 B

完成第二步的時候,在終端看下任務棧的狀況:

Running activities (most recent first):
    TaskRecord{5d54c1c #85 A=com.example.activitydemo U=0 StackId=1 sz=2}
    Run #1: ActivityRecord{ff0b8e u0 com.example.hellodemo/.HelloActivity t85}
    Run #0: ActivityRecord{95ee35c u0 com.example.activitydemo/.MainActivity t85}
複製代碼

能夠看出 HelloActivity 運行在應用 A 的任務棧中。

完成第四步後,再看下任務棧:

Running activities (most recent first):
    TaskRecord{74c894d #86 A=com.example.hellodemo U=0 StackId=1 sz=2}
        Run #1: ActivityRecord{ff0b8e u0 com.example.hellodemo/.HelloActivity t86}
    TaskRecord{5d54c1c #85 A=com.example.activitydemo U=0 StackId=1 sz=1}
        Run #0: ActivityRecord{95ee35c u0 com.example.activitydemo/.MainActivity t85}
複製代碼

從結果能夠看到,HelloActivity 從應用 A 的任務棧移動到應用 B 的任務棧。

如今再修改下 HelloActivity 的 taskAffinity 屬性,代碼以下:

<activity android:name=".HelloActivity"
    android:exported="true"
    android:allowTaskReparenting="true"
    android:taskAffinity="com.chan"/>
複製代碼

從新根據以上步驟操做,操做完畢後看下任務棧信息:

Running activities (most recent first):
    TaskRecord{50264fe #90 A=com.example.hellodemo U=0 StackId=1 sz=1}
    Run #2: ActivityRecord{bc77713 u0 com.example.hellodemo/.MainActivity t90}
    TaskRecord{41abf9e #89 A=com.example.activitydemo U=0 StackId=1 sz=2}
    Run #1: ActivityRecord{2d0b7bb u0 com.example.hellodemo/.HelloActivity t89}
    Run #0: ActivityRecord{8b57551 u0 com.example.activitydemo/.MainActivity t89}
複製代碼

能夠看出 HelloActivity 並無移動到應用 B 的主任務棧中,由於這並非 HelloActivity 想要的任務棧。

繼續修改 HelloActivity 配置屬性,增長 singleTask 屬性:

<activity android:name=".HelloActivity"
    android:exported="true"
    android:allowTaskReparenting="true"
    android:taskAffinity="com.chan"
    android:launchMode="singleTask"/>
複製代碼

繼續操做,任務棧結果以下:

Running activities (most recent first):
    TaskRecord{775e709 #95 A=com.example.hellodemo U=0 StackId=1 sz=1}
        Run #2: ActivityRecord{757bb47 u0 com.example.hellodemo/.MainActivity t95}
    TaskRecord{aa75b2 #94 A=com.chan U=0 StackId=1 sz=1}
        Run #1: ActivityRecord{76e2133 u0 com.example.hellodemo/.HelloActivity t94}
    TaskRecord{21c8903 #93 A=com.example.activitydemo U=0 StackId=1 sz=1}
        Run #0: ActivityRecord{be84df4 u0 com.example.activitydemo/.MainActivity t93}
複製代碼

能夠看出與沒有增長 singleTask 屬性的結果是同樣的,其實 allowTaskReparenting 這個屬性的最主要做用就是將這個 Activity 轉移到它所屬的任務棧中,例如一個短信應用收到一條帶網絡連接的短信,點擊連接會跳轉到瀏覽器中,這時候若是 allowTaskReparenting 設置爲 true 的話,打開瀏覽器應用就會直接顯示剛纔打開的網頁頁面,而打開短信應用後這個瀏覽器界面就會消失。

3.3 指定啓動模式的方式

指定啓動模式的方式有兩種,一種是在 AndroidMenifest 文件設置 launchMode 屬性,另外一種就是在 Intent 當中設置標誌位。第二種方式的優先級會比第一種的要高,若是兩種都設置了會以第二種方式爲準。咱們來驗證一下:

在 MainActivity 設置以下代碼:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.start1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, SecondActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        });

    }
複製代碼

SecondActivity 在 AndroidMenifest 設置以下:

<activity android:name=".SecondActivity"
            android:taskAffinity="com.chan" />
複製代碼

這裏我並無設置 SecondActivity 爲 sigleTask,來驗證下啓動 SecondActivity 是否會開啓一個新的任務棧。

運行後,任務棧的結果爲:

Running activities (most recent first):
      TaskRecord{148d7c5 #143 A=com.chan U=0 StackId=1 sz=1}
        Run #2: ActivityRecord{de59b2d u0 com.example.activitydemo/.SecondActivity t143}
      TaskRecord{520151a #142 A=com.example.activitydemo U=0 StackId=1 sz=1}
        Run #1: ActivityRecord{d80bfc1 u0 com.example.activitydemo/.MainActivity t142}
複製代碼

從結果能夠看出,是開啓了一個新的任務棧的,也證實了第二種方式的優先級比較高

3.4 onNewIntent 回調時機

啓動 singleTask 的 Activity 的時候會回調 onNewIntent() 方法,可是並非全部狀況都這樣,總結以下圖:

onNewIntent 回調時機

如下使用代碼來驗證一下這四種狀況:

3.4.1 不存在 A 所需的任務棧

代碼以下:

MainActivity.java:

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.start1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, SecondActivity.class));
            }
        });

    }


    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.e("chan", "MainActivity=======================onNewIntent");
    }


}
複製代碼

SecondActivity.java:

public class SecondActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        findViewById(R.id.start2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SecondActivity.this, ThirdActivity.class));
            }
        });

    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.e("chan", "SecondActivity=======================onNewIntent");
    }
}
複製代碼

清單文件中將 SecondActivity 設置爲 singleTask,taskAffinity 屬性設置一個非該程序包名的值,代碼以下:

<activity android:name=".SecondActivity"
            android:taskAffinity="com.onnewintent"
            android:launchMode="singleTask" />
複製代碼

以上代碼的結果並無打印任何東西,證實這樣並不會回調 onNewIntent()。

3.4.2 存在 A 所需的任務棧

這種狀況還要分兩種子狀況,一種就是 A 不在棧中,另外一種 A 不在棧中。

3.4.2.1 A 不在棧中

仍是用回上面的例子的代碼,添加一個 ThridActivity,ThridActivity 在清單文件描述以下:

<activity android:name=".ThirdActivity"
            android:taskAffinity="com.onnewintent"
            android:launchMode="singleTask" />

複製代碼

點擊 SecondActivity 跳轉按鈕後一樣也不會有任何打印,證實並不會回調 onNewIntent()。

3.4.2.2 A 在棧中

這種狀況也會分兩種子狀況,一種就是 A 在棧頂,另外一種就是 A 不在棧頂。

3.4.2.2.1 A 在棧頂

一樣也是使用上面的例子,修改 ThirdActivity,代碼以下:

public class ThirdActivity extends AppCompatActivity {



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
        findViewById(R.id.start3).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(ThirdActivity.this, ThirdActivity.class));
            }
        });

    }


    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.e("chan", "ThirdActivity=======================onNewIntent");
    }


}
複製代碼

到 ThirdActivity 時,ThirdActivity 已是在棧頂了,這時候點擊按鈕再次啓動 ThirdActivity,打印結果以下:

chan    : ThirdActivity=======================onNewIntent
複製代碼

能夠發現這種狀況會回調 ThirdActivity 的 onNewIntent。

3.4.2.2.2 A 不在在棧頂

一樣改動 ThirdActivity,此次啓動的是 SecondActivity。具體代碼就不寫了,打印結果以下:

chan    : SecondActivity=======================onNewIntent
複製代碼

能夠發現這種狀況會回調 SecondActivity 的 onNewIntent。

3.5 Activity 中的 Flags

3.5.1 FLAG_ACTIVITY_NEW_TASK

與啓動模式 singleTask 的做用同樣,必須設置 taskAffinity

3.5.2 FLAG_ACTIVITY_SINGLE_TOP

與啓動模式 singleTop 的做用同樣

3.5.3 FLAG_ACTIVITY_CLEAR_TOP

從名字就能夠看出這個標誌的做用就是若是這個 Activity 頂部有別的 Activity 的話,那麼就會它頂部的 Activity 所有出棧,若是這個 Activity 的啓動模式爲 standard 的話,就會將以前的 Activity 出棧,而且建立一個新的 Activity,若是不是的話就會調用 onNewIntent 方法。

3.5.4 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

不能在查看歷史 Activity 中查看到此 Activity

相關文章
相關標籤/搜索