Android四大組件之Activity入門篇

??Activity在Android APP中負責與用戶的交互,是APP界面功能的載體和入口。一個APP中可以存在多個Activity掩缓,每個Activity就是一個交互界面。它們可以運行在一個進程中遵岩,也可以運行在不同的進程中你辣。那么Android系統(tǒng)是怎么管理這些Activity的呢?Activity是怎么被啟動的尘执?它的生命周期又是怎樣的呢舍哄?

目錄

  1. Activity的管理-任務棧
  2. Activity的生命周期
    2-1 正常情況下的生命周期
    2-2 異常情況下的生命周期
    2-3 Activity生命周期流程圖
  3. Activity的啟動模式
    3-1 standard模式
    3-2 singleTop模式
    3-3 singleTask模式
    3-4 singleInstance模式
    3-5 啟動模式與startActivityForResult

一、Activity的管理--任務棧

??每個Activity的實例都是保存在任務棧中誊锭,這個任務棧是一個堆棧表悬,滿足先進后出的特點,由Android系統(tǒng)創(chuàng)建丧靡。因此蟆沫,它的特性如下:
a、保存Activity實例
b温治、棧名由屬性taskAffinity屬性指定饭庞,默認情況下就是包名
c、不依附于APP罐盔,依附于系統(tǒng)但绕。因此妨蛹,它可以被多個進程共享
d症副、因為存在多個任務棧,所以位于棧頂?shù)腁ctivity實例不一定可見椿浓,但可見的Activity一定存在棧頂纬黎。

??前面兩點容易理解幅骄,可這第三點怎么來驗證呢?我們可以寫兩個簡單的demo本今,指定的棧名一樣拆座,然后兩個demo分別打印出任務棧的taskId主巍,如果相同,這說明任務棧是可以被多個進程共享的挪凑。
Demo的代碼如下:

