Android Activity的生命周期详解

蓝咒 提交于 2019-12-04 04:31:30

Activity是由Activity栈进管理,当来到一个新的Activity后,此Activity将被加入到Activity栈顶,之前的Activity位于此Activity底部。Acitivity一般意义上有四种状态:

1.当Activity位于栈顶时,此时正好处于屏幕最前方,此时处于运行状态

2.当Activity失去了焦点但仍然对用于可见(如栈顶的Activity是透明的或者栈顶Activity并不是铺满整个手机屏幕),此时处于暂停状态

3.当Activity被其他Activity完全遮挡,此时此Activity对用户不可见,此时处于停止状态

4.当Activity由于人为或系统原因(如低内存等)被销毁,此时处于销毁状态

在每个不同的状态阶段,Adnroid系统对Activity内相应的方法进行了回调。因此,我们在程序中写Activity时,一般都是继承Activity类并重写相应的回调方法。

在上图中,Activity有三个关键的循环: 

1.Activity实例是由系统自动创建,并在不同的状态期间回调相应的方法。Activity在onCreate()设置所有的“全局”状态,在onDestory()释放所有的资源。一个最简单的完整的Activity生命周期会按照如下顺序回调:onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy。称之为entire lifetime(整个的生命周期)例如:某个Activity有一个在后台运行的线程,用于从网络下载数据,则该Activity可以在onCreate()中创建线程,在onDestory()中停止线程。

2.当执行onStart回调方法时,Activity开始被用户所见(也就是说,onCreate时用户是看不到此Activity的,那用户看到的是哪个?当然是此Activity之前的那个Activity),一直到onStop之前,此阶段Activity都是被用户可见,称之为visible lifetime(可见的生命周期)尽管有可能不在前台,不能和用户交互。在这两个接口之间,需要保持显示给用户的UI数据和资源等,例如:可以在onStart中注册一个IntentReceiver来监听数据变化导致UI的变动,当不再需要显示时候,可以在onStop()中注销它。onStart(),onStop()都可以被多次调用,因为Activity随时可以在可见和隐藏之间转换。

3.当执行到onResume回调方法时,Activity可以响应用户交互,一直到onPause方法之前,该Activity处于所有 Activity的最前面,和用户进行交互。此阶段Activity称之为foreground lifetime(前台的生命周期)Activity可以经常性地在resumed和paused状态之间切换,例如:当设备准备休眠时,当一个 Activity处理结果被分发时,当一个新的Intent被分发时。所以在这些接口方法中的代码应该属于非常轻量级的。

需要注意一下几点:

在实际应用场景中,假设A Activity位于栈顶,此时用户操作,从A Activity跳转到B Activity。那么对AB来说,具体会回调哪些生命周期中的方法呢?回调方法的具体回调顺序又是怎么样的呢?

开始时,A被实例化,执行的回调有A:onCreate -> A:onStart -> A:onResume。

当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。

此时如果点击Back键,将依次执行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。

至此,Activity栈中只有A。在Android中,有两个按键在影响Activity生命周期这块需要格外区分下,即Back键和Home键。我们先直接看下实验结果:

此时如果按下Back键,系统返回到桌面,并依次执行A:onPause -> A:onStop -> A:onDestroy。

此时如果按下Home键(非长按),系统返回到桌面,并依次执行A:onPause -> A:onStop。由此可见,Back键和Home键主要区别在于是否会执行onDestroy。

此时如果长按Home键,不同手机可能弹出不同内容,Activity生命周期未发生变化(由小米2s测的,不知道其他手机是否会对Activity生命周期有影响)。

由于Android本身的特性,使得现在不少应用都没有直接退出应用程序的功能,按照一般的逻辑,当Activity栈中有且只有一个Activity时,当按下Back键此Activity会执行onDestroy,那么下次点击此应用程图标将从重新启动,因此,当前不少应用程序都是采取如Home键的效果,当点击了Back键,系统返回到桌面,然后点击应用程序图标,直接回到之前的Activity界面,这种效果是怎么实现的呢?

通过重写按下Back键的回调函数,转成Home键的效果即可。

@Override
public void onBackPressed() {
    Intent home = new Intent(Intent.ACTION_MAIN);
    home.addCategory(Intent.CATEGORY_HOME);
    startActivity(home);
}

当然,此种方式通过Home键效果强行影响到Back键对Activity生命周期的影响。注意,此方法只是针对按Back键需要退回到桌面时的Activity且达到Home效果才重写。

或者,为达到此类效果,Activity实际上提供了直接的方法。

activity.moveTaskToBack(true);

