黑發不知勤學早,白首方悔讀書遲。——《勸學》
今天花了整個下午+晚上的的時間學習了Activity的啟動模式,本來以為這個知識點很簡單,但是在學習的過程中發現,Activity的啟動模式并沒有自己想象的那么簡單,下面我們一起來看看這Activity的四種啟動模式吧,如有疑問歡迎留言,如有謬誤歡迎大家批評指正,謝謝
Activity的啟動模式共有四種
1.standard
2.singleTop
3.singleTask
4.singleInstance
如圖所示:

LaunchMode在多個Activity跳轉的過程中扮演著重要的角色,它可以決定是否生成新的Activity實例,是否重用已存在的Activity實例,是否和其他Activity實例公用一個task里。這里簡單介紹一下task的概念,task是一個具有棧結構的對象,一個task可以管理多個Activity,啟動一個應用,也就創建一個與之對應的task。
下面我們就依次來說說這幾種啟動模式
1.standard
standard模式是Activity默認的啟動模式,當我們在沒有配置activity的launchMode時它就會按照standard方式去啟動,
下面通過一個實例來解釋下這種啟動模式
FirstActivity代碼如下:
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class FirstActivity extends Activity {
private Button btn_jumpToSecondActivity;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText(FirstActivity.this.toString());
btn_jumpToSecondActivity=(Button) findViewById(R.id.btn_jumpToSecondActivity);
btn_jumpToSecondActivity.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstActivity.this,FirstActivity.class);
startActivity(intent);
}
});
}
}
~~~
啟動后的界面

此時它所對應的任務棧如下

在此基礎上我們點擊按鈕再次啟動Activity此時的界面如下

此時的任務棧變化過程如下

我們再次點擊按鈕跳轉到FirstActivity界面如下

此時的任務棧的變化過程如下

好了到這我們就可以分析一下了,在上述過程中我們點擊了三次按鈕它實例化了三個FirstActivity
這就是standard模式的特點:不管任務棧中有沒有實例存在它都會實例化一個Activity
當我們點擊返回按鈕時它會依次把最上面的Activity出棧,上面的過程中一共實例化了三個Activity因此我們需要點擊三次返回按鈕應用才能退出。
2.singleTop
還用上面那個例子,此時我們給FirstActivity的屬性指定為:android:launchMode="singleTop"
啟動后的界面

此時的任務棧如下