public class BaseActivity extends AppCompatActivity{
  ....
    protected void dumpTaskAffinity(){
        try {
            ActivityInfo info = this.getPackageManager()
                    .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Log.i(this.getClass().getSimpleName(), "current activity hascode = " + this.hashCode() + " taskId = " + getTaskId() + " taskAffinity = "+info.taskAffinity + " taskHasCode = " + info.hashCode());
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
    }
}
//AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:taskAffinity="com.carol.practice.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

注意:android:taskAffinity指定的名字必須是xxx.xxx的形式孕索,如果寫成android:taskAffinity=“xxx”這樣的形式,在安裝時會提示錯誤:


taskAffinity取名錯誤時的安裝提示.png

demo1的代碼如下:

public class Demo1BaseActivity extends AppCompatActivity{
   ...
    protected void dumpTaskAffinity(){
        try {
            ActivityInfo info = this.getPackageManager()
                    .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Log.i(this.getClass().getSimpleName(), "current activity hascode = " + this.hashCode() + " taskId = " + getTaskId() + " taskAffinity = "+info.taskAffinity + " taskHasCode = " + info.hashCode());
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

public class Demo1MainActivity extends Demo1BaseActivity {

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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest1">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Demo1MainActivity"
            android:taskAffinity="com.carol.practice.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

可以看下兩個demo幾乎是一樣的躏碳,只是Activity的名字不同罷了搞旭。這個時候我們分別運行,他們的輸出結果如下:


demo的輸出結果.png

demo1的輸出結果.png

??對比我們可以看出菇绵,兩個APP的棧ID都是19肄渗,棧名也是一樣的。這說明任務棧確實不是APP創(chuàng)建的而是由系統(tǒng)創(chuàng)建的咬最,并且可以被多個APP共享翎嫡。
??通過對比,我們發(fā)現(xiàn)兩個APP的Activity實例的hascode值也是一樣的永乌,這是不是說明他們用的是同一個實例呢惑申?其實,hascode并不能用來表明實例的地址铆遭,兩個相同的hascode值不能說明是同一個實例硝桩,但可以通過hascode值的不等來判斷不是同一個實例。

二枚荣、Activity的生命周期

??2-1 正常情況下的生命周期

??一個Acitvity從創(chuàng)建到銷毀的過程就是它的一次完整的生命周期碗脊。對應的生命周期函數(shù)依次是onCreate、onStart橄妆、onResume衙伶、onPause、onStop害碾、onDestory矢劲,在一次生命周期中,Activity表現(xiàn)為四種狀態(tài)慌随,分別是運行狀態(tài)芬沉、暫停狀態(tài)、停止狀態(tài)和銷毀狀態(tài)阁猜。那怎么來區(qū)分當前Activity當前處在哪個狀態(tài)呢丸逸,這個就跟前面講的任務棧有關系了。

??2-1-1剃袍、運行狀態(tài)

??Activity實例位于棧頂并可見并能與用戶交互黄刚,對應的生命周期函數(shù)是onResume。執(zhí)行onStart時民效,Activity可見憔维,但是還無法和用戶進行交互涛救,當執(zhí)行完onResume函數(shù)之后才可以和用戶進行交互。這種狀態(tài)下的Activity優(yōu)先級最高业扒,異常情況下检吆,系統(tǒng)最不愿意回收這種Activity。

??2-1-2凶赁、暫停狀態(tài)

??Activity實例不在棧頂?shù)强梢娺掷酰瑢纳芷诤瘮?shù)是onPause。一般情況下虱肄,我們在做Activity切換時這個狀態(tài)時很短暫的,因為第二個Activity會馬上覆蓋掉之前的Activity交煞,是之前的Activity不可見咏窿。但如果你要切換到的Activity不是占滿屏幕類似對話框(不是Dialog)這樣的Activity時就會處一直處在這個狀態(tài),直到發(fā)生下一次切換素征。從暫停狀態(tài)回到運行狀態(tài)會直接執(zhí)行onResume函數(shù)集嵌。這種狀態(tài)下的Activity優(yōu)先級較高,異常情況下御毅,如果存在更低優(yōu)先級的Activity根欧,系統(tǒng)也不會回收它。

??2-1-3端蛆、停止狀態(tài)

??Activity實例不在棧頂且不可見凤粗,對應的生命周期函數(shù)是onStop。此時要切換到的Activity會完全覆蓋掉之前的Activity今豆。這種狀態(tài)下的Activity系統(tǒng)仍然會保存它嫌拣,只有在異常情況下,比如內(nèi)存不夠了呆躲,它的優(yōu)先級最低异逐,系統(tǒng)會回收它。

??2-1-4插掂、銷毀狀態(tài)

??Activity實例被從任務棧中移除灰瞻,對應的生命周期函數(shù)是onDestory。處于這種狀態(tài)下辅甥,不管是否出現(xiàn)異常情況酝润,系統(tǒng)都會回收。
??在Activity的生命周期中還有兩個函數(shù)onSaveInstanceState和onRestoreInstanceState肆氓。這個onSaveInstanceState函數(shù)袍祖,在正常啟動Activity時不會被調用。它的調用時機有如下幾個:
a谢揪、點擊home鍵回到手機界面時蕉陋。
b捐凭、從Activity A跳轉到Activity B時,A的onSaveInstanceState方法會被調用凳鬓。
c茁肠、發(fā)生異常,比如系統(tǒng)配置發(fā)生改變時缩举,當前Activity被殺死時垦梆。
??至于onRestoreInstanceState方法只會在發(fā)生異常,比如系統(tǒng)配置發(fā)生改變時仅孩,當前Activity被殺死后重新創(chuàng)建時調用托猩,它的調用時機是在onStart之后,onResume之前辽慕。

??2-2 異常情況下的生命周期

??前面所講是正常情況下Activity的生命周期流動情況京腥,在異常情況下,當前的Activity或是整個進程都會被殺死溅蛉,這個時候Android系統(tǒng)又提供了另一些處理函數(shù)公浪,比如保存當前的數(shù)據(jù)防止數(shù)據(jù)丟失。異常情況主要分兩種船侧,資源相關的系統(tǒng)配置發(fā)生改變和系統(tǒng)內(nèi)存不足欠气。

??2-2-1 資源相關的系統(tǒng)配置發(fā)生改變導致當前的Activity被殺死重建

??首先,系統(tǒng)配置發(fā)生改變是一種什么樣的情況镜撩?最常見的例子就是手機的橫屏和豎屏之間的切換预柒,這種情況下,系統(tǒng)配置會自動改變琐鲁,此時當前的Activity會被殺死并重新創(chuàng)建卫旱。注意,要殺死的Activity是當前處于運行狀態(tài)的Activity(不能說是棧頂?shù)腁ctivity围段,因為可能存在多個任務棧)顾翼,處于其他狀態(tài)下的Activity不會被殺死。被殺死時奈泪,系統(tǒng)會調用當前Activity的onPause适贸、onStop、onDestory還有onSaveInstanceState方法涝桅。onPause拜姿、onStop、onDestory是依次調用的冯遂,至于onSaveInstanceState方法的調用可能在onPause之前也可能在onPause之后蕊肥,但一定在onStop之前。這個怎么驗證呢?可以創(chuàng)建兩個Activity壁却,第一個Activity啟動第二個Activity批狱,然后旋轉屏幕。為了說明展东,殺死的是運行狀態(tài)的Activity而不是只是存在棧頂?shù)腁ctivity赔硫,我們把啟動的第二個Activity的啟動模式指定為singleTask,任務棧名和第一個Activity的不同盐肃。代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest1">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Demo1MainActivity"
            android:taskAffinity="com.carol.practice.task1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".Demo1BaseActivity"/>
        <activity android:name=".Demo1FirstActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task2"/>
    </application>

</manifest>
public class Demo1MainActivity extends Demo1BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Demo1MainActivity.this, Demo1FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

Demo1MainActivity的啟動模式標準啟動模式爪膊、任務棧名是"com.carol.practice.task1"

public class Demo1FirstActivity extends Demo1BaseActivity {
    private static final String TAG = "Demo1FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        dumpTaskAffinity();
    }
}

