Activity的启动模式
在实际项目中需要根据特定的需求为每一个Activity指定恰当的启动模式。
启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性选择启动模式
- standard
standard是Activity默认的启动模式,在不进行显示指定的情况下,所有Activity都使用这种启动模式。由于Android是使用返回栈来管理Activity的,在standard(即默认情况)下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建该Activity的一个新的实例
在代码中观察一下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity", this.toString());
setContentView(R.layout.first_layout);
Button button1 = (Button)findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
// 运行结果
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@bf411c9
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@a01da18
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@c439963
代码看起来有点奇怪,在FirstActivity的基础上启动FirstActivity。从逻辑上讲没什么意义,但对于研究standard模式会很有用,通过在onCreate()添加打印运行的结果来看,每次点击按钮都会创建一个新的FirstActivity实例。此时返回栈也会存在3个FirstActivity的实例,因此需要连续按3次Back键才能退出程序
standard模式的原理示意图:
- singleTop
可能会觉得standard模式不太合理,命名已经在栈顶了为什么启动的时候还要创建一个新的实例呢,这只是系统默认的一种启动模式而已,完全可以根据自己的需要进行修改。当Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的Activity实例
先修改一下AndroidManifrst.xml中FirstActivity的启动模式,如下所示:
<activity android:name=".FirstActivity"
android:label="This is FirstActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
观察按钮点击后的输出
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity", this.toString());
setContentView(R.layout.first_layout);
Button button1 = (Button)findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
// 打印结果
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@77e7e6c
运行结果显示不管点击多少次按钮都不会再有新的信息出现,因为此时FirstActivity已经处于返回栈栈顶,每当想再启动一个FirstActivity的时候都会直接使用栈顶的Activity,因此FirstActivity也只会有一个实例,仅按一次Back键即可退出程序。
不过当FirstActivity并未处于栈顶位置的时候,这时再启动FirstActivity,还是会创建新的实例,可以通过以下代码进行演示。
创建SecondActivity,让FirstActivity点击按钮进入SecondActivity,在SecondActivity中的按钮点击又进入FirstActivity
FirstActivity中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity", this.toString());
setContentView(R.layout.first_layout);
Button button1 = (Button)findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
在SecondActivity中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("SecondActivity", this.toString());
setContentView(R.layout.second_layout);
Button button2 = (Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intet = new Intent(SecondActivity.this, FirstActivity.class);
startActivity(intet);
}
});
}
运行结果:
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@ee8ec35
// D/SecondActivity: com.zntq.xietao.activitytest.SecondActivity@ecd00ce
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@a01da18
从运行结果可以看出创建了两个不同的FirstActivity实例,这是由于在SecondActivity中再次启动FirstActivity时,栈顶Activity是SecondActivity,因此会创建一个新的FirstActivity实例。现在按下Back返回键会返回到SecondActivity,再次按下Back键又返回FirstActivity,再按一次Back键才会退出程序。
singleTop模式的原理示意图:
- singleTask
使用singleTop模式可以很好的解决重复创建栈顶Activity的问题,但是正如上边看到的,如果该Activity并没有处于栈顶还是会创建多个Activity实例的,那么有没有可以让某个Activity在整个应用程序的上下文中只存在一个实例呢?这就需要借助singleTask模式来实现。
当Activity的启动模式指定为singleTask的时候,每次启动该Activity时系统首先会在栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用这个实例,并把在这个Activity上的所有Activity统统出栈,如果没有发现就会创建一个新的Activity实例。还是通过下边的代码来看一下
修改AndroidManifest.xml中FirstActivity的启动模式:
<activity
android:name=".FirstActivity"
android:label="This is FirstActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
然后在FirstActivity中添加onRestart()方法,并打印日志:
@Override
protected void onRestart() {
super.onRestart();
Log.d("FirstActivity", "onRestart");
}
最后在SecondActivity中添加onDestroy()方法,并打印日志:
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("SecondActivity", "onDestroy");
}
重新运行程序,在FirstActivity界面点击按钮进入SecondActivity,然后在SecondActivity界面点击按钮又会重新进入到FirstActivity
// 打印结果
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@ee8ec35
// D/SecondActivity: com.zntq.xietao.activitytest.SecondActivity@ecd00ce
// D/FirstActivity: onRestart
// D/SecondActivity: onDestroy
从打印结果可以看出,在SecondActivity中启动FirstActivity时,会发现返回栈中已经存在一个FirstActivity的实例了,并且是在SecondActivity的下面,于是SecondActivity会从返回栈中出栈,而FirstActivity重新成为栈顶Activity,因此FirstActivity的onRestart()方法和SecondActivity的onDestroy()方法会得到执行。现在返回栈中支撑下一个FirstActivity的实例了,按一下Back键就可以退出程序。
singleTask模式的原理示意图:
- singleInstance
singleInstance模式是4种启动模式中最特殊也是最复杂的一个。不同于以上3种启动模式,指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity。这样做的意义在于当程序中有一个Activity是允许其他程序调用的,那么想实现其他程序和我们的程序可以共享这个Activity的实例,就可以通过singleInstance模式来实现了,而其他3种模式均无法做到,因为每个应用程序都有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然是创建了新的实例。singleInstance这种模式会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用同一个返回栈,也就解决了共享Activity实例的问题。同样通过以下代码来验证。
修改AndroidManifest.xml中SecondActivity的启动模式,同时创建ThirdActivity
<activity android:name=".SecondActivity"
android:launchMode="singleInstance">
</activity>
修改FirstActivity中onCreate()方法的代码,打印当前返回栈的id
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity", "Task id is" + this.getTaskId());
setContentView(R.layout.first_layout);
Button button1 = (Button)findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
然后修改SecondActivity中的onCreate()方法,打印当前返回栈的id,同时点击按钮启动ThirdActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("SecondActivity", "Task id is" + this.getTaskId());
setContentView(R.layout.second_layout);
Button button2 = (Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intet = new Intent(SecondActivity.this, ThirdActivity.class);
startActivity(intet);
}
});
}
最后修改ThirdActivity中的onCreate()方法,同样打印当前返回栈的信息
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ThirdActivity", "Task id is" + this.getTaskId());
setContentView(R.layout.third_layout);
}
运行结果
// 打印结果
// D/FirstActivity: Task id is17
// D/SecondActivity: Task id is18
// D/ThirdActivity: Task id is17
通过运行结果可以看到,SecondActivity的Task id不同于FirstActivity和ThirdActivity,这说明SecondActivity确实是存放在一个单独的返回栈里,而且这个返回栈只有SecondActivity这一个Activity
然后按下Back键进行反悔,会发现ThirdActivity竟然直接返回到了FirstActivity,再按下Back返回键又返回到SecondActivity,再按下Back键才会退出程序。这个原因其实也很简单,由于FirstActivity和ThirdActivity是存放在一个返回栈中的,当在ThirdActivity中按下Back键,ThirdActivity从返回栈中出栈,那么FirstActivity久成为了栈顶Activity显示在界面上,因此就出现了从ThirdActivity直接返回到FirstActivity的情况。然后在FirstActivity界面再次按下Back键,这时当前的返回栈已经空了,于是就显示了另一个返回栈的栈顶Activity,即SecondActivity。最后再按下Back键,这时所有的返回栈都已经空了,也就自然退出程序了。
singleInstance模式的原理示意图:
Activity的最佳实践
知晓当前是在哪一个Activity
这个技巧将会教会你如何根据程序当前的界面就能判断出是哪一个Activity
创建一个BaseActivity类,因为不需要让BaseActivity在AndroidManifest.xml中注册,因此创建一个普通的Java类就可以了,然后让BaseActivity继承自AppCompatActivity,并重写onCreate()方法,如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
在BaseActivity中的onCreate()方法中获取了当前实例的名字,并通过Log打印出来。接下来让FirstActivity和SecondActivity继承自BaseActivity。由于BaseActivity又是继承自AppCompatActivity的,所以项目中所有Activity的现有功能不会受影响,仍然完全继承了Activity中的所有特性.
打印结果
D/BaseActivity: FirstActivity
D/BaseActivity: SecondActivity
现在每当我们进入到一个Activity的界面,该Activity的类名就被打印出来,这样就可以时时知晓当前界面对应的哪一个Activity了。
随时随地退出程序
当页面停留比较深的时候,想退出程序需要按下多次Back键。按HOME键只是把程序挂起来,并没有真正退出程序,因此需要有一个随时随地都能退出程序的方案,思路就是用一个专门的集合类对所有的Activity进行管理就可以了。
新建一个ActivityController类作为Activity管理器,代码如下:
public class ActivityController {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity) {
activities.add(activity);
}
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
public static void finishAll() {
for (Activity activity: activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
在活动管理器中,通过一个List来暂存活动,然后提供一个addActivity()方法用于向List中添加一个Activity,提供了一个removeActivity()方法用于从List中移除Activity,最后提供了一个finishAll()方法用于将List中存储的活动全部销毁掉。
接下来修改BaseActivity中的代码如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
ActivityController.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityController.removeActivity(this);
}
}
在BaseActivity的onCreate()方法中调用了ActivityController的addActivity()方法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity中重写onDestroy()方法,并调用ActivityController的removeActivity()方法,表明将一个马上要销毁的Activity从Activity从活动管理器中移除。
以后无论想在任何地方退出程序,只需要调用ActivityController的finishAll()方法就可以了,如在SecondActivity中想要退出程序,只需要写如下代码就可以了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
// 给按钮注册点击事件,并在点击事件中添加返回数据的逻辑
Button button2 = (Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityController.finishAll();
}
});
}
当然还可以在销毁所有的代码后边加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程的代码如下:
android.os.Process.killProcess(android.os.Process.myPid());
其中,killProcess()方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过myPid()方法来获取当前程序的进程id。这个只能傻吊当前程序的进程,不能使用这个方法杀掉其他程序的进程。
启动Activity的最佳写法
启动Activity的方法之前已经说过,首先通过Intent构建出当前的“意图”,然后调用startActivity()或startActivityForResult()方法将Activity启动起来,如果有数据需要从一个Activity传递到另一个Activity,也可以借助Intent来完成。
假设SecondActivity中需要用到两个非常重要的字符串参数,在启动SecondActivity的时候必须要传递过来,那么在FirstActivity中很容易会写下如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button = (Button)findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);
}
});
}
这样写是完全OK的,只是如果在真正的项目开发中遇到SecondActivity并不是由你开发,但现在你负责的部分需要启动SecondActivity这个功能,而你却不清楚启动这个Activity需要传递哪些数据。这时,无非两种方法,一是自己阅读SecondActivity的代码,二是询问负责编写SecondActivity的同事,这样的话就会比较麻烦。现在换一种写法来解决以上问题。
修改SecondActivity中的代码:
public class SecondActivity extends BaseActivity {
public static void actionStart(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
...
}
在SecondActivity中添加了一个actionStart()方法,这个方法中完成了Intent的构建,另外所有SecondActivity中需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储到Intent中,最后调用startActivity()方法启动SecondActivity。
这样写的好处在于一目了然,SecondActivity所需要的数据在方法参数中全部体现出来了,这样即使不用阅读SecondActivity中的代码,不去询问负责SecondActivity开发的相关同事,也可以知道启动SecondActivity需要传递哪些数据。这样还简化了启动Activity的代码,现在在FirstActivity中只需要一行代码就可以启动SecondActivity了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button = (Button)findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
}
});
}
养成良好的习惯,给编写的每一个Activity都添加类似的启动方法,这样就可以让启动Activity变得非常简单,还可以节省不少有同事过来询问你的时间。