我們接著點擊按鈕發現無論點擊幾次界面都沒變說明它只實例化一次,此時的任務站始終是一個Activity此時點擊一次返回鍵便可退出應用。
這是只有一個Activity的情況,下面我們說說多個Activity的情況
再來一個SecondActivity代碼如下:
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class SecondActivity extends Activity {
private Button btn_jumpToFirstActivity_;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText(SecondActivity.this.toString());
btn_jumpToFirstActivity_=(Button) findViewById(R.id.btn_jumpToFirstActivity);
btn_jumpToFirstActivity_.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(SecondActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
}
~~~
把FirstActivity的代碼稍作修改
~~~
btn_jumpToSecondActivity.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
~~~
在上面的Activity中FirstActivity的啟動模式是singleTop,SecondActivity的啟動模式是默認的standard,做好準備之后我們來做操作
啟動后的界面如下:

此時的任務棧如下

在此基礎上我們點擊一次按鈕界面如下

此時的任務棧的變化如下

在SecondActivity中再次點擊按鈕的界面如下

此時的任務棧的變化如下

從上面的過程中我們看到再次從SecondActivity跳轉到FirstActivity時兩次的FirstActivity的序列號不同說明又重新生成了一個FirstActivity
singleTop模式的特點:當從SecondActivity跳轉到FirstActivity時,系統發現存在有FirstActivity實例,但不是位于棧頂,于是重新生成一個實例。這就是singleTop啟動模式的特點,即如果發現有對應的Activity實例正位于棧頂,則重復利用,不再生成新的實例,如果棧頂沒有對應的Activity則實例化一個。
該模式和standard模式基本一致,但有一點不同,當將要被啟動的Activity已經位于Task棧頂時,系統不會重新創建目標Activity的實例,而是直接復用Task棧頂的Activity
3.singleTask(內單例模式)
我們還是建立在上面的基礎上,把FirstActivity的啟動模式改為android:launchMode="singleTask"
啟動后我們點擊三次跳轉按鈕界面如下圖所示




在上面的過程中,FirstActivity的序列號是不變的,SecondActivity的序列號是改變的,說明從SecondActivity跳轉到FirstActivity時,沒有生成新的實例,但是從FirstActivity跳轉到SecondActivity時生成了新的實例。
在此過程中任務棧的變化過程如下

在上面的跳轉過程中當從SecondActivity跳轉到FirstActivity時發現SecondActivity消失了,這就是singleTask的特點在這個跳轉過程中系統發現有存在的FirstActivity實例,于是不再生成新的實例,而是將FirstActivity之上的Activity實例統統出棧,將FirstActivity變為棧頂對象,顯示到幕前。
singleTask模式的特點:如果發現有對應的Activity實例,則使此Activity實例之上的其他Activity實例統統出棧,使此Activity實例成為棧頂對象,顯示到幕前。
Activity在同一個Task內只有一個實例.?當系統采用singleTask模式加載Activity時,又分為以下三種情況:
(1)如果將要啟動的Activity不存在,那么系統將會創建該實例,并將其加入Task棧頂
(2)如果將要啟動的Activity已存在,且存在棧頂,那么此時與singleTop模式的行為相同
(3)如果將要啟動的Activity存在但是沒有位于棧頂,那么 ? ? 此時系統會把位于該Activity上面的所有其他Activity全部移出Task,從而使得該目標Activity位于棧頂
4.singleInstance(全局單例模式)
這種模式是四種模式中最難理解的一種模式,因為這種模式會重新創建一個新的任務棧,將Activity放置于這個棧中,并保證其它的Activity不再進入,由于這種模式比較復雜,我們首先來說說它的原理,然后再結合實例進一步的理解,假如現在用戶打開了兩個應用分別為應用1和應用2,應用1和應用2的任務棧假如如下圖左邊,此時在應用1中想打開Activity3,這時應用1和應用2就會共享Activity3的引用,
注意:之所以能公用Activity的引用是以應用2中的Activity設置了LaunchMode="singleInstance"為前提的。

由于這種模式比較復雜,我們舉兩個不同例子,來說明不同的問題
舉例一、
還是上面的兩個Activity。FirstActivity和SecondActivity在兩個Activityt跳轉的過程中我們打印兩個Activity所在的任務棧的ID
對以上兩個Activity做如下修改,并且把SecondActivity的啟動模式改為singleInstance
~~~
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
~~~
~~~
tv_showViewClass.setText("當前Activity:"+"\n"+this.toString()+"\n"+"當前TaskId:"+this.getTaskId());
~~~
啟動后和點擊跳轉按鈕后的界面如下


我們發現兩個Activity的TaskId是不同的,說明這兩個Activity是位于不同的任務棧中的,從而證實了為SecondActivity重新建立了一個任務棧,可能有的朋友會問,在這個時候如果點擊返回按鈕它們是怎么出棧的呢?假如現在我們點擊返回按鈕它的任務棧的變化如下圖

假如我們在SecondActivity中點擊按鈕跳轉到FirstActivity然后會以怎樣的方式退出應用呢?此時 它的任務棧的變化如下

圖中下半部分顯示的在SecondActivity中再次跳轉到FirstActivity,這個時候系統會在原始棧結構中生成一個FirstActivity實例,然后回退兩次,注意,并沒有退出,而是回到了SecondActivity,為什么呢?是因為從SecondActivity跳轉到FirstActivity的時候,我們的起點變成了SecondActivity實例所在的棧結構,這樣一來,我們需要“回歸”到這個棧結構。
由于singleInstance比較復雜些,我們再來舉一個兩個應用的例子為了和上面的例子混淆,我們重新寫兩個應用
第一個App中有兩個Activity分別為Activity1和ShareActivity
第二個App中有一個Activity2我們在這個App中啟動第一個App的ShareActivity
第一個App的Activity源碼如下
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class Activity1 extends Activity {
private Button btn_jumpToSecondActivity;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText("當前Activity:"+"\n"+this.toString()+"\n"+"當前TaskId:"+this.getTaskId());
btn_jumpToSecondActivity=(Button) findViewById(R.id.btn_jumpToSharedActivity);
btn_jumpToSecondActivity.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(Activity1.this,ShareActivity.class);
startActivity(intent);
}
});
}
}
~~~
ShareActivity源碼
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class ShareActivity extends Activity {
private Button btn_jump;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText("當前Activity:"+"\n"+this.toString()+"\n"+"當前TaskId:"+this.getTaskId());
}
}
~~~
我們要特別注意ShareActivity在清單文件中的配置如下
~~~
<activity
android:name="com.example.activitylauchmodepractice.ShareActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="SecondActivity_action"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
~~~
我們需要配置ShareActivity的action在另一個應用中啟動時會用到
第二個App中的Activity2的源碼如下
~~~
package com.example.singleinstancepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button btn_jump;
private TextView tv_showTaskId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_showTaskId=(TextView) findViewById(R.id.tv_showTaskId);
tv_showTaskId.setText("當前Activity:"+"\n"+this.toString()+"\n"+"當前TaskId:"+this.getTaskId());
btn_jump=(Button) findViewById(R.id.btn_jump);
btn_jump.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent =new Intent();
intent.setAction("SecondActivity_action");
startActivity(intent);
}
});
}
}
~~~
當我們在第一個App中打開ShareActivity后再按后退鍵回到原來界面時,ShareActivity做為一個獨立的個體存在,如果這時我們在第二個App中打開ShareActivity無需創建新的ShareActivity實例即可看到結果,因為系統會自動查找,存在則直接利用。原理圖如下:

注意:上圖是建立在第一個App運行到手機上時點擊第二個App上的跳轉按鈕跳轉到ShareActivity的情況的基礎上的變化過程。