Demo1FirstActivity的啟動模式singleTask,任務棧名是"com.carol.practice.task2"砸王。和Demo1MainActivity不一樣推盛。

public class Demo1BaseActivity extends AppCompatActivity{
   private static final String TAG = "Demo1BaseActivity";
   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       Log.i(TAG, getClass().getSimpleName() + " onCreate");
   }

   @Override
   protected void onNewIntent(Intent intent) {
       super.onNewIntent(intent);
       Log.i(TAG, getClass().getSimpleName() + " onNewIntent");
   }

   @Override
   protected void onStart() {
       super.onStart();
       Log.i(TAG, getClass().getSimpleName() + " onStart");
   }

   @Override
   protected void onResume() {
       super.onResume();
       Log.i(TAG, getClass().getSimpleName() + " onResume");
   }

   @Override
   protected void onPause() {
       super.onPause();
       Log.i(TAG, getClass().getSimpleName() + " onPause");
   }

   @Override
   protected void onRestart() {
       super.onRestart();
       Log.i(TAG, getClass().getSimpleName() + " onRestart");
   }

   @Override
   protected void onStop() {
       super.onStop();
       Log.i(TAG, getClass().getSimpleName() + " onStop");
   }
   
   @Override
   protected void onSaveInstanceState(Bundle outState) {
       super.onSaveInstanceState(outState);
       Log.i(TAG, getClass().getSimpleName() + " onSaveInstanceState");
   }

   @Override
   protected void onRestoreInstanceState(Bundle savedInstanceState) {
       super.onRestoreInstanceState(savedInstanceState);
       Log.i(TAG, getClass().getSimpleName() + " onRestoreInstanceState");
   }

   @Override
   protected void onDestroy() {
       super.onDestroy();
       Log.i(TAG, getClass().getSimpleName() + " onDestroy");
   }

   protected void dumpTaskAffinity(){
       try {
           ActivityInfo info = this.getPackageManager()
                   .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
           Log.i(TAG, this.getClass().getSimpleName() + " current activity hascode = " + this.hashCode() + " taskId = " + getTaskId() + " taskAffinity = "+info.taskAffinity + " taskHasCode = " + info.hashCode());
       } catch (PackageManager.NameNotFoundException e) {
           e.printStackTrace();
       }
   }
}

運行,從Demo1MainActivity跳轉到Demo1FirstActivity处硬,然后旋轉小槐。結果如下:


系統(tǒng)配置發(fā)生改變導致當前Activity被殺死后重現(xiàn)創(chuàng)建.png

