Android技能樹 — Activity小結

青蛙要fly發表於2018-02-05

前言

最近年底了,打算把自己的Android知識都整理一下。

Android技能書系列:

Android基礎知識

Android技能樹 — 動畫小結

Android技能樹 — View小結

Android技能樹 — Activity小結

Android技能樹 — View事件體系小結

Android技能樹 — Android儲存路徑及IO操作小結

Android技能樹 — 多程式相關小結

Android技能樹 — Drawable小結

資料結構基礎知識

Android技能樹 — 陣列,連結串列,雜湊表基礎小結

Android技能樹 — 樹基礎知識小結(一)

演算法基礎知識

Android技能樹 — 排序演算法基礎小結

這次是講Activity的相關內容。還是老樣子,先上腦圖,然後具體一塊塊詳細說明。

Activity腦圖連結

Android技能樹 — Activity小結
Activity

Android技能樹 — Activity小結

Activity生命週期

我估計如果面試面試官問你Activity的生命週期報一遍看,你心裡一定暗罵mmp。因為這種一般菜鳥都知道有哪些常用的,雖然可能有些不常用的,不清楚也是很正常,而且面試官也就是想知道幾個常用的而已。

詳細的生命週期可以看我這篇文章:超詳細的生命週期圖-你能回答全嗎

Android技能樹 — Activity小結

正常生命週期

一般來說我們講的生命週期就是下面幾個:

Android技能樹 — Activity小結

其中主要提下(onStart - onStop),(onResume - onPause)這二對。

問題1:

我們一般app裡面啟動一個新的Activity後,onStart ——> onResume,都會執行,那什麼時候會執行onStart,什麼時候接著執行onResume呢?而我們啟動另外一個新的Activity,或者按了Home鍵回到了桌面,這時候會執行onPause ——> onStop,那什麼時候會執行onPause呢?什麼時候會執行onStop呢?
我們可以這麼記:Activity啟動後,我們會看到介面,然後可以點選介面上的按鈕,這時候是不是分成了二大塊:

  1. 看得到我們寫Activity的介面。
  2. 然後可以操作我們的介面。

所以(onStart - onStop)和介面的可見與否有關, (onResume - onPause)和介面是否可以操作有關。

舉個簡單例子,比如我們在Activity上有個按鈕A,,這時候啟動一個彈框或者啟動一個新的透明的Activity,這個按鈕A肯定是不能按的,但是我們可以看到A這個按鈕,這時候onStop不會執行,但會執行onPause ,因為我們已經不能點選這個按鈕了,但是我們能看到這個按鈕。

問題2:

如果我們從A 這個Activity ,跳到了 B 這個Activity,那二個Activity的(onStart - onStop)和(onResume - onPause)又分別如何執行。

大家如果要實驗,只要在相應的生命週期處打上Log即可。結果是這樣的:
A(onPause) -> B(onCreate)->B(onStart) —> B(onResume) -> A(onStop)

所以,如果你在從一個Activity跳轉到另外一個Activity之前,要做一些操作的話,最好是放在onStop中,因為如果放在onPause中的話,會影響新的Activity啟動速度。

異常生命週期

Android技能樹 — Activity小結

我們看腦圖就知道,在異常生命週期中,我們會額外執行二個方法:onSaveInstanceState(Bundle outState)onRestoreInstanceState(Bundle savedInstanceState),看字面意思就知道了。在Activity銷燬的時候先通過onSaveInstanceState的Bundle引數裡面,儲存一些內容,然後在重建時候呼叫onRestoreInstanceState方法傳遞剛才那個Bundle,然後我們可以取出我們剛存的東西。

我們先來看什麼時候會出現異常生命週期:

  1. 資源相關的系統配置改變(最常見的就是旋轉螢幕)
  2. 記憶體不足時候,會殺死優先順序低的Activity

我們來分別檢視:

旋轉螢幕

最簡單的還是用例子來說明,我們寫了一個Activity,裡面有一個EditText:

public class SaveActivity extends AppCompatActivity {

    TextView a;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_save);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("data", "儲存的資料");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.v("dyp", savedInstanceState.getString("data"));
    }

}
複製程式碼

我們旋轉手機螢幕,在onSaveInstanceState中,在bundle裡面存了個字串,然後Activity重建後會在onRestoreInstanceState中的bundle中可以拿到我們存的字串。

列印的內容:
V/dyp: 儲存的資料
複製程式碼

有人會問了。我發現一個現象,就是比如我們的Activity裡面有個EditText,這時候我在裡面輸入了123456,我也沒在onSaveInstanceStateonRestoreInstanceState裡面做特殊處理,但是手機螢幕轉過來後,我的EditText還是能顯示123456。我們具體來分析下這個現象。

Android技能樹 — Activity小結
豎屏

Android技能樹 — Activity小結
橫屏

我們知道在重寫onSaveInstanceStateonRestoreInstanceState的時候,預設程式碼是這樣的:

@Override
protected void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
}
複製程式碼

既然我們沒有做額外處理,那說明關鍵點就在super.onSaveInstanceState(outState);super.onRestoreInstanceState(savedInstanceState);這二句話了。
簡單來說:super.onSaveInstanceState(outState);它會幫我們儲存Activity的相關檢視,然後分別呼叫每個View的onSaveInstanceState方法,比如EditText在自己的類中的這個方法就是儲存了輸入的內容。然後在super.onRestoreInstanceState(savedInstanceState);方法中會再呼叫每個View的onRestoreInstanceState方法,我們的EditText就是會呼叫自己的這個方法,然後再把儲存好的內容再賦值進去。所以我們如果想知道某一個具體的View系統能自動幫我們恢復哪些資料,我們可以檢視這個View的onSaveInstanceStateonRestoreInstanceState程式碼(比如ListView會自動恢復滾動位置等)。

