学习Android之路_活动的生命周期、启动模式_第三天

先从看得到的入手——探究活动

2.3活动的生命周期

掌握活动周期对开发者来说非常重要,当你深入了解并理解活动周期之后,就可以写出更加连贯流畅的程序,并在如何合理管理应用资源方面发展得游刃有余。你的应用程序会拥有更好的体验感。

2.3.1返回栈

你会发现,活动是可以层叠的。启动一个新的活动,就会覆盖在原活动上,点击Back键就会销毁最上面的活动,下面的一个活动就会显示出来。
其实Android是使用任务来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈。栈是一个先进后出的数据结构,在默认的情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈这时前一个人入栈的活动会重新处于栈顶的位置。系统总是显示栈顶的活动给用户。
返回栈工作示意图:
学习Android之路_活动的生命周期、启动模式_第三天

2.3.2活动状态

活动状态有四种。

  1. 运行状态:当一个活动位于返回栈的栈顶时,这个活动就是处于运行状态。系统最不愿意回收的就是处于运行状态的活动。
  2. 暂停状态:当一个活动不再处于栈顶的位置,但任然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还做么会可见呢?这时因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动,只会占用屏幕中间的部分区域,后面我们就会看到这种活动。属于暂停状态的活动任然是完全存活的,系统也不愿意去回收这种活动,只有在内容极地的情况下,系统才会去回收这种活动。
  3. 停止状态:当一个活动不再处于栈顶的位置,并完全不可见的时候,就进入了停止状态。系统任然会为这种活动保存相应的状态和成员变量,但这并不是一定会保存的,当其他地方需要用到内存的时候,处于停止状态的活动有可能会被系统回收。
  4. 销毁状态:当一个活动从返回栈中移除后就变成了销毁状态。系统会最先回收这种状态的活动,从而保证手机内存充足。

2.3.3活动的生存期

Activity类中定义了7个回调方法,覆盖了活动生命周期的每一个环节,下面就来一一介绍这7个方法。

  • onCreate():这个方法想必你也见过多次了,每个活动我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如加载布局,绑定事件。
  • onStart():这个方法在活动由不可见变为可见时调用。
  • onResume():这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
  • onPause():这个方法在系统准备去启动或者恢复另一个活动时调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法执行速度一定要快,要不会影响新的栈顶活动的使用。
  • onStop():这个方法在活动完全不可见的时候调用。他和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法不会执行。
  • onDestroy():这个方法在活动被销毁之前调用,之后活动的状态变为销毁状态。
  • onRestart():这个方法在活动有停止状态变为运行状态之前调用,也就是活动被重新启动了。

以上七种方法除了onRestart()方法,其他都是两两相对的,从而又可以将活动氛围3种生存期。

  • 完整生存期:活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个活动会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存操作。
  • 可见生存期:活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,几遍有可能无法和用户进行交互。我们可以通过这两种方法,合理的管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
  • 前台生存期:活动在onResume()方法和onPause()方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态的,次时的活动是可以和用户进行交互的,我们平时看到和接触最多的就是这个状态下的活动。

2.3.4体验活动的生命周期

讲了那么多理论,是时候展现真正的技术了,下面我们来实践一下,让你更直观的体验活动的生命周期。
我们新建一个项目,先关闭之前的项目,然后新建一个ActivityLiftCycleTest项目,让AS自动帮我们创建活动和布局,选择默认Empty Activity模块。
主活动创建好之后,接下来创建两个子活动NormalActivity和DialogActivity。
右击com.erxiao.activitylifecycletest包New-Activity-Empty Activity,新建NormalActivity,布局起名为normal_layout。DialogActivity同样,布局dialog_layout。
现在我们打开normal_layout.xml文件,将代码如下代码增加到文件中:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a normal activity"
    />

打开dialog_layout.xml文件,将代码如下代码增加到文件中:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a dialog activity"
    />

两个布局文件只是显示内容不同。
接下来我们将dialog设置成对话框的形式,打开AndroidManifest.xml,修改成如下代码:

<activity android:name=".NormalActivity" >
</activity>
<activity android:name=".DialogActivity"
    android:theme="@style/Theme.AppCompat.Dialog">
</activity>

android:theme属性是设置主题,给当前活动设置指定的主题,这里的@android:style/Theme.Dialog是将这个活动设置成对话框式的主题。
接下来我们修改activity_main.xml,增加两个按钮:

<Button
    android:id="@+id/start_normal_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start NormalActivity" />

<Button
    android:id="@+id/start_dialog_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start DialogActivity"
    android:layout_marginTop="50dp"
    app:layout_constraintTop_toTopOf="parent"/>