從輸出結果看,可以得出幾點:
a荷辕、系統(tǒng)配置更改時要殺死只是當前運行狀態(tài)的Activity,不是Activity處于棧頂就會被殺死件豌。
b疮方、殺死當前Activity之前onSaveInstanceState方法被調用。
c茧彤、當前Activity重新創(chuàng)建之后onRestoreInstanceState方法在onStart之后被調用骡显。
??那如果系統(tǒng)上同時運行兩個APP呢?
結果就不貼了曾掂,隱藏在后臺的進程它的Activity都處在停止狀態(tài)惫谤,所以當發(fā)生屏幕旋轉時,不會被殺死珠洗,只有當前處于運行狀態(tài)的Activity會被殺死重建溜歪。
??系統(tǒng)配置對應一個屬性configChange,如果我們不想在屏幕發(fā)生旋轉時殺死當前Activity可以設置這個屬性许蓖,如下:

android:configChange="orientation|screenSize"

注意:只設置orientation是沒有效果的蝴猪,屏幕旋轉仍會殺死重建,需要和screenSize組合在一起使用才行膊爪。

??2-2-2 資源內(nèi)存不足導致低優(yōu)先級的Activity被殺死

??Activity的優(yōu)先級我們在前面講Activity生命周期的四種狀態(tài)時已經(jīng)講過了自阱,優(yōu)先級最低就是Activity處在停止狀態(tài)的時候。當系統(tǒng)內(nèi)存不足的時候米酬,系統(tǒng)就會殺死這些最低優(yōu)先級的Activity沛豌,在《Android開發(fā)藝術探索》中說是會殺掉擁有最低優(yōu)先級Activity的進程,因此赃额,這里不太明白到底只是殺死低優(yōu)先級的Activity還是整個進程被殺死加派。不過根據(jù)Android官方開發(fā)文檔的流程圖來看叫确,當內(nèi)存不足時,處在暫停狀態(tài)和停止狀態(tài)的Activity所在的進程會被殺死哼丈,進程被殺死之前還是會調用onSaveInstanceState,當要返回到這個Activity時會重新創(chuàng)建進程并重新創(chuàng)建這個Activity启妹。官方文檔的Activity的生命周期流程圖如下:


Activity的生命周期官方文檔.png

??2-3 Activity生命周期流程圖

??前面說了那么多,還是來一個自己總結的流程圖吧醉旦。


Activity生命周期流程圖.png

這里說明一下饶米,前面說onSaveInstanceState和onPause誰先執(zhí)行誰后執(zhí)行不能確定,但根據(jù)我在Android5.1系統(tǒng)上測試情況來看都是在onPause先調用onSaveInstanceState后調用车胡。

三檬输、Activity的啟動模式

????Activity的啟動模式有四種:standard模式、singleTop模式匈棘、singleTask模式和singleInstance模式丧慈。這四種模式對應四種對Activity實例不同的管理方式。
????給Activity指定啟動模式有兩種辦法:通過Intent的FLAG_ACTIVITY_XXX標志來指定啟動模式主卫。如下:

Intent intent = new Intent();
intent.setClass(MainActivity.this, FirstActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)逃默;
startActivity(intent);

另一種就是在AndroidManifest.xml中通過launchMode屬性指定啟動模式。比如:

<activity android:name=".FirstActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task1"/>

??3-1 standard模式

????標準模式簇搅,系統(tǒng)默認的啟動方式完域。它的特點如下:
????a、每啟動一個Activity實例就會創(chuàng)建一個Activity實例入棧瘩将。不管當前任務棧中是否已經(jīng)存在這個實例了吟税。
????b、誰啟動了這個Activity姿现,那么這個Activity實例會進入啟動它的Activity所在的任務棧中肠仪。比如,Activity A啟動Activity B备典,A指定的任務棧是a异旧,B是標準模式,指定的任務棧是b熊经,此時B的實例進入的任務棧a而不是指定任務棧b泽艘。這說明,這種模式下taskAffinity是無效的镐依。雖然b對應輸出的taskAffinity就是它指定的名字但是任務棧的ID還是A對應的任務棧的匹涮。這里也說明了任務棧另一個問題,比較任務棧是否相同只看它的taskId是否相同槐壳,不看taskAffinity然低。
????c、由于在標準模式具有b特性,因此如果使用非Activity類型的Context(如果ApplicationContext)啟動標準模式的Activity將會報錯雳攘。因為非Activity類型的Context沒有任務棧带兜。如圖所示:


