Android四大组件——Activity

Activity

Activity是一种展示型组件,用于向用户直接地展示一个界面,并且可以接受用户的输入信息并进行交互。从代码的继承关系可以看出,Activity可以理解为一个带Theme的Context。

Activity

Activity生命周期

Activity生命周期
  • onCreate:第一个被调用的方法,负责创建和初始化Activity(加载布局资源和初始化数据),生命周期内只会被调用一次。
  • onRestart:重新启动,Activity从不可见重新变为可见状态。
  • onStart:正在启动,Activity已经可见但是不在前台,无法和用户进行交互。
  • onResume:Activity已经在前台,并可以跟用户进行交互。
  • onPause:正在停止,Activity开始转入后台。此时不可以做耗时工作,因为前一个Activity的onPause方法执行完后一个Activity的onResume方法才会执行。
  • onStop:Activity已经不可见,即将停止。
  • onDestroy:Activity即将销毁,处理资源的回收和释放。

注:是否能跟用户进行交互来区分Activity是在前台还是后台;UI是否显示来区分是否可见。onStart 和onStop 从是 否可见来回调的;onResume和onPause 从是否在前台来回调的。可见但非前台的常见例子:Activity弹出一个dialog,Activity可见但不可以和用户交互。

当资源相关的系统配置发生改变及系统内存不足时,Activity就可能被杀死。

  • 资源相关的系统配置发生改变导致Activity被杀死并重新创建

当系统配置发生改变后,Activity会被销毁再重建,其onPause、onStop和onDestroy均会被调用。同时,Activity有可能被杀死的时候系统会调用onSaveInstanceState来保存当前Activity的状态。该方法在onStop之前调用,与onPause没有先后关系。Activity被重新创建时系统会调用onRestoreInstanceState方法,在onStart后被调用。系统只会在Activity即将被销毁并且有机会重新显示的情况下才会去调用onSaveInstanceState方法。
同Activity一样,每个View都有onSaveInstanceState和onRestoreInstanceState方法,可以查看相关源码了解系统可以自动为每个View恢复哪些数据。保存和恢复View层次结构,系统工作流程:当Activity被意外终止,Activity首先会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window在委托它上面的顶级容器去保存数据(DecorView)。最后顶级容器在去一一通知他的子元素来保存数据。这个过程是一个典型的委托思想,上层委托下层、父容器委托子元素去做一件事情。View的绘制过程、事件的分发等都是采用类似思想。

  • 资源内存不足导致低优先级的Activity被杀死

Activity优先级由高到低:前台Activity>可见但非前台>后台不可见Activity。当系统内存不足时,就会根据上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState方法来备份和恢复数据。如果一个进程没有四大组件在执行,那么这个进程将很快被系统杀死。我们可以把后台工作放在service中执行,以保证具有一定的优先级,不会轻易的被系统杀死。

Google源码对onSaveInstanceState方法的注释中指出——只要Activity有可能被杀掉就会调用onSaveInstanceState方法来保存瞬时数据,比如旋转屏幕、按下home键、menu键或是启动了另一个Activity等场景都会触发onSaveInstanceState方法的调用。

下面通过打印的log来分析一下不同场景下Activity生命周期相关方法调用的顺序。

  • 启动
03-09 00:31:31.755 1549-1549/com.android.peter.activitydemo D/Activity: onCreate
03-09 00:31:31.759 1549-1549/com.android.peter.activitydemo D/Activity: onStart
03-09 00:31:31.770 1549-1549/com.android.peter.activitydemo D/Activity: onResume
  • Home键退出、menu键或是启动了另一个activity的情况
03-09 00:31:59.068 1549-1549/com.android.peter.activitydemo D/Activity: onPause
03-09 00:31:59.099 1549-1549/com.android.peter.activitydemo D/Activity: onSaveInstanceState
03-09 00:31:59.102 1549-1549/com.android.peter.activitydemo D/Activity: onStop
  • 再次进入
03-09 00:32:35.716 1549-1549/com.android.peter.activitydemo D/Activity: onRestart
03-09 00:32:35.717 1549-1549/com.android.peter.activitydemo D/Activity: onStart
03-09 00:32:35.719 1549-1549/com.android.peter.activitydemo D/Activity: onResume
  • 按Back键退出