两个按键一个用来启动NormalActivity,一个用来启动DialogActivity。
最后我们修改一下MainActivity代码:

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
        Button startDialotActivity = (Button) findViewById(R.id.start_dialog_activity);
        startNormalActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               Intent intent = new Intent(MainActivity.this,NormalActivity.class);
               startActivity(intent);
            }
        });
        startDialotActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,DialogActivity.class);
                startActivity(intent);
            }
        });
Log.d(TAG,"onCreate");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"onRestart");
    }
}

onCreate()方法中注册了两个点击事件,第一个按钮会打开NormalActivity,第二个按钮会打开DialogActivity。然后再Activity的7个回调方法中打印了一句话,这样更直观看出生命周期。
运行后界面如下:
学习Android之路_活动的生命周期、启动模式_第三天
按下START NORMALACTIVITY按钮,显示出新的页面:
学习Android之路_活动的生命周期、启动模式_第三天
按下START DIALOGACTIVITY按钮,显示出对话框,虽然不太美观但是功能都实现了:
学习Android之路_活动的生命周期、启动模式_第三天
接下来我们来看一下日志调试的内容:
当我们第一次打开程序的时候会依次执行onCreate()、onStart()、onResume()方法:
学习Android之路_活动的生命周期、启动模式_第三天
当我们按下START NORMALACTIVITY按钮:
学习Android之路_活动的生命周期、启动模式_第三天
由于NormalActivity已经完全吧MainActivity遮挡住了,所以会执行onPause和onStop方法。
然后按下Back键返回:
学习Android之路_活动的生命周期、启动模式_第三天
由于MainActivity已经进入停止状态,所以会依次执行onRestart、onStart、onResume方法,onCreate方法不会执行,因为MainActivity并没有重新创建。
按下START DIALOGACTIVITY按钮:
学习Android之路_活动的生命周期、启动模式_第三天
会执行onPause方法,而没有执行onStop方法,因为DialogActivity并没有完全遮挡住MainActivity,此时MainActivity只是进入暂停状态,并没有停止。
按下Back键:
学习Android之路_活动的生命周期、启动模式_第三天
会执行响应的onResume方法。
最后按下Back键退出程序:
学习Android之路_活动的生命周期、启动模式_第三天
可能是由于模拟器的问题,执行的内容与按下START NORMALACTIVITY按钮一样,没有执行销毁onDestroy方法,可能是模拟器只是认为返回桌面并没有销毁它。

2.3.5活动被回收了怎么办

进入停止系统的活动时有可能被系统回收的,那么想象一下。应用中有个活动A,在活动A的基础上打开了活动B,那么A进入停止状态,这时候由于内存不足,将活动A回收掉了,那么用户按下Back键返回活动A会出现什么情况呢?其实还是会正常显示活动A只是不会再执行onResume方法,而会执行onCreate方法,因为这种情况下活动A是被重新创建的。
这看上去好像没有问题,但是别忽略了一个重要的问题,如果活动A存在临时数据或状态。举个例子,活动A有个文本框,这是你输入了一些数据,然后你打开的活动B,活动A由于内存不足被回收,那么你按下Back键的时候活动A文本框中的文字会全部没掉了,因为活动A被重新创建了。
这时我们就要用到onSaveINstanceState回调方法,这个方法可以保证被回收之前一定会被调用,因此我们可以通过这个方法来解决一下被回收时临时数据得不到保存的问题。
onSaveInstanceState方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法保存数据,比如使用putString方法保存字符串,putInt方法保存整型数据,以此类推。每个保存方法需要传入两个参数,第一个是键值,主要为了后面取数据,第二个是要保存的内容。
接下来我们在MainActivity写一下代码:

public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you just typed";
    outState.putString("data_key",tempData);
}

数据保存了,但是我们要在哪里恢复呢?可以看到onCreate方法中也有一个Bundle类型的参数。这个参数一般情况下是null的,但是如果活动被系统收回时有通过onSaveInstanceState方法来保存数据,那么这个参数会带有之前保存的全部数据,具体取数据的方法如下:
在onCreate方法中增加:

if(savedInstanceState!=null){
    String tempData = savedInstanceState.getString("data_key");
    Log.d(TAG,tempData);
}

其实Intent和Bundle可以结合起来传递数据,首先可以把需要传递的数据保存在Bundle对象中,然后再结合Bundle对象存放在Intent里,到了活动目标将Intent中的Bundle数据取出。
我们来修改一下按钮NormalActivity的事件:

startNormalActivity.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
       Intent intent = new Intent(MainActivity.this,NormalActivity.class);
       Bundle outState = new Bundle();
       String tempData = "Something you just typed";
       outState.putString("data_key",tempData);
       intent.putExtra("bundle_data",outState);
       startActivity(intent);
    }
});

然后再修改NormalActivity活动的onCreate方法:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.normal_layout);
    Intent intent = getIntent();
    Bundle getState = intent.getBundleExtra("bundle_data");
    String tempData = getState.getString("data_key");
    Log.d("NormalActivity",tempData);
}

可以看到打印出了对应的数据。
学习Android之路_活动的生命周期、启动模式_第三天

2.4活动的启动模式

启动模式一共有四种:standard、singleTop、singleTask和singleInstance。可以在AndroidManifest.Xml中个标签指定android:laynchMode属性来选择启动模式。

2.4.1standard

Standard是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。因此,到目前为止我们写过的所有活动都是使用的standard模式。在之前的学习中,知道Android是使用返回栈入栈来管理活动的,在standard模式的活动下系统不会在乎这个活动是否已经在返回栈存在,每次启动都会创建改活动的一个新的实例。
我们来体验一下standard模式,这次选择在ActivityTest项目基础上修改,先将ActivityLifeCycleTest项目关闭,打开ActivityTest项目。
修改FristActivity中onCreate方法的代码,如下所示:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("FristActivity",this.toString());
    setContentView(R.layout.frist_layout);
    Button button1 = (Button) findViewById(R.id.button_1);
    button1.setOnClickListener(new View.OnClickListener(){
       @Override
       public void onClick(View v){
           Intent intent = new Intent(FristActivity.this,FristActivity.class);
           startActivity(intent);
       }
    });
}

可以看到代码有点奇怪,在FristActivity基础上启动FristActivity,从逻辑上来讲没有什么意义,不过我们重点是为了研究standard模式,因此不必在意这段话的逻辑性。另外我们还在onCreate中添加了一行打印信息,用于打印当前活动的实例。
现在启动程序,按下两次按钮,可以看到打印信息如下:
学习Android之路_活动的生命周期、启动模式_第三天
可以看到打印出了三条实例。这时我们需要按三下Back才能退出程序。
我们来看一下standard模式的原理示意图:
学习Android之路_活动的生命周期、启动模式_第三天

2.4.1singleTop

是不是觉得standard模式不合理,活动明明已经在栈顶了,为什么再次启动的时候还要创建出一个实例?别着急,这只是系统默认的一种启动模式而已,我们完全可以按照自己的要求修改程序,比如说使用singleTop模式。当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是改活动,则直接使用它,不会再进行创建新的实例了。
接下来我们来修改一下AndroidManifest程序FristActivity启动模式,添加一条如下代码,如下所示:
学习Android之路_活动的生命周期、启动模式_第三天
运行程序,你会发现打印出了一条实例,但是不管你按多少次按钮他都不会再打印出信息,因为FristActivity已经处于返回栈的栈顶,当想再启动一个FristActivity是都会直接使用栈顶的活动,因此FristActivity也只会有一个实例,仅按一次Back键就可以退出程序。
学习Android之路_活动的生命周期、启动模式_第三天
不过当FristActivity不处于返回栈的栈顶,再次启动FristActivity是还是会创建出一个实例,下面我们来实验一下。
修改FristActivity中的onCreate方法,如下所示:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("FristActivity",this.toString());
    setContentView(R.layout.frist_layout);
    Button button1 = (Button) findViewById(R.id.button_1);
    button1.setOnClickListener(new View.OnClickListener(){
       @Override
       public void onClick(View v){
           Intent intent = new Intent(FristActivity.this,SecondActivity.class);
           startActivity(intent);
       }
    });
}

然后再打开SecondActivity修改onCreate方法,代码如下所示:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("SecondActivity",this.toString());
    setContentView(R.layout.second_layout);
    Button button2 = findViewById(R.id.button_2);
    button2.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v){
            Intent intent = new Intent(SecondActivity.this,FristActivity.class);
            startActivity(intent);
        }
    });
}

这时我,运行程序,我们在FristActivity按下按钮,在SecondActivity按下按钮,可以看到优惠重新进入到FristActivity中:
学习Android之路_活动的生命周期、启动模式_第三天
可以看到系统创建了两个不同的FristActivity实例,这是由于SecondActivity中再次启动FristActivity时,栈顶活动已经变成了SecondActivity,因此会创建一个新的FristActivity实例。现在按下Back键会返回到SecondActivity,再次按下Back键又会回到FristActivity中,再按一次才退出程序。
singleTop模式的原理示意图:
学习Android之路_活动的生命周期、启动模式_第三天

