4.Android开发笔记:Activity的生命周期、启动方式、最佳实践
1.活动状态和生存期
4个状态,6个方法,3个生存期
状态:运行、暂停、停止、销毁
方法: onCreate、onResume、onPause、onStop、onDestroy、onRestart
生存期:
完整生存期 :
活动在 onCreate() 方法和 onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个活动会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作。
可见生存期 。
活动在 onStart() 方法和 onStop()方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
前台生存期 。活动在 onResume() 方法和 onPause()方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,我们平时看到和接触最多的也就是这个状态下的活动。
Android官方提供了一张活动生命周期的示意图,
Activity被回收了怎么办
场景:这样看上去好像一切正常,可是别忽略了一个重要问题,活动A中是可能存在临时数据和状态的。打个比方,MainActivity中有一个文本输入框,现在你输入了一段文字,然后启动NormalActivity,这时MainActivity由于系统内存不足被回收掉,过了一会你又点击了Back键回到MainActivity,你会发现刚刚输入的文字全部都没了,因为MainActivity被重新创建了。
如果我们的应用出现了这种情况,是会严重影响用户体验的,所以必须要想想办法解决这个问题。
方案:onSaveInstanceState()方法
Activity中还提供了一个onSaveInstanceState()回调方法,这个方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
onSaveInstanceState()方法会携带一个Bundle类型的参数,
Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,以此类推。每个保存方法需要传入两个参数,
第一个参数是键,用于后面从Bundle中取值,
第二个参数是真正要保存的内容。
public class FirstActivity extends BaseActivity {//AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
if (savedInstanceState != null){
String tempData= savedInstanceState.getString("key_tempData");
Log.d("savedInstanceState",tempData);
}
...
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Activity回收前保存的数据";
outState.putString("key_tempData", tempData);
Log.d("savedInstanceState", tempData);
}
....
不知道你有没有察觉,使用Bundle来保存和取出数据是不是有些似曾相识呢?没错!我们在使用Intent传递数据时也是用的类似的方法。这里跟你提醒一点,Intent还可以结合Bundle一起用于传递数据,首先可以把需要传递的数据都保存在Bundle对象中,然后再将Bundle对象存放在Intent里。到了目标活动之后先从Intent中取出Bundle ,再从 Bundle中一一取出数据。
2.活动的启动模式
standard
活动默认的启动模式
在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop
在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
修改AndroidManifest.xml中FirstActivity的启动模式:
<activity
android:name=".FirstActivity"
android:launchMode="singleTop"
...
singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。 我们还是通过代码来更加直观地理解一下。修改AndroidManifest.xml中FirstActivity的启动模式:
<activity
android:name=".FirstActivity"
android:launchMode="singleTask"
...
singleInstance模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动。那么这样做有什么意义呢?想象以下场景,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?使用前面3种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
修改AndroidManifest.xml中SecondActivity的启动模式:
<activity
android:name=".FirstActivity"
android:launchMode="singleInstance"
...
3.活动的最佳实践
(1)知晓当前是在哪一个活动
创建 BaseActivity,
首先需要新建一个BaseActivity类。右击com.example.activitytest包→New→Java Class,在弹出的窗口出输入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().getName());
}
}
其它所以Activity继承BaseActivity.
public class FirstActivity extends BaseActivity {//AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
.....
}
(2)随时随地退出程序
如果目前你手机的界面还停留在ThirdActivity,你会发现当前想退出程序是非常不方便的,需要连按3次Back键才行。按Home键只是把程序挂起,并没有退出程序。其实这个问题就足以引起你的思考,如果我们的程序需要一个注销或者退出的功能该怎么办呢?必须要有一个随时随地都能退出程序的方案才行。 其实解决思路也很简单,只需要用一个专门的集合类对所有的活动进行管理就可以了,下面我们就来实现一下。
1) 创建Activity管理类:
public class ActivityCollector {
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();
}
}
activities.clear();
}
}
2) 修改BaseActivity类:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity:", getClass().getName());
ActivityCollector.activities.add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
3)全部退出:
ActivityCollector.finishAll();
你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程的代码如下所示:
android.os.Process.killProcess(android.os.Process.myPid());
其中, killProcess()方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过myPid()方法来获得当前程序的进程id。需要注意的是,killProcess()方法只能用于杀掉当前程序的进程,我们不能使用这个方法去杀掉其他程序。
(3)启动活动的最佳写法
Activity自己封装启动它的方法,
public class SecondActivity extends BaseActivity {//AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Intent intent = getIntent();
String data1 = intent.getStringExtra("param1");
String data2 = intent.getStringExtra("param2");
Log.i("param1", data1);
Log.i("param1", data2);
Toast.makeText(SecondActivity.this, data1 + "/" + data2,Toast.LENGTH_LONG).show();
...
}
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);
}
...
}
其它地方调用该方法启动该 Activity:
public class FirstActivity extends BaseActivity {//AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this, "Button被点击", Toast.LENGTH_LONG)
.show();
SecondActivity.actionStart(FirstActivity.this,"参数1", "参数2");
}
});
}