1. 程式人生 > >Android Activity 完全解析(中)

Android Activity 完全解析(中)

      在上一篇文章中我們學習了 Activity 的生命週期以及用 Intent 開啟系統的方法相關方面的知識,分析了單個 Activity 生命週期從建立到銷燬的過程,以及多個 Activity 之間互相跳轉的生命週期的建立過程,如果你還不是很清楚,可以先閱讀 Android Activity完全解析(上),今天我們接著來學習一下 Activity 其它的相關方面的知識,為了讓大家看起來一目瞭然今天分享的內容,繪製如下腦圖:

一、Activity 之間的資料互動

1)簡單的資料傳遞

        在我們的專案開發過程中經常需要從上一頁向下一頁傳遞資料,以減少再請求流量的損耗,下面我們來看一下簡單資料的傳遞,首先我們建立一個MainActivity,並實現一個 onClick 方法,用於點選跳轉,然後再建立一個 ThirdActivity,在 MainActivity 中傳送資料,在 ThirdActivity 中接受資料,程式碼如下:

MainActivity 中通過 onClick 方法傳送資料:

 @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this,ThirdActivity.class);
        intent.putExtra("name","name");
        intent.putExtra("age","age");
        startActivity(intent);
    }
ThirdActivity 中接受資料:這裡沒什麼需要多說的,相信大家一看就懂
稍微需要注意的就是對 Intent 進行判 null 處理
/**
 * 資料傳遞測試
 */

public class ThirdActivity extends AppCompatActivity {

    private static final String TAG = "ThirdActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();
        if (intent != null) {
            String name =intent.getStringExtra("name");
            String age = intent.getStringExtra("age");
            Log.i(TAG, "name="+name);
            Log.i(TAG, "age="+age);
        }
    }
}

這樣我們就完成了簡單的資料傳遞,我們看一下控制檯 Log 的列印:

03-12 19:28:16.834 22772-22772/com.example.qiudengjiao.activitytest I/ThirdActivity: name=name
03-12 19:28:16.834 22772-22772/com.example.qiudengjiao.activitytest I/ThirdActivity: age=age

可以看到,我們的資料成功傳遞過來

2)傳遞 Bundle 物件

同樣我們還是在 MainActivity 中傳送資料:

@Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this,ThirdActivity.class);

        //這裡傳遞Bundle物件
        Bundle bundle = new Bundle();
        bundle.putString("name","姓名");
        bundle.putString("age","0");
        intent.putExtras(bundle);

        startActivity(intent);
    }
在 ThirdActivity 中接受資料:這裡接受資料和1)的接受方法是一樣的,不用修改
/**
 * 資料傳遞測試
 */

public class ThirdActivity extends AppCompatActivity {

    private static final String TAG = "ThirdActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_third);
        TextView textView = (TextView) findViewById(R.id.tv_textView);

        Intent intent = getIntent();
        if (intent != null) {
            String name = intent.getStringExtra("name");
            String age = intent.getStringExtra("age");
            Log.i(TAG, "name=" + name);
            Log.i(TAG, "age=" + age);
            textView.setText(name + age);
        }
    }
}


3)傳遞 JavaBean

當我們傳遞的資料比較大的時候,這時候我們可以直接傳遞一個 JavaBean,這裡注意要實現 Serializable 介面

首先我們先建立一個 Person 類, 並生成它的構造方法和 toString 方法,程式碼如下:

/**
 * Person實體類
 */

public class Person implements Serializable {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

接下來我們開始在 MainActivity 中去傳送資料,程式碼如下:
  public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this, ThirdActivity.class);

        Person person = new Person("姓名", 0);
        Bundle bundle = new Bundle();
        bundle.putSerializable("person", person);
        intent.putExtras(bundle);

        startActivity(intent);
    }

ThirdActivity 中的程式碼和上面的不太一樣,寫法如下:
 Intent intent = getIntent();
        if (intent != null) {
            Person person = (Person) intent.getSerializableExtra("person");
            Log.i(TAG, "person=" + person);
        }