非Activity類型的Context啟動標準模式的Activity.png

從錯誤提示中,可以看出標準模式下Activity的c特點吨灭。解決這種錯誤的方法就是使用Intent的Flag標志刚照,設置成FLAG_ACTIVITY_NEW_TASK,這樣啟動的時候就會為它創(chuàng)建一個新的任務棧喧兄。實際上是以singleTask的模式啟動的无畔。

??3-2 singleTop模式

????棧頂復用模式。它的特點如下:
????a吠冤、跟標準模式一樣浑彰,誰啟動這種Activity它就進入誰的任務棧中,不受taskAffinity影響拯辙。
????b郭变、啟動這種模式的Activity會先檢查啟動它的Activity所在任務棧的棧頂是否存在相同Activity,如果存在涯保,則不重新創(chuàng)建诉濒,直接調用待啟動Activity的onNewIntent的方法。如果棧頂不存在夕春,則重新創(chuàng)建循诉,不管該任務棧的棧中是否有這個Activity實例。
????使用代碼驗證一下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".BaseActivity"/>
        <activity android:name=".FirstActivity"
            android:launchMode="singleTop"
            android:taskAffinity="com.carol.practice.task1"/>
    </application>
</manifest>

MainActivity是標準模式撇他,F(xiàn)irstActivity是singleTop模式,并且制定任務棧名是"com.carol.practice.task1狈蚤。

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

在MainActivity中點擊按鈕進入FirstActivity困肩。

public class FirstActivity extends BaseActivity{
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        dumpTaskAffinity();
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

FirstActivity中也有一個按鈕,單擊之后脆侮,自己啟動自己锌畸。
其輸出結果如下:


singleTop模式輸出.png

從結果可以看出,第一靖避、FirstActivity進入的是MainActivity所在的任務棧潭枣;第二、FirstActivity位于棧頂幻捏,在自己啟動自己時FirstActivity先進入暫停狀態(tài)然后依次調用onNewIntent和onResume并沒有調用onCreate盆犁、onStart方法,說明沒有被重新創(chuàng)建

??3-3 singleTask模式

????棧內(nèi)復用模式篡九。它的特點如下:
????a谐岁、受taskAffinity影響。它只會進入taskAffinity指定的任務棧中。
????b伊佃、指定的任務棧不存在窜司,則先創(chuàng)建任務棧然后將Activity實例入棧;指定的任務棧存在航揉,則檢查棧內(nèi)是否存在Activity實例塞祈,如果存在,則將位于該Activity實例之上的其他所有Activity實例出棧帅涂,將自己置于棧頂?shù)奈恢靡樾健H绻麠?nèi)不存在,則創(chuàng)建Activity實例漠秋。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".BaseActivity"/>
        <activity android:name=".FirstActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task1"/>
    </application>

</manifest>

將FirstActivity的啟動模式改為singleTask模式笙蒙,taskAffinity指定為"com.carol.practice.task1",使其和MainActivity的taskAffinity不一致庆锦。

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpTaskAffinity();
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }
}

從MainActivity中切換到FirstActivity

public class FirstActivity extends BaseActivity{
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        dumpTaskAffinity();
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, MainActivity.class);
                startActivity(intent);
            }
        });
    }
}

從FirstActivity中切換到MainActivity捅位。
輸出結果如下:


singleTask模式輸出.png

第一,MainActivity啟動FirstActivity搂抒,F(xiàn)irstActivity實例進入的任務棧是FirstActivity指定的任務棧不是MainActivity的任務棧艇搀;第二,從FirstActivity啟動MainActivity求晶,MainActivity重新創(chuàng)建實例并進入FirstActivity所在的任務棧焰雕,因為MainActivity是標準啟動模式;第三芳杏,再次重MainActivity啟動FirstActivity時位于FirstActivit實例所在的任務棧棧頂?shù)腗ainActivity先進入暫停狀態(tài)然后依次調用FirstActivity的onNewIntent矩屁,onRestart、onStart和onResume爵赵,最后MainActivity被銷毀吝秕。我們在singleTop模式下發(fā)現(xiàn)只是調用onNewIntent和onResume,為什么在singleTask模式還調用了onRestart和onStart呢空幻?這跟Activity實例所處的狀態(tài)有關烁峭,singleTop模式下,Activity實例已經(jīng)在棧頂了秕铛,而現(xiàn)在的Activity實例是在停止狀態(tài)约郁,根據(jù)生命周期流程圖從停止狀態(tài)回到運行狀態(tài)時需要一次調用onRestart、onStart和onResume的但两。

