安卓活动的启动模式
版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。2019-09-23,22:05:10。
作者By-----溺心与沉浮----博客园
在安卓实际项目中我们应该根据特定的需求为每个活动指定恰当的启动模式。启动模式一共有4种,分别是standard、singleTop、singleTask、singleInstance,我们可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性来选择启动模式。Activity四种启动模式:
- standard
标准启动模式,也是默认启动模式,每次激活Activity时都会创建Activity,并放入任务栈中。
- singleTop
如果在任务的栈顶正好存在该Activity的实例,就重用该实例,否则就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例)。
- singleTask
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent()), 重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。一般不会新建栈,taskAffinity可设置自己的栈。当前栈实例唯一。
- singleInstance
在一个新栈中创建该Activity实例,保证不再有其他Activity实例进入,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。系统范围内实例唯一。
注意点:
-singleTask、singleInstance这两个LaunchMode标识只能用在startActivity()的方法中,而不能使用在startActivityForResult方法中,系统会返回resultCode=RESULT_CANCELED。(startActivityForResult在我的上一篇博文,安卓生命周期https://www.cnblogs.com/Reverse-xiaoyu/p/11563742.html有简单描写)
一、standard
standard是活动默认的启动模式,在不进行显示指定的情况下,所有活动都会自动使用这种启动模式。Android使用返回栈来管理活动的,在standard(即默认情况下),每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
我们现在通过实践来体会一下standard模式,这次是在我上一个项目中的基础上修改(项目下载连接:https://pan.baidu.com/s/1VqLPX8npqGgDQiD0l0LGPQ,后面我会使用GitHub)。下载好项目后,打开它。
在activity_main.xml中增加一个按钮,代码如下:
1 <Button
2 android:id="@+id/button_standard"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:text="Standard"/>
按钮添加完成后,在MainActivity中onCreate()方法添加的代码,如下所示:
1 package com.xiaoyu.activitytest;
2
3 import androidx.annotation.NonNull;
4 import androidx.annotation.Nullable;
5 import androidx.appcompat.app.AppCompatActivity;
6
7 import android.content.Intent;
8 import android.net.Uri;
9 import android.os.Bundle;
10 import android.util.Log;
11 import android.view.Menu;
12 import android.view.MenuItem;
13 import android.view.View;
14 import android.widget.Button;
15 import android.widget.TextView;
16 import android.widget.Toast;
17
18 import javax.xml.transform.Templates;
19
20 public class MainActivity extends AppCompatActivity {
21
22 @Override
23 protected void onCreate(Bundle savedInstanceState) {
24 super.onCreate(savedInstanceState);
25 setContentView(R.layout.activity_main);
26 Log.d("xiaoyu", this.toString());
27
28 Button buttonStandard = (Button) findViewById(R.id.button_standard);
29
30 buttonStandard.setOnClickListener(new View.OnClickListener() {
31 @Override
32 public void onClick(View view) {
33 Intent intent = new Intent(MainActivity.this, MainActivity.class);
34 startActivity(intent);
35 }
36 });
37 ......
38 }
这段代码不用在意有什么实际用途,重点研究standard模式,重新运行程序,然后在MainActivity界面连续点击三次按钮,可以看到logcat中打印信息如下所示:
从打印信息可以看出,每点击一次按钮就会创建出一个新的MainActivity实例。此时返回栈中也会存在4个MainActivity的实例,因此我们需要连续按4此Back键才能退出程序。
standard模式的原理示意图,如下图所示:
standard模式示意图
二、singleTop
有时候我们会觉得standard模式不太合理。活动都明明已经在栈顶了,为什么再次启动的时候还要创建一个新的活动实例呢?别慌,这只是系统默认的一种启动方式而已,我们完全可以根据自己所需进行修改,比如使用singleTop模式。当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
我们用实例来体会一下,修改AndroidManifest.xml中的MainActivity的启动模式,如下所示:
1 <activity android:name=".MainActivity"
2 android:label="This is MainActivity"
3 android:launchMode="singleTop">
4 <intent-filter>
5 <action android:name="android.intent.action.MAIN" />
6
7 <category android:name="android.intent.category.LAUNCHER" />
8 </intent-filter>
9 </activity>
重新运行程序,查看logcat会看到已经创建了MainActivity的实例,如下图所示:
但是之后不管点击多少次按钮都不会再有新的打印信息出现,因为目前MainActivity已经处于返回栈的栈顶,每当想要再启动一个MainActivity时都会直接使用栈顶的活动,因此MainActivity也只会有一个实例,仅按一次Back键就可以退出程序。
不过当MainActivity并未处于栈顶位置时,这时再启动MainActivity,还是会创建新的实例的。
我们来实例测试一下,再activity_main.xml中再增加一个按钮,代码如下:
1 <Button
2 android:id="@+id/button_singleTop"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:text="singleTop"/>
紧接着修改MainActivity中onCreate()方法的代码,如下所示:
1 package com.xiaoyu.activitytest; 2 3 import androidx.annotation.NonNull; 4 import androidx.annotation.Nullable; 5 import androidx.appcompat.app.AppCompatActivity; 6 7 import android.content.Intent; 8 import android.net.Uri; 9 import android.os.Bundle; 10 import android.util.Log; 11 import android.view.Menu; 12 import android.view.MenuItem; 13 import android.view.View; 14 import android.widget.Button; 15 import android.widget.TextView; 16 import android.widget.Toast; 17 18 import javax.xml.transform.Templates; 19 20 public class MainActivity extends AppCompatActivity { 21 22 @Override 23 protected void onCreate(Bundle savedInstanceState) { 24 super.onCreate(savedInstanceState); 25 setContentView(R.layout.activity_main); 26 Log.d("MainActivity", this.toString()); 27 28 Button buttonSingleTop = (Button) findViewById(R.id.button_singleTop); 29 30 31 buttonSingleTop.setOnClickListener(new View.OnClickListener() { 32 @Override 33 public void onClick(View view) { 34 Intent intent = new Intent(MainActivity.this, SecondActivity.class); 35 startActivity(intent); 36 } 37 }); 38 ...... 39 }
这次设置点击按钮后启动的活动时SecondActivity。给activity_second.xml添加一个按钮代码,然后修改onCreate()方法的代码,如下所示:
1 <Button
2 android:id="@+id/button_single_top"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:text="single_top"/>
1 package com.xiaoyu.activitytest;
2
3 import androidx.appcompat.app.AppCompatActivity;
4
5 import android.content.Intent;
6 import android.os.Bundle;
7 import android.view.View;
8 import android.widget.Button;
9 import android.widget.TextView;
10
11 public class SecondActivity extends AppCompatActivity {
12
13 @Override
14 protected void onCreate(Bundle savedInstanceState) {
15 super.onCreate(savedInstanceState);
16 setContentView(R.layout.activity_second);
17 Button button_single_top= (Button) findViewById(R.id.button_single_top);
18 TextView receiveData = (TextView) findViewById(R.id.receive_data);
19
20 button_single_top.setOnClickListener(new View.OnClickListener() {
21 @Override
22 public void onClick(View view) {
23 Intent intent = new Intent(SecondActivity.this, MainActivity.class);
24 startActivity(intent);
25 }
26 });
27
28 Intent intent = getIntent();
29 String data = intent.getStringExtra("extra_data");
30 receiveData.setText(data);
31 }
32 ......
33 }
我们在SecondActivity中的按钮点击事件里又加入启动FirstActivity的代码。现在重新运行程序,在MainActivity界面点击按钮进入到SecondActivity,然后在SecondActivity界面点击按钮,又会重新进入到MainActivity。
查看logcat中的打印信息,如图所示:
可以看到,系统创建了两个不同的MainActivity实例,这是由于在SecondActivity中再次启动MainActivity时,栈顶活动已经变成了SecondActivity,因此会创建一个新的MainActivity实例,现在按下Back键会返回到SecondActivity,再次按下会回到MainActivity,再按一次Back键才会退出程序。
singleTop模式的原理示意图,如下图所示:
singleTop模式示意图
三、singleTask
使用singleTop模式可以很好地解决重复创建栈顶活动地问题,但是正如我们singleTop例子中看到的,如果该活动并没有处于栈顶位置,还是可能会创建多个活动实例的。那我们有没有什么办法可以让某个活动在整个应用程序的上下文中只存在一个实例呢?我们就需要借助singleTask模式来实现了。当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在整个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
我们接下来通过代码分析。修改AndroidManifest.xml中MainActivity的启动模式:
1 <activity android:name=".MainActivity"
2 android:label="This is MainActivity"
3 android:launchMode="singleTask">
4 <intent-filter>
5 <action android:name="android.intent.action.MAIN" />
6
7 <category android:name="android.intent.category.LAUNCHER" />
8 </intent-filter>
9 </activity>
在MainActivity中添加onRestart()方法,并打印日志:
1 @Override
2 protected void onRestart() {
3 super.onRestart();
4 Log.d(TAG, "onRestart: ");
5 }
最后在SecondActivity中添加onDestroy方法,并打印日志:
1 @Override
2 protected void onDestroy() {
3 super.onDestroy();
4 Log.d(TAG, "onDestroy: ");
5 }
重新运行程序,在MainActivity界面点击按钮进入到SecondActivity,然后再SecondActivity界面点击按钮,又会重新进入到MainActivity。查看logcat中的打印信息,如下图所示:(由于本例没有添加按钮,我们把singleTop中的按钮名字更改为singleTask)
从打印信息中就可以明显看出了,在SecondActivity中启动MainActivity时,会发现返回栈中已经存在了一个MainActivity的实例,并且是在SecondActivity的下面,于是SecondActivity会从返回栈中出栈,而MainActivity重新成为了栈顶活动,因此MainActivity的onRestart()方法和SecondActivity的onDestroy()方法会得到执行。现在返回栈中应该只剩下一个MainActivity的实例了,只需要按一下Back键就可以退出程序了。
singleTask模式的原理示意图,如下图所示:
singleTask模式示意图
四、singleInstance
singleInstance模式是这4种模式中最特殊也是最复杂的一个了,我们需要多花费一点时间来理解这个模式。不同于以上3种启动模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。那么这样做的意义何在?假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?使用前面3种启动模式肯定是做不到的,因为每个应用程序都有自己的返回栈,同一个活动在不同的返回栈中必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
我们来实例操作一下,修改AndroidManifest.xml中SecondActivity的启动模式:
1 <activity android:name=".SecondActivity"
2 android:launchMode="singleInstance">
3 <intent-filter>
4 <action android:name="com.xiaoyu.activitytest.ACTION_START" />
5
6 <category android:name="android.intent.category.DEFAULT" />
7 <category android:name="com.xiaoyu.activitytest.MY_CATEGORY" />
8 </intent-filter>
9 </activity>
先将SecondActivity的启动模式指定为singleInstance,在activity_main.xml中添加下面代码,然后修改MainActivity中onCreate()方法的代码:
1 <Button
2 android:id="@+id/button_singleInstance"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:text="singleInstance"/>
1 package com.xiaoyu.activitytest;
2
3 import androidx.annotation.NonNull;
4 import androidx.annotation.Nullable;
5 import androidx.appcompat.app.AppCompatActivity;
6
7 import android.content.Intent;
8 import android.net.Uri;
9 import android.os.Bundle;
10 import android.util.Log;
11 import android.view.Menu;
12 import android.view.MenuItem;
13 import android.view.View;
14 import android.widget.Button;
15 import android.widget.TextView;
16 import android.widget.Toast;
17
18 import javax.xml.transform.Templates;
19
20 public class MainActivity extends AppCompatActivity {
21 private static final String TAG = "MainActivity";
22
23 @Override
24 protected void onCreate(Bundle savedInstanceState) {
25 super.onCreate(savedInstanceState);
26 setContentView(R.layout.activity_main);
27 Log.d("MainActivity", this.toString());
28 Log.d(TAG, "Task id is " + getTaskId());
29 Button buttonSingleInstance = (Button) findViewById(R.id.button_singleInstance);
30 buttonSingleInstance.setOnClickListener(new View.OnClickListener() {
31 @Override
32 public void onClick(View view) {
33 Intent intent = new Intent(MainActivity.this, SecondActivity.class);
34 startActivity(intent);
35 }
36 });
37 ......
38 }
在onCreate()方法中打印了当前返回栈的id。然后修改SecondActivity中的onCreate()方法的代码,修改如下:
1 package com.xiaoyu.activitytest;
2
3 import androidx.appcompat.app.AppCompatActivity;
4
5 import android.content.Intent;
6 import android.os.Bundle;
7 import android.util.Log;
8 import android.view.View;
9 import android.widget.Button;
10 import android.widget.TextView;
11
12 public class SecondActivity extends AppCompatActivity {
13 private static final String TAG = "SecondActivity";
14
15 @Override
16 protected void onCreate(Bundle savedInstanceState) {
17 super.onCreate(savedInstanceState);
18 setContentView(R.layout.activity_second);
19 Log.d(TAG, this.toString());
20 Log.d(TAG, "Task id is " + getTaskId());
21
22 Button button_single_instance= (Button) findViewById(R.id.button_single_instance);
23
24 button_single_instance.setOnClickListener(new View.OnClickListener() {
25 @Override
26 public void onClick(View view) {
27 Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
28 startActivity(intent);
29 }
30 });
31 ......
32 }
33 }
同样在onCreate()方法中打印了当前返回栈的id,然后又修改了按钮点击事件的代码,用于启动ThirdActivity。最后修改ThirdActivity中onCreate()方法的代码:
1 package com.xiaoyu.activitytest;
2
3 import androidx.appcompat.app.AppCompatActivity;
4
5 import android.os.Bundle;
6 import android.util.Log;
7 import android.view.View;
8 import android.widget.Button;
9
10 public class ThirdActivity extends AppCompatActivity {
11 private static final String TAG = "ThirdActivity";
12 @Override
13 protected void onCreate(Bundle savedInstanceState) {
14 super.onCreate(savedInstanceState);
15 setContentView(R.layout.activity_third);
16
17 Log.d(TAG, "Task id is " + getTaskId());
18 }
19 }
主活动的界面如下:
SecondActivity的界面如下
ThirdActivity的界面如下
查看logcat中的打印信息,如图所示:
可以看到,SecondActivity的Task id不同于MainActivity和ThirdActivity,这说明SecondActivity确实是存放在一个单独的返回栈里的,而且这个栈中只有SecondActivity这一个活动。
然后我们按下Back键进行返回,会发现ThirdActivity直接返回到了MainActivity,再按下Back键又会返回到SecondActivity,再按下Back键才会退出程序,why?这是由于MainActivity和ThirdActivity是存放在同一个返回栈里的,当在ThirdActivity的界面按下Back键,ThirdActivity会从返回栈中出栈,那么MainActivity就成为了栈顶活动显示在界面上,因此也就出现了从ThirdActivity直接返回到了MainActivity的情况。然后在MainActivity界面再次按下Back键,这是当前的返回栈已经空了,于是就显示了另一个返回栈的栈顶活动,即SecondActivity。最后再次按下Back键,这时所有的返回栈都已经空了,也就退出了程序。
singleInstance模式的原理示意图,如下图所示:
singleInstance模式示意图