Activity的優先順序

記憶體不足時候殺死優先順序低的Activity,這時候的資料儲存和恢復過程和我們上面講的也是一樣的。

那Activity的具體的優先順序怎麼樣的呢:

  1. 前臺Activity - 正在和使用者互動的Activity,優先順序最高。
  2. 可見但不能操作的Activity - 比如我們上面說的彈出彈框等情況。
  3. 後臺Activity - 比如執行了onStop的activity。

我們可以看到後臺Activity很容易被殺死,所以一些後臺工作更適合放到Service中去,這樣保證優先順序。不會輕易被系統殺死。

生命週期切換過程

當然一般我們也只要知道幾個常用的切換過程即可,貼上網上別的文章經常用到的圖片:

Android技能樹 — Activity小結
生命週期切換圖


Android技能樹 — Activity小結

Activity啟動方式

Android技能樹 — Activity小結

其實關於啟動方式的,文章真的太多太多了,我推薦一下這二篇文章,方便大家弄懂。

基礎知識:
徹底弄懂Activity四大啟動模式

這裡推薦一篇進階版的啟動模式的文章:
Android面試官裝逼失敗之:Activity的啟動模式

好吧,其實就是我偷懶了。不想長篇大論的寫啟動方式了。。。哈哈

Android技能樹 — Activity小結

啟動Activity

Android技能樹 — Activity小結

Activity的啟動可以分為顯式呼叫和隱式呼叫二種。

顯式呼叫啟動Activity

這個是我們最常見的方式了。直接寫上目標的Activity的名字,然後startActivity或者startActivityForResult來啟動。

一般的程式碼是這樣的(比如從MainActivity啟動了TargetActivity):

Intent intent = new Intent();
intent.setClass(MainActivity.this,TargetActivity.class);
startActivity(intent);
複製程式碼

隱式呼叫啟動Activity

大家可能會想,一般都是上面那種啟動方式來啟動的,那這種隱式呼叫有啥用。

比如我們現在需要點選按鈕,進入到撥打我們APP客服電話。我們總不可能讓使用者每次都背下來號碼,然後手動開啟電話那裡去按。
我們當前Activity上有一個按鈕,並且設定按鈕的點選事件為:

Button btn = findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Uri data = Uri.parse("tel:10086");
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        intent.setData(data);
        startActivity(intent);
    }
});
複製程式碼

這時候,你點選了這個按鈕,我們呼叫startActivity方法,就會自動跳到撥號介面。

Android技能樹 — Activity小結
可是我們並沒有設定Intent指向了具體的某個XXX名字的Activity。但是還是開啟了撥號介面的這個Activity,因為我們是隱式呼叫,並且設定了規則。只要規則匹配上,就會呼叫。

不過如果我們設定的規則有多個Activity都匹配,則會出現選擇框,讓你進行選擇。
比如說我們在app中開啟一個網址,

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri data=Uri.parse("http://www.baidu.com");
intent.setData(data);                
startActivity(intent);
複製程式碼

這時候一般都會跳出這麼一個介面:

Android技能樹 — Activity小結

那具體的匹配規則是怎麼樣呢,我們可以看到我們上面有這二行程式碼:

intent.setAction(Intent.ACTION_VIEW);
intent.setData(data);    
複製程式碼

所以actiondata一定是匹配規則,同時其實還有另外一個category

好,我們回頭來看,我們如何設定一個Activity的規則,然後讓其他Activity通過隱式呼叫來啟動自己,就是在AndroidManifest.xml中進行設定<intent-filter>標籤,我們還記不記得我們設定的一個APP的啟動Activity,是不是也用的隱式呼叫。

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
複製程式碼

PS:在這裡我們看到了category了。

所以我們只需要在AndroidManifest.xml中新增<intent-filter>標籤,然後加入相應的action,category,data等過濾條件,只要符合了,就會啟動相應的Activity。

其中具體的匹配規則,上面的腦圖也已經寫出來了:

Android技能樹 — Activity小結

注意點

避免隱式呼叫時候找不到Activity產生的報錯

採用相關方法,提前判斷是否有相匹配的Activity。

Android技能樹 — Activity小結

隱式呼叫中category的注意點

Android技能樹 — Activity小結

比如我們在AndroidManifest.xml中這麼寫的:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="dyp"/>
    </intent-filter>
</activity>
複製程式碼

我們只設定了action的過濾條件,然後我們在其他activity中想啟動這個MainActivity。寫了如下程式碼:

Intent intent = new Intent();
intent.setAction("dyp");
startActivity(intent);
複製程式碼

你會發現,會報異常:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=dyp }
複製程式碼

會提示找不到,為什麼會這樣,因為我們在呼叫startActivity或者startActivityForResult的時候會預設幫我們的Intent加上一個category,也就是intent.addcategory("android.intent.category.DEFAULT");所以如果你的activity在AndroidManifest.xml中的<intent-filter>沒有新增這個category,就會無法成功匹配。

所以我們這裡要改成這樣:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="dyp"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
複製程式碼

隱式呼叫中data的注意點

和上面的category比較類似,所以也不具體些例子了。大家看腦圖即可。

Android技能樹 — Activity小結

結語

圖片代表我的心。。。有啥寫錯的,歡迎吐槽留言。。哈哈。

Android技能樹 — Activity小結

相關文章