??3-4 singleInstance模式

????單例模式鬓梅。在介紹它的特點之前,我們先來看兩個例子(Activity A標準模式 镜遣, Activity B singleInstance模式己肮。在Android5.1上測試):
情況1:APP啟動時先啟動A士袄,創(chuàng)建任務棧A,然后A啟動B谎僻,B單獨創(chuàng)建一個任務棧B娄柳。再從B啟動A,A重新創(chuàng)建實例進入任務棧A艘绍。由于singleInstance的獨占任務棧的特性赤拒,A無法進入任務棧B,系統(tǒng)讓其直接進入之前的任務棧A并重新創(chuàng)建A實例诱鞠。每次從B啟動A都是這樣復用任務棧A重新創(chuàng)建A實例挎挖。這個可以理解。
情況2:APP啟動時先啟動B航夺,創(chuàng)建任務棧B蕉朵,然后B啟動A,A單獨創(chuàng)建一個任務A阳掐。再從A啟動B始衅,B復用之前的實例,接著B啟動A缭保,根據(jù)情況1應該是復用任務棧A重新創(chuàng)建A實例汛闸,可現(xiàn)在輸出結果是復用任務棧A也復用A實例,不重新創(chuàng)建A實例艺骂。這是為什么呢诸老?情況2這種,A都不符合標準模式的特性了(每次啟動都會創(chuàng)建實例)钳恕。貼上我觀察到的輸出結果:


singleInstance模式輸出結果.png

從輸出結果可以看到别伏,MainActivity第一創(chuàng)建完成之后,后面啟動都不再重新創(chuàng)建了忧额。造成這種差異性就是APP運行時第一次啟動的Activity是標準模式還是singleInstance模式畸肆。如果第一次啟動的Activity是標準模式,那么標準模式Activity和singleInstance模式的Activity之間的切換宙址,標準模式的Activity會復用它所在的任務棧并重新創(chuàng)建實例;如果第一次啟動的Activity是singleInstance模式调卑,這個又要分情況抡砂。
在情況2中,A占用一個任務棧A恬涧,B占用一個任務棧B注益, B啟動A,如果任務棧A只有A溯捆,不論多少個丑搔,A復用任務棧A也復用實例A厦瓢,不再重新創(chuàng)建A實例;如果任務棧A不止有A實例還有C實例D實例啤月,那么B啟動A時都會重新創(chuàng)建A實例煮仇。這里就不再帖代碼了。只對singleInstance模式的特點總結如下:
????a谎仲、singleInstance模式下的Activity獨占一個任務棧浙垫,全局唯一。由于任務棧中只有一個Activity實例郑诺,因此夹姥,可見并獲得焦點時它是棧頂運行狀態(tài);可見但失去焦點時它是棧低暫停狀態(tài)辙诞;不可見時它是棧低停止狀態(tài)辙售。
????b、APP啟動時飞涂,第一次啟動的是standard模式下的Activity A旦部,創(chuàng)建任務棧A,后面從A啟動singleInstance模式的Activity B封拧,因為a特點不論B是否重新指定taskAffinity志鹃,都會重新創(chuàng)建一個任務棧B。然后每次從B啟動A時泽西,A都會重新創(chuàng)建實例進入任務棧A曹铃。
????c、APP啟動時捧杉,第一次啟動的是singleInstance模式的Activity B陕见,創(chuàng)建任務棧B。然后從B啟動standard模式的Activity A味抖,如果A不存在會單獨創(chuàng)建一個任務棧A评甜,如果存在,則看任務棧A中是只有A實例(不論多少個)還是除A實例之外還有別實例仔涩。只有A實例則直接復用不重新創(chuàng)建忍坷;除A實例之外還有的別實例則每次都重新創(chuàng)建A實例。這種違反standard模式特性的情況熔脂,只在這里適用佩研。