2.4.3singleTask

刚才的两种模式似乎达不到程序只有一个实例。接下来我们就要借助singleTask模式来实现程序只有一个实例了。当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中间检查是否存在该活动的实例,如果发现以存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有就会创建一个新的活动实例。
我们还是通过代码来更直观的了解吧。修改AndroidManifest中的FristActivity的启动模式:
学习Android之路_活动的生命周期、启动模式_第三天
然后在FristActivity中添加onRestart(启动)方法,并打印日志:

protected void onRestart() {
    super.onRestart();
    Log.d("FristActivity","onRestart");
}

最后再SecondActivity中添加onDestroy(销毁)方法,并打印日志:

protected void onDestroy() {
    super.onDestroy();
    Log.d("SecondActivity","onDestroy");
}

现在我们运行程序,在FristActivity界面电机按钮进入到SecondActivity,然后在SecondActivity界面点击按钮,优惠重新进入到FristActivity。
查看打印日志:
学习Android之路_活动的生命周期、启动模式_第三天
可以看到SecondActivity启动FristActivity时会发现返回栈中已经存在一个FristActivity的实例,并且在SecondActivity下面,预算SecondActivity会从返回栈中出栈,二FristActivity重新成为栈顶活动,因此FristActivity的onStart方法和SecondActivity的onDestroy方法会得到执行。现在返回栈中只剩下FristActivity的实例了,按下Back键就可以退出程序。
singleTask模式的原理示意图:
学习Android之路_活动的生命周期、启动模式_第三天

2.4.4singInstance

singInstance模式应该算是4种模式中最为复杂的一个了,你也需要多花点功夫来理解这个模式。不同于上面的3个模式,指定为singleInstance模式的活动会启用宇哥新的返回栈来管理这个活动。这么做有什么意义呢?我们来设想一下,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序共享这个活动的实例,应该如何实现?前面3种启动模式肯定是做不到,因为每个应用程序都会有自己的返回栈,同一个活动在痛的返回栈入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
为了更好的理解这种启动模式,我们还是来实践一下,修改AndroidManifest中SecondActivity的启动模式:
学习Android之路_活动的生命周期、启动模式_第三天
然后修改FristActivity中onCreate方法的代码:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("FristActivity","Task id is"+ getTaskId());
    setContentView(R.layout.frist_layout);
    Button button1 = (Button) findViewById(R.id.button_1);
    button1.setOnClickListener(new View.OnClickListener(){
       @Override
       public void onClick(View v){
           Intent intent = new Intent(FristActivity.this,SecondActivity.class);
           startActivity(intent);
       }
    });
}

在onCreate方法中打印了当前返回栈的id。然后修改SecondActivity中onCreate方法的代码:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("SecondActivity","Task id is"+ getTaskId());
    setContentView(R.layout.second_layout);
    Button button2 = findViewById(R.id.button_2);
    button2.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v){
            Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
            startActivity(intent);
        }
    });
}

同样的在onCreate方法中打印当前的返回栈的id,然后修改了按钮点击事件的代码,启动了ThirdActivity。最后修改ThirdActivity中的onCreate方法的代码:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("ThirdActivity","Task id is"+ getTaskId());
    setContentView(R.layout.third_layout);
}

仍然是打印返回栈的id。
现在运行一下程序,在FristActivity点击按钮进入SecondActivity,在SecondActivity点击按钮进入ThirdActivity。
查看打印内容:
学习Android之路_活动的生命周期、启动模式_第三天
可以看到SecondActivity不同于FristActivity和ThirdActivity,这说明SecondActivity确实是存放在单独的一个返回栈,而这个返回栈只有SecondActivityige这一个活动。
然后我们按下Back键进行返回,你会发现ThirdActivity直接返回到了FristActivity,再按下Back键又返回到了SecondActivity,再按下Back才会退出程序,这是为什么呢?原因很简单,由于FristActivity和ThirdActivity是存放在一个返回栈里的,当在ThirdActivity的界面按下Back键,ThirdActivity会从返回栈中出栈,那么FristActivity就会成为了栈顶活动显示在界面上,然后FristActivity再次按下Back键,这是当前返回栈已经空了,于是就显示了另一个返回栈的栈顶活动,即SecondActivity。再次按下Back键,这时所有返回栈都已经空了,也就自己退出了程序。
singleInstance模式的原理示意图:
学习Android之路_活动的生命周期、启动模式_第三天

2.5活动的最佳实践

