启动模式的重要性
Android编程中经常涉及到页面的切换,启动一个新的页面(或者说Activity)的时候需要为其指定合适的“启动模式”。指定的启动模式不合适,会出现类似下面这种奇怪的效果:
- 你拿起QQ切换了一个新的账号,一直按返回却没有退出程序,而是又回到了旧帐号对应的页面…
- 你点击别人头像的时候不知道为什么系统卡顿了,于是你又点击了几次,等到系统反应过来,给你打开了一个又一个别人的主页,你只好一个又一个地退出,因为这些页面实际上是一样的…
这些情况都是我在自己写项目或者使用市场上一些软件的时候遇到的。为了实现Activity切换、显示等的正常,我们需要为活动指定适合的启动模式(launchMode)。
安卓中的启动模式
Android编程时Activity有4种启动模式,分别为standard,singleTop,singTask和singleInstance。使用Android Studio开发时每新建一个活动,Android Studio都会为我们在AndroidManifest.xml中为其注册。活动的启动模式在标签中的<android: launchMode=" ">属性中指定。
安卓使用 返回栈(Back Stack) 来管理活动。不同的启动模式,实际上对应的是返回栈中不同的变化。(而调用finish()方法销毁一个活动的时候,对应的都是栈顶活动的出栈)
1 standard
standard是活动的默认启动模式。如果不使用launchMode属性来指定,系统就会默认这种启动模式。
standard模式的特点:
- 当启动一个Activity时,如果这个Activity的启动模式为默认的standard模式,那么无论当前栈顶元素是否是它本身,他都会简单地新建一个Activity,并将其加到栈顶。
因为standard模式比较简单,所以这里直接通过代码来演示一下standard启动模式时,页面、返回栈中内容的变化。
- 新建一个LaunchModeTest 工程,并在其中创建FirstActivity和其对应的xml。如图所示。由于没有指定其启动模式,所以会采用默认的standard启动模式。
- 在activity_first.xml中添加一个加入如下代码,使其页面上出现一个写着"To myself"的Button。我们下面将实现点击这个Button,来从“自己”跳到“自己”。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstActivity">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="To myself"
/>
</LinearLayout>
- 在FirstActivity中对按钮添加监听事件——由FirstActivity切换到FirstActivity。
public class FirstActivity extends AppCompatActivity {
private Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
button1 = findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
}
- 运行程序,观察运行效果。
可以看到,每次都可以启动一个新的FirstActivity,退出时要一个一个退回。其中的栈结构变化为:
- 启动软件:FirstActivtiy
- 点击button一次:Firstctivity->FirstActivtiy
- 点击button两次:Firstctivity->FirstActivtiy->FirstActivity
- 返回一次:Firstctivity->FirstActivtiy
- 返回两次:Firstctivity
- 返回三次:(退出程序了)
2 singleTop
singleTop其实可以从字面上理解为栈顶Activity的唯一性。其特点在于:
singleTop模式下,假如新建的Activity和栈顶的Activity是同一个Activity,那么就不会再新建。
实例如下:
- 在上述代码的基础上,只做一个修改:在AndroidManifest.xml中的标签中指定launchMode为singleTop。
<activity android:name=".FirstActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- 运行程序,观察效果。
和standard模式同样的代码,只是修改了启动模式,点击按钮之后不再会创建多余的自己了。这样即使系统卡顿、网络延迟,也可以消除堆叠多个同样Activity的情况。
其中的栈结构变化为:
- 启动软件:FirstActivtiy
- 点击button一次:Firstctivity
- 点击button两次:Firstctivity
- 返回一次:(退出程序了)
3 singleTask
singleTask其实也可以顾名思义,不就是一个返回栈中某个Activity只有一个实例吗?如何做到只有一个实例呢?
假设现在栈底到栈顶的情况依次为:A->B->C->D 现在D要启动launchMode为singleTask的B,那么栈中情况如何变化才能保证只有一个B呢?难道将B单独抽出、提到栈顶,直接变成A->C->D->B吗?这显然不太符合逻辑。倒不如是直接将C、D出栈,直接让B变成栈顶元素,变成A->B,这样还可以少创建一次了呢。
实际上,安卓就是这样处理的——
当启动一个launchMode为singleTask的Activity时,假设之前没有创建过就需要重新创建;有,就直接使用原来的那个,并且将其上所有Activity全部出栈。
大家可能会注意到,我上面说了“使用原来的那个”,这意味着,如果系统已经创建过一次singleTask的B,并且还没有将其销毁,那么D启动B时,实际上不是新创建了一个B,而是直接使用的原来的B。如果我们重写B的onCreate()和onStart()方法,可以发现B第一次被创建时会调用的是onCreate()方法,而被D启动时会调用onStart()方法,说明其只是由不可见变为可见,而不是重新创建。
这里可以使用一个小例子来直观地说明singleTask直接复用以前创建的Activity这个特点:
- 创建一个project,并且在其中创建LoginActivity、AfterLoginActivity1、AfterLoginActivity2三个Activity和其对应的xml文件。指定LoginActivity的launchMode为singleTask。其中用LoginActivity的xml模拟登录界面,而另外两个都只放一个button,用于实现跳转到另一个界面。
AndroidManifest.xml相关代码:
<activity
android:name=".LoginActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
activity_login.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
tools:context=".LoginActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="账号:"
android:textSize="20sp"
android:layout_gravity="center_vertical"/>
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密码:"
android:textSize="20sp"
android:layout_gravity="center_vertical"/>
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="login"
/>
</LinearLayout>
其他部分代码非常简单,就不贴了。整个程序的逻辑是从LoginActivity可以跳转到AfterLoginActivity1,从AfterLoginActivity2可以跳转到LoginActivity。
- 运行,观察结果
在AfterLoginActivity2中点击“SWITCH ACCOUNT”之后,程序切到了LoginActivity,但是我们可以发现,之前输入的姓名和密码都还存在,可见,这其实是直接使用的以前创建的LoginActivity。再使用返回键,程序直接退回到了桌面,更是说明这的确是初始是已经存在的那个LoginActivity,且其他两个Activity都已经出栈了,而不是将LoginActivity提到了栈顶。
其栈结构变化为:
- 程序启动:LoginActivity
- 点击跳转到After1:LoginActivity->AfterLoginActivity1
- 点击跳转到After2:LoginActivity->AfterLoginActivity1->LoginActivity2
- 点击跳转到LoginActivity:LoginActivity
- 点击返回:(退出程序)
4 singleInstance
singleInstance,名称是单例模式,也即,系统中只有那么一个实例。既然只有一个,那么也就说明很重要、很特殊咯,我们需要将其“保护起来”。安卓对单例模式的“保护措施”是将其单独放到一个任务栈中。
假设现在有A、B、C、D四个Activity,其中B为singleInstance模式,而其他是standard模式。A启动B,B启动C,C启动D,那么此时栈中的情况如何呢?我们说了,B需要单独放入一个栈中,所以这个时候会存在两个栈,一个中的内容为A->C->D,一个为B。现在我们已经在D活动了,依次按返回键销毁这些活动,那么C是否会返回B呢?答案是否定的,因为B和C不在一个栈中,一次会C无法返回B,而是直接返回A。从A返回会并不会直接退出程序,而是出现B。因为此时存在两个任务栈,第一个栈中的A、C、D均已经被销毁,系统就找到了另一个栈中的B,将B也销毁,才会完全退出程序。
不过我思考了很久,仍然还是没有想到什么使用singleInstacne很好的情况,所以就先不写实例了,等有时间再来补充一下。