moveTaskToBack()此方法直接将当前Activity所在的Task移到后台,同时保留activity顺序和状态。


在之前的项目开发过程中,当时遇到一个很奇怪的问题:手机上的“开发者选项”中有一个“不保留活动”的设置,当开启此设置,手机上的设置提示是“用户离开后即销毁每个活动”,开启后,对于其他的应用程序是从A Acticity到B Activity,然后Back键回到A,此时,其他应用程序只是先白屏(有可能黑屏等,取决于主题设置)一下,然后A开始可见,但是我的应用程序中出现的一个结果却是直接返回到了桌面。一开始百思不得其解。最后终于定位出问题。首先,我们需要明确开启此设置项后对Activity生命周期的影响。开启此设置项后,当A到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop -> A:onDestroy。是的,A在系统原本的生命周期回调中增加了onDestroy。此即“用户离开后即销毁每个活动”的含义。但此时需要注意的是,只要没有认为的调用A的finish()方法,虽然A执行了onDestroy,但Activity栈中依然保留有A,此时B处于栈顶。那么在B中按Back键回到A时,将依次执行:B:onPause -> A:onCreate -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。没错,A从onCreate开始执行了。此处也就解释了为什么A可能会出现白屏(或黑屏等)一下的原因了。

那么为什么我的应用程序会跟其他应用程序出现不一样呢?最后定为出问题在于当时我的应用程序中为了做到完全退出应用程序效果,专门使用了一个Activity栈去维护Activity(当时是借鉴了网上的此类实现方案,现在想想,实在没必要,且不说Android本身特性决定了没必要通过如此方法去达到退出效果,仅仅是此方法本身也存在很大的问题,现在在网上依然能见到有不少文章说到应用程序退出可以使用此方法,哎。。),在onCreate中入栈,onDestroy出栈,调用了如下方法:

// 结束Activity&从堆栈中移除
AppManager.getAppManager().finishActivity(this);

其中,AppManager中finishActivity函数具体定义是:

/**
 * 结束指定的Activity
 */
public void finishActivity(Activity activity) {
    if (activity != null) {
        activityStack.remove(activity);
        activity.finish();
        activity = null;
    }
}

至此,相信大家应该看出问题的所在了吧。

没错,问题在于执行了activity的finish()方法!! activity的finish()方法至少有两个层面含义,1.将此Activity从Activity栈中移除,2.调用了此Activity的onDestroy方法。对于不开启“不保留活动”的设置项,实际上也没什么影响,但是一旦开启此设置,问题显露无疑。开启此此设置后,正常情况下离开A,即使执行了A的onDestroy,Activity栈中还是有A的,但是我这样写后,finish()方法一执行,Activity栈中就没有A了,因此,当点击Back键时,Activity栈中已经没有此应用的任何Activity了,直接来到了手机桌面。

可能,有些人会说,我就是要通过此种方法想去完全退出应用程序,同时希望自己的Activity栈和系统中Activity栈保持一致,怎么办呢?

在此,可以通过如下改写去实现:

/**
* 结束指定的Activity
 */
public void finishActivity(Activity activity) {
    if (activity != null) {
    // 为与系统Activity栈保持一致,且考虑到手机设置项里的"不保留活动"选项引起的Activity生命周期调用onDestroy()方法所带来的问题,此处需要作出如下修正
    if(activity.isFinishing()){
        activityStack.remove(activity);
        //activity.finish();
        activity = null;
    }
    }
}

下面通过一个例子来介绍:

public class ActivityDemo extends Activity {  
    private static final String TAG = "ActivityDemo";  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        Log.e(TAG, "start onCreate~~~");  
    }  
    @Override  
    protected void onStart() {  
        super.onStart();  
        Log.e(TAG, "start onStart~~~");  
    }  
    @Override  
    protected void onRestart() {  
        super.onRestart();  
        Log.e(TAG, "start onRestart~~~");  
    }  
    @Override  
    protected void onResume() {  
        super.onResume();  
        Log.e(TAG, "start onResume~~~");  
    }  
    @Override  
    protected void onPause() {  
        super.onPause();  
        Log.e(TAG, "start onPause~~~");  
    }  
    @Override  
    protected void onStop() {  
        super.onStop();  
        Log.e(TAG, "start onStop~~~");  
    }  
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        Log.e(TAG, "start onDestroy~~~");  
    }  
}

我们打开应用时先后执行了onCreate()->onStart()->onResume()三个方法,看一下LogCat视窗如下:

当我们按BACK键时,我们这个应用程序将结束,这时候我们将先后调用onPause()->onStop()->onDestory()三个方法,如下图所示:

当我们打开应用程序时,比如浏览器,我正在浏览NBA新闻,看到一半时,我突然想听歌,这时候我们会选择按HOME键,然后去打开音乐应用程序,而当我们按HOME的时候,Activity先后执行了onPause()->onStop()这两个方法,这时候应用程序并没有销毁。如下图所示:

而当我们再次启动ActivityDemo应用程序时,则先后分别执行了onRestart()->onStart()->onResume()三个方法,如下图所示:

这里我们会引出一个问题,当我们按HOME键,然后再进入ActivityDemo应用时,我们的应用的状态应该是和按HOME键之前的状态是一样的,同样为了方便理解,在这里我将ActivityDemo的代码作一些修改,就是增加一个EditText。然后再次运行ActivityDemo程序,在EditText里输入如"Frankie"字符串(如下图:)

这时候,大家可以按一下HOME键,然后再次启动ActivityDemo应用程序,这时候EditText里并没有我们输入的"Frankie"字样,如下图:

这显然不能称得一个合格的应用程序,所以我们需要在Activity几个方法里自己实现,修改ActivityDemo.java代码如下:

public class ActivityDemo extends Activity {  
     private static final String TAG = "ActivityDemo";  
     private EditText mEditText;  
     //定义一个String 类型用来存取我们EditText输入的值  
     private String mString;  
     public void onCreate(Bundle savedInstanceState) {  
         super.onCreate(savedInstanceState);  
         setContentView(R.layout.main);  
         mEditText = (EditText)findViewById(R.id.editText);  
         Log.e(TAG, "start onCreate~~~");  
     }  
     @Override  
     protected void onStart() {  
         super.onStart();  
         Log.e(TAG, "start onStart~~~");  
     }  
     //当按HOME键时,然后再次启动应用时,我们要恢复先前状态  
     @Override  
     protected void onRestart() {  
         super.onRestart();  
         mEditText.setText(mString);  
         Log.e(TAG, "start onRestart~~~");  
     }   
     @Override  
     protected void onResume() {  
         super.onResume();  
         Log.e(TAG, "start onResume~~~");  
     }  
     //当我们按HOME键时,我在onPause方法里,将输入的值赋给mString  
     @Override  
     protected void onPause() {  
         super.onPause();  
         mString = mEditText.getText().toString();  
         Log.e(TAG, "start onPause~~~");  
     }   
     @Override  
     protected void onStop() {  
         super.onStop();  
         Log.e(TAG, "start onStop~~~");  
     }  
       
     @Override  
     protected void onDestroy() {  
         super.onDestroy();  
         Log.e(TAG, "start onDestroy~~~");  
     }  
 }

重新运行ActivityDemo程序,重复第五步操作,当我们按HOME键时,再次启动应用程序时,EditText里有上次输入的"Frankie"字样,如下图如示:

OK,大功基本告成,这时候大家可以在回上面看一下Activity生命周期图,我想大家应该完全了解了Activity的生命周期了,不知道你了解了没?

最后再用一个实际的例子来说明Activity的各个生命周期。假设有一个程序由2个Activity A和B组成,A是这个程序的启动界面。当用户启动程序时,Process和默认的Task分别被创建,接着A被压入到当前的Task中,依次执行了 onCreate, onStart, onResume事件被呈现给了用户;此时用户选择A中的某个功能开启界面B,界面B被压入当前Task遮盖住了A,A的onPause事件执行,B的 onCreate, onStart, onResume事件执行,呈现了界面B给用户;用户在界面B操作完成后,使用Back键回到界面A,界面B不再可见,界面B的onPause, onStop, onDestroy执行,A的onResume事件被执行,呈现界面A给用户。此时突然来电,界面A的onPause事件被执行,电话接听界面被呈现给用户,用户接听完电话后,又按了Home键回到桌面,打开另一个程序“联系人”,添加了联系人信息又做了一些其他的操作,此时界面A不再可见,其 onStop事件被执行,但并没有被销毁。此后用户重新从菜单中点击了我们的程序,由于A和其所在的进程和Task并没有被销毁,A的onRestart 和onStart事件被执行,接着A的onResume事件被执行,A又被呈现给了用户。用户这次使用完后,按Back键返回到桌面,A的 onPause, onStop被执行,随后A的onDestroy被执行,由于当前Task中已经没有任何Activity,A所在的Process的重要程度被降到很低,很快A所在的Process被系统结束。

补充一点,当前Activity产生事件弹出ToastAlertDialog的时候Activity的生命周期是不会有改变,即不会触发onPause()等事件


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!