這裡我們也成功接收到了傳遞過來的資料,控制檯列印情況如下:

03-12 20:48:25.021 5314-5314/com.example.qiudengjiao.activitytest I/ThirdActivity: person=Person{name='姓名', age=0}

4)傳遞 BitMap 物件(這裡注意不能太大)

我們還是按照上面的慣例,先在 MainActivity 中傳送資料:

@Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this, ThirdActivity.class);

        Bundle bundle = new Bundle();
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
        bundle.putParcelable("bitmap",bitmap);
        intent.putExtras(bundle);

        startActivity(intent);
    }

在 ThirdActivity 中接收:這樣我們就可以接收到傳遞的 BitMap 物件

/**
 * 資料傳遞測試
 */

public class ThirdActivity extends AppCompatActivity {

    private static final String TAG = "ThirdActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_third);
        TextView textView = (TextView) findViewById(R.id.tv_textView);

        Intent intent = getIntent();
        if (intent != null) {
            Parcelable bitmap= intent.getParcelableExtra("bitmap");
        }
    }
}

好,常見的 Activity 之間的資料傳遞我們就一起探討完了,接下來我們來看一下 Activity 之間傳遞大資料發生的問題

二、Activity 傳遞大資料的時候遇到的問題

首先我們還是在 MainActivity 的點選事件裡寫下如下程式碼:

這裡我們試著來傳遞一些大一點的資料,看有什麼效果

   @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this, ThirdActivity.class);

        Bundle bundle = new Bundle();
        int[] data = new int[1024*1024*8];
        bundle.putIntArray("name",data);
        intent.putExtras(bundle);

        startActivity(intent);
    }

在 ThirdActivity 中接收程式碼如下:
 Intent intent = getIntent();
        if (intent != null) {
            int[] data = intent.getIntArrayExtra("name");
            Log.i(TAG, "data=" + data);
        }

我們點選按鈕跳轉,發現程式崩潰,我們來看一下控制檯列印的日誌:

Caused by: android.os.TransactionTooLargeException: data parcel size 33554940 bytes

TransactionTooLargeException:我們看到報了交易資料太大的異常資訊,這就說明我們資料之間的資料是不能太大的,這裡建議不要超過 0.5 M,還望看到這樣的提示異常,大家可以知道該去如何解決

三、任務和返回棧

1)Tasks 和 back stack

       任務是指在執行特定作業時與使用者進行互動的一些列 Activity,這些 Activity 按照各自的開啟順序排列在堆疊(即返回棧)中,裝置的主螢幕是大多數任務的起點,當用戶觸控到應用啟動器中的圖示(或螢幕上的快捷方式),該應用的任務出現在前臺,如果應用不存在任務(應用最近未曾使用),則會建立一個新任務,並且該應用的“主” Activity 將作為堆疊中的根 Activity 開啟。

       當前 Activity 啟動另一個 Activity 時,該新的 Activity 會被推送到棧頂,成為焦點所在,前一個 Activity 任然保留在堆疊中,但是處於停滯狀態,Activity 停滯時,系統會保留其使用者介面的當前狀態,使用者按“返回”按鈕時,當前 Activity 會從堆疊頂部彈出( Activity 被銷燬),而前一個 Activity 恢復執行(恢復其UI 的前一狀態),堆疊中的 Activity 永遠不會重新排列,僅推入和彈出堆疊:由當前的 Activity 啟動時推入堆疊,使用者返回按鈕時彈出堆疊,因此返回棧以“後進先出”物件結構執行,下圖通過時間線顯示 Activity 之間的進度以及每個時間點的當前返回棧,形象的呈現了這種行為:


        如果使用者繼續按"返回",堆疊中的相應 Activity 就會彈出,以顯示前一個 Activity,直到使用者返回主螢幕為止,當所有 Activity 均從堆疊中移除後,任務不復存在