03-09 00:33:54.916 1549-1549/com.android.peter.activitydemo D/Activity: onPause
03-09 00:33:55.322 1549-1549/com.android.peter.activitydemo D/Activity: onStop
03-09 00:33:55.325 1549-1549/com.android.peter.activitydemo D/Activity: onDestroy
  • 旋转屏幕
03-09 00:34:27.845 1549-1549/com.android.peter.activitydemo D/Activity: onPause
03-09 00:34:27.847 1549-1549/com.android.peter.activitydemo D/Activity: onSaveInstanceState    //跟onPause方法没有明确的先后关系,在onStop之前被调用
03-09 00:34:27.851 1549-1549/com.android.peter.activitydemo D/Activity: onStop
03-09 00:34:27.853 1549-1549/com.android.peter.activitydemo D/Activity: onDestroy
03-09 00:34:28.066 1549-1549/com.android.peter.activitydemo D/Activity: onCreate
03-09 00:34:28.069 1549-1549/com.android.peter.activitydemo D/Activity: onStart
03-09 00:34:28.070 1549-1549/com.android.peter.activitydemo D/Activity: onRestoreInstanceState    //在onStart之后被调用
03-09 00:34:28.077 1549-1549/com.android.peter.activitydemo D/Activity: onResume
  • 在onResume中调用finish
05-03 16:35:35.592 25819-25819/com.android.peter.handlerdemo D/MainActivity: onCreate
05-03 16:35:35.596 25819-25819/com.android.peter.handlerdemo D/MainActivity: onStart
05-03 16:35:35.603 25819-25819/com.android.peter.handlerdemo D/MainActivity: onResume
05-03 16:35:35.605 25819-25819/com.android.peter.handlerdemo D/MainActivity: finish
05-03 16:35:35.623 25819-25819/com.android.peter.handlerdemo D/MainActivity: onPause
05-03 16:35:36.380 25819-25819/com.android.peter.handlerdemo D/MainActivity: onStop
05-03 16:35:36.382 25819-25819/com.android.peter.handlerdemo D/MainActivity: onDestroy
  • 在最近任务中清除Activity会回调onDestroy方法,在应用管理界面强行停止应用不会回调onDestroy方法。

Activity启动模式

Activity有四种启动模式:

  • standard:标准模式,系统默认模式。典型的多实例,每个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。
    典型问题:在Service中启动一个Acitivity时经常会遇到无法启动并Crash的问题,出现提示:"Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?"。这是因为standard模式默认会启动他的Activity所属的任务栈,但是非Activity类型的Context并没有任务栈,导致此exception的抛出。解决这个问题的方法是在启动Activity的时候添加FLAG_ACTIVITY_NEW_TASK标志位,这样Activity被启动的时候会为它创建一个新的任务栈,这个时候待启动Activity实际上是以singleTask方式启动的。
  • singleTop:栈顶复用模式。如果新的Activity已经在任务栈的栈顶,Activity 将不会被重新创建。同时,它的onNewIntent方法会被回调,通过此方法的参数(intent中包含的信息)我们可以取出当前请求的信息。如果新Activity的实例已经存在但不在栈顶,那么新Activity仍然会被重建。
  • singleTask:栈内复用模式。比如启动Activity A,系统首先会寻找是否存在A的任务栈,如果不存在就新建一个任务栈,并创建A的实例并放到栈中。如果存在A所需的任务栈,会去查询栈中A是否有实例存在,如果存在且在栈顶,此时与singleTop启动行为一致。如果存在且不在栈顶,系统就会把A上面的Activity出栈,使A回到栈顶。无论A的实例是否在栈顶,都会回调onNewIntent方法。如果A的任务栈存在但实例不存在,系统会创建一个A的实例并放入到栈中。
  • singleInstance:单实例模式,增强的singleTask,具有此模式的Activity只能单独的位于一个任务栈中。使用场景:singleInstance适合需要与程序分离开的页面。例如闹铃提醒,将闹铃提醒与闹铃设置分离。又如第一次启动一个应用让用户填写资料的Activity,填写完后转到主Activity,按back键不会回到前一个Activity。也可以在启动时候添加 FLAG_ACTIVITY_NO_HISTORY标志,使启动的Activity不被压入栈中实现。