到现在已经掌握了很多关于活动的知识,不过恐怕离灵活运用还差一段距离。虽然知识只有这么多,但运用的技巧却是多种多样,所以,在这里我准备使用几种关于活动的最佳实践技巧,这些技巧在以后开发工作中会非常受用。

2.5.1知晓当前是在哪一个活动

这个技巧将会让我们知道如何根据当前的界面就能判断出是哪一个活动。可能你会觉得挺纳闷的,我们自己写的代码怎么会不知道这是哪一个活动呢?很不幸的是,在你真正进入到企业之后,更有可能接收别人写的代码,因为你刚进公司就正好有一个新的项目这并不太可能。阅读别人的代码是一件头疼的问题,就是当你需要在某个界面上修改一些非常简单的东西时,却半天找不到这个界面对应的活动是哪一个。学会了这个技巧后,这对你来说就不是难题了。
还是在ActivityTest项目上修改,手下你需要新建一个类。右击com.erxiao.activitytest包-New-Java Class,弹出的窗口输入BaseActivity,如下图:
学习Android之路_活动的生命周期、启动模式_第三天
然后编写BaseActivity代码:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity",getClass().getSimpleName());
    }
}

继承AppCompatActivity类,并重写onCreate方法,获取实例的类名,并打印出来。
接下来我们需要让BaseActivity陈玮ActivityTest项目中所有活动的父类。
修改FristActivity、SecondActivity和ThirdActivity的继承构造,让他们不再继承AppCompatActivity,而是继承BaseActivity。由于BaseActivity是继承AppCompatActivity所以活动功能不会受到影响。
运行程序,然后用按钮分别进入FristActivity、SecondActivity和ThirdActivity的界面中,这时观察打印的情况:
学习Android之路_活动的生命周期、启动模式_第三天
现在每当我们进入一个活动界面的时候,该活动的类名就会打印出来,这样我们就可以时时刻刻知道当前界面对应的是哪一个活动了。

2.5.2随时随地退出程序

目前手机界面停留在ThirdActivity时,你需要按3次Back键才能退出程序。按Home键只是将程序挂起,并没有退出。如果我们需要主要或者退出的功能怎么办?必须要有一个随时都能退出的方案。
其实解决思路很简单,只需要用一个专门的集合类对所有的活动进行管理了就可以了,下面我么就来实现以下。
新建一个ActivityCollector类作为活动管理器,右击com.erxiao.activitytest包-New-Java Class,代码如下:

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();
            }
        }
android.os.Process.killProcess(android.os.Process.myPid());
    }
}

在活动管理器中,我们通过一个List来暂存活动,然后提供了一个addActivity方法用于向List中添加一个活动,提供了一个removeActivity方法用于从List中移除活动,最后提供finishAll方法用于将List中存储的活动全部销毁掉,其中android.os.Process/killProcess(android.os.Pricess.myPid())用于杀掉一个进程,它接收一个进程的id参数,这个方法只能杀掉当前程序的进程,不能杀掉其他程序的进程。
接下来修改BaseActivity中的代码,如下所示:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
}

在BaseActivity的onCreate方法中调用了ActivityCollector的addActivity方法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity中重写onDestroy方法,并调用了ActivityCollector的removetctivity方法,表明将一个马上要销毁的活动从活动管理器移除。
从此以后不管从程序的哪个地方想退出程序只要调用ActivityCollector.finishAll方法就可以了。例如在ThirdActivity界面想通过点击按钮退出程序,代码更改如下:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("ThirdActivity","Task id is"+ getTaskId());
    setContentView(R.layout.third_layout);
    Button button3 = (Button)findViewById(R.id.button_3);
    button3.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ActivityCollector.finishAll();
        }
    });
}

2.5.3启动活动的最佳写法

比如SecondActivity中需要传递非常重要的字符串参数,在启动启动SecondActivity必须要传递过来,我们一般会写成:
学习Android之路_活动的生命周期、启动模式_第三天
这样写是完全正确的。但是如果这个活动不是你开发的,而你只需要负责传递数据的部分,那么你却不清楚要传递的数据有哪些。这时候你就需要这样写:
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);
}
直接写出一个方法,然后直接调用这个方法。
养成一个良好的习惯,给你编写的每个活动都添加类似的启动方法,这样不仅可以让活动变得非常简单,还可以节省不少同事过来询问你的时间。

总结:

学习真的很疲劳,坐着大半天了,不过收获挺多的,学会了活动的模式、活动的生命周期状态,以及活动的启动模式几乎学习了活动的重要知识。
不过这才是刚刚开始,后面需要学习的东西还有很多,也许会比现在还要累,但是只要坚持,那都不是事!加油!