Activity 和任務的預設行為總結如下:

  • 當前 Activity A 啟動 Activity B 時,Activity A 將會停止,但系統會保留其狀態,如果使用者處在 Activity B 時按“返回”按鈕,則 Activity A 將恢復其狀態,繼續執行
  • 使用者按“主頁”按鈕離開任務時,當前 Activity 將停止,其任務會進入後臺,系統將保留任務中每個 Activity 狀態,如果使用者稍後通過選擇開始任務的啟動器圖示來恢復任務,則當前任務出現在前臺,並恢復執行堆疊頂部的 Activity
  • 如果使用者按“返回”按鈕,則當前的 Activity 會被銷燬,堆疊中的前一個 Activity 恢復執行,被銷燬的 Activity 不會保留該 Activity 的狀態
  • 即使來自其他任務,Activity 也可以多次例項化
2)adb 檢視 Activity 的命令 我們可以使用 adb 命令來檢視 Activity 的建立情況,這樣可以很清楚的看到我們開啟的每個 Activity 的情況,這裡就不演示了,希望大家可以去親自看一下 命令為:adb shell dumpsys activity

3) LaunchMode

      接下來我們來看比較重要的 Activity 的啟動模式,Activity 的啟動模式在面試中也是經常遇到的,即便面試問不到,我們也需要好好去搞明白,不說廢話了,我們開始來說 Activity 的啟動模式:

standard:

       標準模式,也是系統的預設模式,每次啟動一個 Activity 都會重新建立一個新的例項,不管這個例項是否存在,被建立的例項的生命週期符合我們之前所說的正常情況下的生命週期的過程

singleTop:

       棧頂複用模式,在這種模式下,如果新 Activity 已經位於任務棧的棧頂,那麼此 Activity 不會被重新建立,同時它的 onNewIntent 方法會被呼叫,通過此方法的引數,我們可以取出當前的請求資訊,需要注意的是這個 Activity 的 onCreate、onStart 不會被系統呼叫,因為它並沒有發生改變,如果新的 Activity 已經存在,但是並沒有處在棧頂,那麼這個 Activity 任然會被建立,這裡舉個例子,假設目前棧內的情況是 ABCD,其中 ABCD 為 4 個 Activity,A位於棧低,D 位於棧頂,這個時候要再次啟動 D,如果 D 的啟動模式為 singleTop, 那麼棧內的情況仍然為 ABCD,如果 D 的啟動模式為 standard,那麼由於 D 被重新建立,導致棧內的情況就變為 ABCDD

singleTask:

        棧內複用模式,這是一種單例項模式,在這種模式下,只要 Activity 在一個棧中存在,那麼多次啟動此 Activity 都不會再重新建立例項,和 singleTop 一樣,系統也會回撥它的 onNewIntent 方法,具體一點,當一個 Activity 的啟動模式是 singleTask 模式時,比如 Activity A,系統會首先去找是否存在 A 想要的任務棧,如果不存在,就重新建立一個任務棧,然後建立 A 的例項並放到棧中,如果存在 A 所需要的任務棧,這時要看 A 例項在棧中是否存在,如果不存在,就建立 A 例項,如果存在,就把 A 調到棧頂,並呼叫 onNewIntent 方法,下面舉個例子:

1)比如目前任務棧 S1 的情況為 ABC,這個時候 D 以 singleTask 的方式請求啟動,它所需要的任務棧為 S2,由於 S2 和 D 都不存在,所以會先建立S2,然後建立 D 的例項並放入棧中

2)另一種情況,假如 D 所需要的任務棧為 S1,其他情況如上,那麼由於 S1 已經存在,系統就會直接建立 D 的例項並將其入棧到 S1

3)如果 D 所需要的任務棧為 S1,並且當前任務棧的情況為 ADBC,根據棧內複用的原則,系統會把 D 切換到棧頂並呼叫其 onNewIntent 方法,同時singleTask 模式具有 clearTop 的效果,會導致棧內所有在 D 上面的 Activity 全部出棧,即全部銷燬,於是最後的情況為 AD

singleInstance:

單例項模式,這是一種加強的 singleTask 模式,它除了具有 singleTask 的全部屬性外,那就是這種模式的 Activity 只能單獨的位於一個任務棧中