Activity常用的Flag

FLAG_ACTIVITY_NEW_TASK:为Activity指定“singletask”模式。
FLAG_ACTIVITY_SINGLE_TOP:为Activity指定“singletop”模式。 FLAG_ACTIVITY_CLEAR_TOP:和FLAG_ACTIVITY_NEW_TASK配对使用,在同一任务栈中所有位于它上面的Activity都要出栈。
FLAG_ACTIVITY_NO_HISTORY:使用这个flag启动Activity不会被压入栈中。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:启动的Activity不会出现在recentTask中,等同于在AndroidManifest.xml中Activity的属性 android:excludeFromRecents="true"。

Activity保存临时数据和状态

系统在Activity即将被销毁并且有机会重新显示的情况下才会去调用onSaveInstanceState方法来保存临时数据和状态。该方法在onStop之前调用,与onPause没有先后关系。Activity被重新创建时可以从onCreate方法或是onRestoreInstanceState方法中获得之前保存的临时数据和状态。

有以下几个常见的场景:
1、当用户按下HOME键时。
2、长按HOME键,选择运行其他的程序时。
3、按下电源按键(关闭屏幕显示)时。
4、从activity A中启动一个新的activity B时。
5、屏幕方向切换时,例如从竖屏切换到横屏时。

下面以旋转屏幕为例看一下Activity各个方法的调用情况。主要逻辑代码如下:

public class MainActivity extends AppCompatActivity {
    private final static String TAG = "Activity";
    private final static Boolean DEBUG_ACTIVITY_PERIOD = true;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(DEBUG_ACTIVITY_PERIOD) Log.d(TAG,"onCreate");
        if(savedInstanceState != null) {
            Log.d(TAG,"onCreate " + savedInstanceState.getString("msg","null"));
        }
    }

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

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

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

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

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

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

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if(DEBUG_ACTIVITY_PERIOD) Log.d(TAG,"onSaveInstanceState");
        outState.putString("msg","Android");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if(DEBUG_ACTIVITY_PERIOD) Log.d(TAG,"onRestoreInstanceState");
        if(savedInstanceState != null) {
            Log.d(TAG,"onRestoreInstanceState " + savedInstanceState.getString("msg","null"));
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if(DEBUG_ACTIVITY_PERIOD) Log.d(TAG,"onConfigurationChanged");
    }
}

上面示例的代码逻辑很简单,就是在onSaveInstanceState方法被调用的时候保存了一个字符串“msg:Android”到Bundle实例里面去,在onCreate和onRestoreInstanceState把这个字符串从Bundle实例里面取出来。旋转后截取的log如下:

03-09 18:24:14.113 8705-8705/com.android.peter.activitydemo D/Activity: onPause
03-09 18:24:14.116 8705-8705/com.android.peter.activitydemo D/Activity: onSaveInstanceState
03-09 18:24:14.127 8705-8705/com.android.peter.activitydemo D/Activity: onStop
03-09 18:24:14.129 8705-8705/com.android.peter.activitydemo D/Activity: onDestroy
03-09 18:24:14.269 8705-8705/com.android.peter.activitydemo D/Activity: onCreate
03-09 18:24:14.269 8705-8705/com.android.peter.activitydemo D/Activity: onCreate Android
03-09 18:24:14.272 8705-8705/com.android.peter.activitydemo D/Activity: onStart
03-09 18:24:14.273 8705-8705/com.android.peter.activitydemo D/Activity: onRestoreInstanceState
03-09 18:24:14.273 8705-8705/com.android.peter.activitydemo D/Activity: onRestoreInstanceState Android
03-09 18:24:14.281 8705-8705/com.android.peter.activitydemo D/Activity: onResume

总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。另外,使用过程中还要需要注意以下几点:
1.布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,app将不会存储它的状态。
2.由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。 既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了,如果你自己的派生类中有变量影响到UI或你程序的行为,当然就要把这个变量也保存了,那么就需要自己实现,否则就不需要。
3.由于onSaveInstanceState()方法调用的不确定性,你应该只使用这个方法去记录activity的瞬间状态(UI的状态),不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在onPause()方法中存储持久化数据(例如应该被存储到数据库中的数据)。
4.onSaveInstanceState()如果被调用,这个方法会在onStop()前被触发,但系统并不保证是否在onPause()之前或者之后触发。