??3-5 啟動模式與startActivityForResult

????前面介紹四種啟動模式時我們在代碼中要從一個Activity A跳轉到另一個Activity B都是調用方法startActivity,如果我們想從B返回到A時把B中數(shù)據(jù)也傳遞給A則在A中調用方法startActivityForResult啟動B(startActivityForResult的第二個參數(shù)requestCode必須是大于-1的數(shù)霞揉,不然不會回調onActivityResult)旬薯,這樣返回時Activity A的onActivityResult方法就會被調用,該方法會帶回B使用setResult方法給A的intent對象适秩。代碼如下:

public class MainActivity extends BaseActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = (Button) findViewById(R.id.btn_route);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivityForResult(intent, 0);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.i(TAG, "requestCode=" + requestCode + " resultCode=" + resultCode);
        if(resultCode == 100){
            String str = data.getStringExtra("result");
            Log.i(TAG, "FirstActivity delivered result : " + str);
        }
    }
}
public class FirstActivity extends BaseActivity{
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_activity_layout);
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("result", "i am FirstActivity");
                setResult(100, intent);
                finish();
            }
        });
    }
}

注意這里是B返回A绊序,比如B自己finish或按back鍵硕舆,不是B啟動A。B啟動A是不會調用A的onActivityResult方法的骤公。
????那使用startActivityForResult啟動Activity抚官,在不同的啟動模式會跟startActivity啟動Activity有什么不同嗎?我們通過代碼來驗證下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.carol.practice.activitytest">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"> 
        <activity android:name=".LaunchModeActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".StandardActivity"/>
        <activity android:name=".SingleTopActivity"
            android:launchMode="singleTop"/>
        <activity android:name=".SingleTaskActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.carol.practice.task1"/>
        <activity android:name=".SingleInstanceActivity"
            android:launchMode="singleInstance"
            android:taskAffinity="com.carol.practice.task2"/>
    </application>
</manifest>
public class LaunchModeActivity extends BaseActivity implements View.OnClickListener{
    private static final String TAG = "LaunchModeActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.launchmode_activity_layout);
        Button standardBtn = (Button)findViewById(R.id.btn_standard_activity);
        standardBtn.setOnClickListener(this);
        Button singleTopBtn = (Button)findViewById(R.id.btn_singleTop_activity);
        singleTopBtn.setOnClickListener(this);
        Button singleTaskBtn = (Button)findViewById(R.id.btn_singleTask_activity);
        singleTaskBtn.setOnClickListener(this);
        Button singleInstanceBtn = (Button)findViewById(R.id.btn_singleInstance_activity);
        singleInstanceBtn.setOnClickListener(this);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.i("BaseActivity", "requestCode=" + requestCode + " resultCode=" + resultCode);
        if(resultCode >= 100) {
            String str = data.getStringExtra("result");
            Log.i("BaseActivity", "delivered result : " + str);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId())
        {
            case R.id.btn_standard_activity:
                Intent standardIntent = new Intent(LaunchModeActivity.this, StandardActivity.class);
                startActivityForResult(standardIntent, 100);
                break;
            case R.id.btn_singleTop_activity:
                Intent singleTopIntent = new Intent(LaunchModeActivity.this, SingleTopActivity.class);
                startActivityForResult(singleTopIntent, 101);
                break;
            case R.id.btn_singleTask_activity:
                Intent singleTaskIntent = new Intent(LaunchModeActivity.this, SingleTaskActivity.class);
                startActivityForResult(singleTaskIntent, 102);
                break;
            case R.id.btn_singleInstance_activity:
                Intent singleInstanceIntent = new Intent(LaunchModeActivity.this, SingleInstanceActivity.class);
               startActivityForResult(singleInstanceIntent, 103);
                break;
            default:
                break;
        }
    }
}
public class StandardActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.standard_activity_layout);

        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(StandardActivity.this, LaunchModeActivity.class);
                startActivityForResult(intent, 100);
            }
        });
    }
}

