Activity组件介绍(一):生命周期

Activity概述

Activity是Android应用的四大组件之一(其他三大组件分别是Service、Broadcast Receiver、Content Provider)。

Activity是Android应用的表示层。Activity通常作为全屏窗口呈现给用户,另外也可以作为浮动窗口或者嵌入另一个Activity内部这两种办法使用。

类比WEB开发,可以把Activity理解成网页中的一个JSP文件;或者也可以把它理解成一个Windows的窗口。

从Activity类的继承关系可以看到Activity是Context类的子类:

Activity生命周期

一个程序一般由多个Activity组成,各Activities之间耦合关系通常很松散。程序必须有一个Activity被指定为主Activity,它是程序启动时首先显示的界面。每个Activity都可以随意启动其它的Activity。每当一个Activity被启动,则前一个Activity就被停止。一个程序中的所有启动的Activity都被放在一个栈中,所有被停止的Activity并没有销毁,而在置存于栈中。新启动的Activity被置于栈顶,然后获得输入焦点。在当前活动的Activity上点返回键,它就会被从栈中取出然后销毁,之后在栈中紧接着的它的前一个Activity被恢复,取而代之获得输入焦点。

Acitvity主要有四种状态:

  • 运行(active or running),此时该Activity处于栈的最顶端,具有屏幕输入焦点。
  • 暂停(pause)。如果该Activity被一个非全屏或者透明的Activity遮罩,此时该Activity失去焦点但仍是清晰可见,则它处于暂停状态。暂停状态的活动完全是活着的,它依然持有所有的状态和成员信息并保持与窗口管理器(Window Manager)的连接,不过系统在极端低内存的情况下也会杀死暂停状态的Activity。
  • 停止(stop),此时Activity被另一个Activity完全遮住。停止状态的Activity也保留了所有状态和成员信息,但不再对用户可见了。系统需要内存空间用于别处时会杀死停止状态的Activity。
  • 被杀(killed)。系统可以要求处于暂停或停止状态的Activity自我终结(finish),或者直接杀死Activity的进程。如果已被杀死的Activity要重新显示给用户的话,它需要完全重启(restart)并恢复之前的状态。

Activity这四种状态可以分别对应四个进程状态:

  • 前台Activity是用户正在交互的Activity,处于屏幕顶端。它的进程是最重要的,除非设备处于内存换页状态,为了保证用户界面响应,才有可能杀死它,否则系统会尽量保证前台Activity的完整可用。
  • 可见Activity顾名思义是对用户可见的,只是被前台对话框等Activity遮住了一部分。它的进程也是相当重要的,一般也不会被杀,除非极端情况需要确保前台Activity的运行。
  • 后台Activity对用户不可见,而且已暂停。它的进程便不再重要了,系统为了保证前台和可见进程的内存可以安全地杀死后台进程。被杀后若要让该Activity重新在屏幕上可见,其onCreate(Bundle)方法会被调用,Bundle这个参数是先前在onSaveInstanceState(Bundle)中保存的成员状态,然后Activity重启,状态恢复至用户上次离开该Activity时的模样。
  • 进程并不持有任何Activity或者其他应用组件,当内存不足时系统会很快将其杀死。因此在Activity以外的任何后台操作都必须要在BroadcastReceiver或Service的context中执行,确保系统不会拿你的进程开刀微笑

当一个Activity因为新的Activity启动而被停止时,它会收到状态变化的通知,这样的变化有多个,每个都会引起系统调用一个相应的回调方法以通知Activity,这些回调方法被统称为“生命周期回调方法”。这些回调方法分别在Activity被创建、停止、恢复、销毁时被调用。

下图是活动的重要状态的路径图,彩色的椭圆框是Activity的状态,矩形框是生命周期回调方法:

这里用实例来演示各生命周期回调方法的触发时机:

本例创建了两个Activity。主Activity名叫MyActivity,包含一个输入框,输入文字后点击按钮,会启动另一个Activity(名叫AnotherActivity),AnotherActivity会显示用户所输入的文字。控制台(Logcat)会输出当前Activity触发的回调函数(本文省略了每个控制台输出的时间戳和前缀等信息,只按顺序保留方法名)。

从启动应用到MyActivity变为运行状态:


com.example.lesson.MyActivity.onCreate(MyActivity.java:18)

com.example.lesson.MyActivity.onStart(MyActivity.java:34)

com.example.lesson.MyActivity.onResume(MyActivity.java:66)

这里onCreateonStartonResume三个方法依次被调用。

在文本框输入文字:


点击Android设备电源按键,关闭设备屏幕:

com.example.lesson.MyActivity.onSaveInstanceState(MyActivity.java:72)

com.example.lesson.MyActivity.onPause(MyActivity.java:40)