Activity其他的常用回调方法

菜单(Options menu)相关

  • onCreateOptionsMenu(Menu menu)
    Initialize the contents of the Activity's standard options menu. You should place your menu items into menu.
    初始化方法,可以在这个方法中设置布局给menu。

  • onPrepareOptionsMenu(Menu menu)
    Prepare the Screen's standard options menu to be displayed. This is called right before the menu is shown, every time it is shown. You can use this method to efficiently enable/disable items or otherwise dynamically modify the contents.
    菜单显示前调用。

  • onMenuOpened(int featureId, Menu menu)
    Called when a panel's menu is opened by the user. This may also be called when the menu is changing from one type to another (for example, from the icon menu to the expanded menu).
    每次点开菜单会被调用两次——一次是用户打开一次是展开时候

  • onOptionsItemSelected(MenuItem item)
    This hook is called whenever an item in your options menu is selected. The default implementation simply returns false to have the normal processing happen (calling the item's Runnable or sending a message to its Handler as appropriate). You can use this method for any items for which you would like to do processing without those other facilities.
    当菜单的某一下被选中。

  • onOptionsMenuClosed(Menu menu)
    This hook is called whenever the options menu is being closed (either by the user canceling the menu with the back/menu button, or when an item is selected).
    菜单关闭时候调用。

Window相关

  • onAttachedToWindow()
    Called when the main window associated with the activity has beens attached to the window manager.
    当与Activity相关的主窗口attached到WindowManager的时候回调这个方法。

  • onDetachedFromWindow()
    Called when the main window associated with the activity has been detached from the window manager.
    当与Activity相关的主窗口从WindowManager中detached的时候回调这个方法。

  • onWindowFocusChanged(boolean hasFocus)
    Called when the current [Window](https://developer.android.google.cn/reference/android/view/Window.html) of the activity gains or loses focus. This is the best indicator of whether this activity is visible to the user. The default implementation clears the key tracking state, so should always be called.
    当窗口焦点变化的时候回调这个方法。

按键(Key)相关

  • onBackPressed()
    Called when the activity has detected the user's press of the back key.
    当Back按键被按下时候调用。

  • onKeyDown(int keyCode, KeyEvent event)
    Called when a key was pressed down and not handled by any of the views inside of the activity.
    当按键被按下,Activity里面没有View来消费这个事件,就会调用Activity的这个方法。

  • onKeyUp(int keyCode, KeyEvent event)
    Called when a key was released and not handled by any of the views inside of the activity.
    当按键被放开,Activity里面没有View来消费这个事件,就会调用Activity的这个方法。

  • onKeyLongPress(int keyCode, KeyEvent event)
    Default implementation of KeyEvent.Callback.onKeyLongPress(): always returns false (doesn't handle the event).
    长按Back键会调用这个方法,长按Home键会被谷歌应用消费,长按Menu键会被多窗口消费。

其他

  • onActivityResult(int requestCode, int resultCode, Intent data)
    Called when an activity you launched exits, giving you the requestCode you started it with, the resultCode it returned, and any additional data from it.
    被启动的Activity退出后通过这个方法把数据传递给启动它的Activity。使用步骤如下:
    1、在Activity A中通过startActivityForResult启动Activity B;
    2、在Activity B中调用setResult方法设置返回的数据;
    3、当Activity B退出时会回调Activity A的onActivityResult方法把数据传递给Activity A。

  • onNewIntent(Intent intent)
    This is called for activities that set launchMode to "singleTop" in their package, or if a client used the FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity(Intent).
    在启动设置SingleTop启动模式Activity的时候会调用这个方法。

  • onLowMemory()
    This is called when the overall system is running low on memory, and actively running processes should trim their memory usage.
    当系统的负载过高会回调这个方法,Activity可以在此方法中释放一些内存减轻负载。

你可能感兴趣的:(Android四大组件——Activity)