public class SingleTopActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.singletop_activity_layout);

        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SingleTopActivity.this, SingleTopActivity.class);
                startActivityForResult(intent, 101);
            }
        });
    }
}

public class SingleTaskActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.singletask_activity_layout);
        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SingleTaskActivity.this, LaunchModeActivity.class);
                startActivityForResult(intent, 102);
            }
        });
    }
}

public class SingleInstanceActivity extends BaseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.singleinstance_activity_layout);

        Button btn = (Button)findViewById(R.id.btn_back);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SingleInstanceActivity.this, LaunchModeActivity.class);
                startActivityForResult(intent, 103);
            }
        });
    }
}

startActivityForResult和standard模式的Activity的輸出結果:


startActivityForResult與standard模式的輸出結果.png

從輸出結果看淋样,startActivityForResult與startActivity啟動standard模式的Activity是一樣的耗式,符合standard模式的特點。

startActivityForResult和singleTop模式的Activity的輸出結果:


startActivityForResult和singleTop模式的輸出結果.png

從這個輸出結果我們發(fā)現(xiàn)趁猴,startActivityForResult與startActivity啟動singleTop模式的Activity有所不同了刊咳,當SingleTopActivity實例已經(jīng)位于棧頂時,SingleTopActivity使用startActivityForResult自己啟動自己還是會重新創(chuàng)建實例儡司。如果使用startActivity自己啟動自己就不會重新創(chuàng)建實例娱挨。

startActivityForResult和singleTask模式的Activity的輸出結果:


startActivityForResult和singleTask模式的輸出結果.png

從結果來看,跟standard模式的輸出一樣捕犬,但是我是指定了SingleTaskActivity的啟動模式和taskAffinity的呀跷坝。系統(tǒng)并沒有為SingleTaskActivity重新創(chuàng)建任務棧,還是使用的LaunchModeActivity的任務棧碉碉,而且最后一步從LaunchModeActivity中啟動SingleTaskActivity時也是重新創(chuàng)建了實例柴钻,沒有復用任務棧里的實例鉴嗤。

startActivityForResult和singleInstance模式的Activity的輸出結果:


startActivityForResult和singleInstance模式的輸出結果.png

這個和singleTask模式的輸出結果是一致的葵第,也是沒有重新創(chuàng)建任務棧和復用任務棧中已經(jīng)存在的SingleInstanceActivity實例矗烛。

因此混弥,從這四種輸出結果可以得出結論:
使用startActivityForResult(參數(shù)requestCode大于-1)啟動Activity時會忽略launchMode和taskAffinity的作用,改為standard啟動模式峦失。launchMode和taskAffinity只對startActivity有效沪悲。
以上測試都是基于Android5.1系統(tǒng)蹲姐。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昔善,一起剝皮案震驚了整個濱河市元潘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌君仆,老刑警劉巖翩概,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異返咱,居然都是意外死亡氮帐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門洛姑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人皮服,你說我怎么就攤上這事楞艾〔瘟” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵硫眯,是天一觀的道長蕴侧。 經(jīng)常有香客問我,道長两入,這世上最難降的妖魔是什么净宵? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮裹纳,結果婚禮上择葡,老公的妹妹穿的比我還像新娘。我一直安慰自己剃氧,他們只是感情好敏储,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著朋鞍,像睡著了一般已添。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滥酥,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天更舞,我揣著相機與錄音,去河邊找鬼坎吻。 笑死缆蝉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的禾怠。 我是一名探鬼主播返奉,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吗氏!你這毒婦竟也來了芽偏?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弦讽,失蹤者是張志新(化名)和其女友劉穎污尉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體往产,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡被碗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仿村。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锐朴。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蔼囊,靈堂內(nèi)的尸體忽然破棺而出焚志,到底是詐尸還是另有隱情衣迷,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布酱酬,位于F島的核電站壶谒,受9級特大地震影響,放射性物質發(fā)生泄漏膳沽。R本人自食惡果不足惜汗菜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挑社。 院中可真熱鬧陨界,春花似錦、人聲如沸滔灶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽录平。三九已至麻车,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斗这,已是汗流浹背动猬。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留表箭,地道東北人赁咙。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像免钻,于是被迫代替她去往敵國和親彼水。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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