这里onSaveInstanceStateonPause方法被依次调用,说明MyActivity进入了暂停状态。

重新打开设备屏幕:

com.example.lesson.MyActivity.onResume(MyActivity.java:66)

发现文本框文字保持原样。这里仅onResume方法被调用,此时MyActivity恢复运行状态。

点击Android设备主页(Home)按键,退到Android启动界面(laucher):

com.example.lesson.MyActivity.onSaveInstanceState(MyActivity.java:72)

com.example.lesson.MyActivity.onPause(MyActivity.java:40)

com.example.lesson.MyActivity.onStop(MyActivity.java:47)

这里onSaveInstanceStateonPauseonStop三个回调方法被依次调用,MyActivity进入停止状态。

然后再点击应用按钮回到界面:

com.example.lesson.MyActivity.onRestart(MyActivity.java:60)

com.example.lesson.MyActivity.onStart(MyActivity.java:34)

com.example.lesson.MyActivity.onResume(MyActivity.java:66)

重新打开应用后发现文本框中的文字还在,说明MyActivity的状态依然是保存完好的。这里onRestartonStartonResume方法被依次调用,说明进入停止状态后需要先重启才能恢复。

触碰屏幕中”Launch Another Activity"按钮:

com.example.lesson.MyActivity.onSaveInstanceState(MyActivity.java:72)

com.example.lesson.MyActivity.onPause(MyActivity.java:40)

com.example.lesson.AnotherActivity.onCreate(AnotherActivity.java:16)

com.example.lesson.AnotherActivity.onStart(AnotherActivity.java:25)

com.example.lesson.AnotherActivity.onResume(AnotherActivity.java:57)

com.example.lesson.MyActivity.onStop(MyActivity.java:47)

界面跳转到AnotherActivity,并显示出MyActivity文本框输入的文字。这里MyActivity先调用onSaveInstanceStateonPause方法,然后AnotherActivity创建并启动,完成后MyActivity再调用onStop方法。

点击Android设备的后退按键:

com.example.lesson.AnotherActivity.onPause(AnotherActivity.java:31)

com.example.lesson.MyActivity.onRestart(MyActivity.java:60)

com.example.lesson.MyActivity.onStart(MyActivity.java:34)

com.example.lesson.MyActivity.onResume(MyActivity.java:66)

com.example.lesson.AnotherActivity.onStop(AnotherActivity.java:38)

com.example.lesson.AnotherActivity.onDestroy(AnotherActivity.java:45)

界面回到MyActivity,其文本框的文字还保持原样;另外后退按键使得AnotherActivity在onDestroy方法被调用,说明它被销毁了,下次启动需重新调用onCreate方法。

将设备旋转至横屏:

com.example.lesson.MyActivity.onSaveInstanceState(MyActivity.java:72)

com.example.lesson.MyActivity.onPause(MyActivity.java:40)

com.example.lesson.MyActivity.onStop(MyActivity.java:47)

com.example.lesson.MyActivity.onDestroy(MyActivity.java:54)

com.example.lesson.MyActivity.onCreate(MyActivity.java:18)

com.example.lesson.MyActivity.onStart(MyActivity.java:34)

com.example.lesson.MyActivity.onResume(MyActivity.java:66)

旋转屏幕会导致MyActivity被摧毁然后重新创建再启动,当然由于onSaveInstanceState方法被调用,其状态被保存,重新创建后文本框文字被原样恢复。

点击Android设备后退按钮再打开应用:


com.example.lesson.MyActivity.onPause(MyActivity.java:40)

com.example.lesson.MyActivity.onStop(MyActivity.java:47)

com.example.lesson.MyActivity.onDestroy(MyActivity.java:54)

com.example.lesson.MyActivity.onCreate(MyActivity.java:18)

com.example.lesson.MyActivity.onStart(MyActivity.java:34)

com.example.lesson.MyActivity.onResume(MyActivity.java:66)

发现文本框中的文字没有被恢复,这是因为点击后退键不会触发onSaveInstance方法,MyActivity状态丢失,下次重建再次调用onCreate(Bundle)方法时,其参数Bundle为空。

MyActivity代码如下:

package com.example.lesson;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MyActivity extends Activity {
    public static final String TAG = "MyActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(view.getContext(), AnotherActivity.class);
                EditText editText = (EditText) findViewById(R.id.editText);
                intent.putExtra(TAG, editText.getText().toString());
                startActivity(intent);
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());

    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }
}
AnotherActivity代码如下:

package com.example.lesson;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class AnotherActivity extends Activity {
    public static final String TAG = "AnotherActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.another_activity);
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
        TextView textView = (TextView) findViewById(R.id.textView);
        Intent intent = getIntent();
        textView.setText(intent.getStringExtra(MyActivity.TAG));
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());

    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG, new Throwable().getStackTrace()[0].toString());
    }
}

要点拾遗

配置变更

类似旋转屏幕这种更改Android设备配置(由Resources.Configuration类定义)的操作还有改变语言、输入设备等,它们都会导致Activity被销毁然后重建。如果想避免这些原因导致的重启的话,可以在manifest中修改android:configChanges属性。当这些配置被修改时,当前Activity的onConfigurationOnChanged(Configuration)方法就会被调用而不会直接重启Activity。

不过一旦涉及到了未声明处理的配置变更,Activity依然会被重启且onConfigurationOnChanged方法不会被调用。举个例子:API 13以后,在AndroidManifest.xml中设置android:configChanges="orientation“后,旋转屏幕时不会触发onConfigurationOnChanged,活动依然会重启,因为screenSize属性也跟着改变了。此时需要增加属性声明为android:configChanges="orientation|screenSize"才能防止活动重启,当然如果仅仅是为了这个目的的话,onConfigurationOnChanged方法不一定要重写(override)。

状态保存

从示例可知,onSaveInstanceState(Bundle)方法被调用时,系统会将Activity的状态存储到Bundle里。如果进程未被杀死,经过onRestart()后Activity状态和成员信息依旧完好,无需恢复;如果被杀死了,则系统会调用onCreate(Bundle)或者onRestoreInstanceState(Bundle)方法恢复Activity状态。概括成图示如下:


onSaveInstanceState方法会在系统认为Activity“容易”(vulnerable)被系统销毁时调用,如果是用户主动销毁的,如示例中按下设备的后退键,则不会调用。以下几种会被调用的常见情况(在示例中均出现了):

  • Activity甲启动了Activity乙使得乙盖到了甲的前方。在示例中,启动AnotherActivity前,MyActivity的该方法先调用。
  • 按下设备主页(Home)键。
  • 按下电源键。
  • 屏幕旋转。

很多资料说长按主页键调出多任务栏也会触发该方法,博主在小米2S(Android 4.1.1)上实测并未成功重现,不知何故。

Android标准UI控件基本都实现了onSaveInstanceState方法,如文本框(EditText)会用户保存输入的文本、复选框(CheckBox)会保存用户打钩的位置等,前提是这些UI控件被分配了唯一的ID,才能被恢复状态。开发者需要覆写该方法时,需要先调用父类的该方法再在后面编写自己的代码。

由于onSaveInstanceState方法并不总是被调用,因此它常用来保存一些临时的状态、成员变量值等,而类似持久化数据(保存数据到数据库或文件中)这样的工作通常被放在onPause方法里(多提一句,如果要保存的数据量很大可能耗时较长,可以另开线程,将这些工作异步处理,尽量不要阻塞UI线程)。

需要持久化的状态一般有两种:

  • 文档化的共享数据,通常利用Content Provider组件存储在SQLite数据库中。出于用户体验的考虑,现在的移动应用很少需要在用户编辑之后点保存按钮了,例如用户写一封电子邮件到一半,关闭应用后再打开,会发现原先的文字早已被存在草稿箱里。要实现这种体验,开发者需要在用户创建新文档时,立刻在数据库或文件系统中新建一个条目;当onPause方法被调用时,将更改提交到Content Provider或者文件中。
  • 内部状态,如用户设置(preferences)。这个功能由SharedPreferences类及其相关API来支持。Android官方文档给出了以下示例代码,用来演示日历浏览模式的保存:
     public  class  CalendarActivity  extends  Activity  { 
         ... 
    
         static  final  int DAY_VIEW_MODE =  0 ; 
         static  final  int WEEK_VIEW_MODE =  1 ; 
    
         private  SharedPreferences mPrefs ; 
         private  int mCurViewMode ; 
    
         protected  void onCreate ( Bundle savedInstanceState )  { 
             super . onCreate ( savedInstanceState ); 
    
             SharedPreferences mPrefs = getSharedPreferences (); 
             mCurViewMode = mPrefs . getInt ( "view_mode" , DAY_VIEW_MODE ); 
         } 
    
         protected  void onPause ()  { 
             super . onPause (); 
     
             SharedPreferences . Editor ed = mPrefs . edit (); 
             ed . putInt ( "view_mode" , mCurViewMode ); 
             ed . commit (); 
         } 
     }

参考资料

Android官方API文档:http://developer.android.com/reference/android/app/Activity.html

nkmnkm的专栏:http://blog.csdn.net/niu_gao/article/details/7101178

Android学习指南:http://android.yaohuiji.com/archives/141

Healtheon的博客:http://www.cnblogs.com/hanyonglu/archive/2012/03/28/2420515.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值