Android学习笔记——四大组件之Activity

1. 简介:

Activity其实是一个与用户交互的一个接口。

2. 生命周期:

Android学习笔记——四大组件之Activity_第1张图片
image.png
  • Activity有四种状态:Active、paused、stopped、killed。
    (1)Active:点前Activity获取到了焦点,正在运行。
    (2)Pause:当前Activity失去了焦点不在栈顶位置,处于暂停状态,但此时的活动并没有被销毁,仍保存活动中的成员变量和状态信息,并且这个Activity仍然对用户可见
    如:Activity上有一个DialogActivity。
    (3)Stopped:Activity不位于栈顶位置,并且不可见。但系统仍然保留Activity对应的状态和成员变量,系统随时可以回收此Activity。
    (4)Killed:Activity从返回栈中移除,并且系统回收此Activity的成员变量,释放内存。

  • 将Activity分为三种生存期:
    (1)完整生存期:onCreate()--onStart()--onResume()--onPause()--onStop()--onDestroy()
    (2)可见生存期:onStart()--onResume()--onPause()--onStop()(Activity可见)
    (3)前台生存期:onResume()--onPause()(Activity运行,可与用户进行交互)

  • Activity中7种方法的作用以及使用场景:
    (1)onCreate():
    Activity正在被创建,这是Activity生命周期的第一个方法。通常我们程序员要在此函数中做初始化的工作,比如:绑定布局,控件,初始化数据等。

    (2)onStart():
    Activity正在由不可见变成可见使用场景:对数据的时效性要求较高。以新闻类App为例:Activity A代表新闻列表,点击列表中的一个Item进入到Activity B新闻详情,在从B返回到A的时候为了保证用户能看到最新的新闻,就需要从服务器拉取最新的新闻列表数据并填充到Activity A,那么这个工作就可以放在onStart里面,当然你也可以放在onRestart里面,但是activity首次加载启动的时候不会调用onRestart,所以也就不会去拉取新闻列表数据。

    (3)onResume():
    Activity可见,并且获得了焦点,用户可以与Activity进行交互使用场景:视频播放,当视图组件获得焦点时,即onResume中播放视频,当视图组件失去焦点时,即onPause中暂停播放视频。

    (4)onPause():
    Activity正在暂停并失去焦点,大多数情况下,Activity执行完onPause()函数后会继续执行onStop()函数,造成这种函数调用的原因是当前的Activity启动了另外一个Activity或者回切到上一个Activity。还有一种情况就是onPause()函数被单独执行了,并没有附带执行onStop()方法,造成这种函数调用的原因很简单,就是当前Activity里启动了类似于对话框的东东。

    (5)onStop():
    Activity即将停止,我们程序员应该在此函数中做一些不那么耗时的轻量级回收操作。使用场景:需要显示动画效果的回收。有些activity需要显示一些动画来帮助提升用户体验,但是当我们从该页面进入到一个新页面时,由于该页面已经不可见了,所以就可以把当前页面中的动画给关掉以节省系统资源,而这个工作就可以放在onStop中进行。

    (6)onDestroy():
    Activity要被销毁了。这是Activity生命中的最后一个阶段,我们可以在onDestory()函数中做一些回收工作和资源释放等,比如:广播接收器的注销等有些资源如果不进行释放会造成内存溢出,例如:异步任务引发的资源泄露,比如handler或者thread。这种情况发生的原因主要是异步任务的生命周期与activity生命周期不同步造成的,以handler中的message为例:

Handler handler =  new Handler();
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        tvContent.setText("newContent");
    }
}, 2000);
handler.obtainMessage(1).sendToTarget()

不管是使用哪种形式来发送message,message都会直接或者间接引用到当前所在的activity实例对象,如果在activity finish后,还有其相关的message在主线程的消息队列中,就会导致该activity实例对象无法被GC回收,引起内存泄露。所以一般我们需要在onDestroy阶段将handler所持有的message对象从主线程的消息队列中清除。示例如下:

Override
protected void onDestroy() {
    super.onDestroy();
    if (handler != null) {
        handler.removeCallbacksAndMessages(null);
    }
}

异步任务引发的App运行异常,这里以一个显示Dialog的场景为例:

andler handler =  new Handler();
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        new AlertDialog.Builder(MainActivity.this).setMessage("Show Dialog").show();
    }
}, 5000);

由于我们设置的是5秒后显示一个dialog,当activity在5秒内被finish后可能会导致显示dialog时App发生崩溃。

FATAL EXCEPTION: main
Process: xiaofei.com.fragmenttest, PID: 4645
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@aebf1e6 is not valid; is your activity running?
    at android.view.ViewRootImpl.setView(ViewRootImpl.java:567)
    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:310)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
    at android.app.Dialog.show(Dialog.java:319)
    at android.support.v7.app.AlertDialog$Builder.show(AlertDialog.java:955)
    at xiaofei.com.fragmenttest.Main2Activity$1.run(MainActivity.java:49)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5417)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

由于我们在activity的onDestroy中会销毁activity对应的窗体资源,所以在显示Dialog的时候由于dialog找不到父窗体就发生异常了。关于onDestroy中系统到底做了哪些资源清理的工作看下面的代码就清楚了:

final void performDestroy() {
    mDestroyed = true;
    mWindow.destroy();
    mFragments.dispatchDestroy();
    onDestroy();
    mFragments.doLoaderDestroy();
    if (mVoiceInteractor != null) {
        mVoiceInteractor.detachActivity();
    }
}

所以在这种场景下正确的做法应该是这样的:

Handler handler =  new Handler();
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        if (!MainActivity.this.isDestroyed()) {
            new AlertDialog.Builder(MainActivity.this).setMessage("Show Dialog").show();
        }
    }
}, 5000);

isDestroyed方法要求最低api level是17,一般我们目前app支持的最低api level是14,所以你可以在activity中添加一个flag来标记当前activity的状态是否被destroyed,其实activity源码里面也是这么干的,依葫芦画瓢就行。

/**
 * Returns true if the final {@link #onDestroy()} call has been made
 * on the Activity, so this instance is now dead.
 */
public boolean isDestroyed() {
    return mDestroyed;
}

(7)onRestart():
Activity正在重新启动。一般情况下,一个存在于后台不可见的Activity变为可见状态,都会去执行onRestart()函数,然后会继续执行onStart()函数,onResume()函数出现在前台并且处于运行状态。

  • Activity的生命周期流程
    例子:在A活动启动B活动(B是普通活动)。
    打开应用:
    A-->:onCreate --onStart --onResume
    从A打开B:
    A-->:onPause --- onStop
    B-->:onCreate -- onStart --onResume
    从B按返回键:
    B-->:onPause --onStop -- onDestroy
    A-->:onRestart --onStart --onResume

    例子:在A活动启动B活动(B是DialogActivity)
    打开应用:
    A-->:onCreate --onStart --onResume
    从A打开B:
    A-->:onPause
    B-->:onCreate -- onStart --onResume
    从B按返回键:
    B-->:onPause --onStop -- onDestroy
    A-->:onResume

3.Activity中实现返回数据给上一个活动

  • startActivityForResult()方法的作用是新开启一个Activity,并可以在新开启的Activity退出栈时返回数据给上一个Activity。
  • 例子:(onActivityResult方法通过检查requsetCode来判断数据应该返回到哪一个Activity)
//Activity A
Intent intent = new Intent(A.this, B.class);
statrActivityForResult(intent, 1);

//Activity中调用onActivityResult()
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data){
    switch(requestCode){
    case 1:
      if(resultCode == RESULT_OK){
          String data = data.getStringExtra("data_return");
    }
  }
}


// Activity B (onDestroy中调用)
Intent intent = new Intent();
intent.putExtra("data_return","Hello Return Data To A");
setResult(RESULT_OK, intent);



4. Activity中发生异常后数据的保存机制:

当Activity发生意外的情况的时候,这里的意外指的就是系统配置发生改变(Activity非正常结束),Activity会被销毁,onPause,OnStop,onDestory函数均会被调用,同时由于Actiivty是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity状态。调用onSaveInstanceState的时机总会发生在onStop之前,至于会不会调用时机发生在onPause方法之前,那就说不定了,这个没有固定的顺序可言,正常情况下一般onSaveInstanceState不会被调用。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Actiivty销毁时onSaveInstanceState方法所保存的Bundle对象作为参数传递给onRestoreInstanceState和onCreate方法。所以我们可以通过onRestoreInstanceState和onCreate方法来判断Actiivty是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复,从时序上来看,onRestoreInstanceState的调用时机发生在onStart之后

  • 例子1(保存和恢复fragment)
    savedInstanceState会保存到有两种数据:系统帮你自动保存的和你自己保存的。系统只会保存它认为有必要保存的(比方说EditText里面的内容,CheckBox的Check状态,Fragment实例等)
//不用onSaveInstanceState
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        getSupportFragmentManager().beginTransaction()
                .add(R.id.container, FragmentBase.newInstance(this, "One", FragmentBase.class.getName()))
                .add(R.id.container, FragmentFour.newInstance(this, "Two", FragmentFour.class.getName()))
                .add(R.id.container, FragmentFive.newInstance(this, "Three", FragmentFive.class.getName()))
                .commit();
    }
//后果:非正常启动Activity时候创建了6个Fragment实例

//使用onSaveInstacneState
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        if (savedInstanceState != null) {
            // 这里根据自己需要去从savedInstanceState中去数据(关键是获取保存了id的fragment)
            Fragment fragment = getSupportFragmentManager().findFragmentByTag(FragmentBase.class.getName());
            if (fragment instanceof FragmentBase) {
                FragmentBase base = (FragmentBase) fragment;
                
            }
        } else {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, FragmentBase.newInstance(this, "One", FragmentBase.class.getName()))
                    .add(R.id.container, FragmentFour.newInstance(this, "Two", FragmentFour.class.getName()))
                    .add(R.id.container, FragmentFive.newInstance(this, "Three", FragmentFive.class.getName()))
                    .commit();
        } 
    }


  • 例子2(恢复数据,Bundle类型的数据和键值对的数据类型差不多)
//MainActivity
@Override
protected void onSaveInstanceState(Bundle outState){
  super.onSaveInstanceState(outState);
  String tempData = "SomeThing you just type";
  outState.putString("data_key",tempData);
}

@Override
protected void onCreate(){
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  if(savedInstanceState != null){
      String temp = savedInstanceState.getString("data_key");
  }
}

5. 关于保存和恢复View层次结构,系统的工作流程

首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window在委托它上面的顶级容器去保存数据。顶级容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一个典型的委托思想,上层委托下层,父容器去委托子元素去处理一件事情,这种思想在Android中有很多应用,比如:View的绘制过程,事件分发等都是采用类似的思想。至于数据恢复过程也是类似的,这样就不再重复介绍了。

6. 关于Activity被系统回收的介绍

  • 资源内存不足导致低优先级的Activity被杀死
    Activity分为以下等级:
    优先级最高: 与用户正在进行交互的Activity,即前台Activity。
    优先级中等:可见但非前台的Activity,比如:一个弹出对话框的Activity,可见但是非前台运行。
    优先级最低:完全存在与后台的Activity,比如:执行了onStop。

当内存严重不足时,系统就会按照上述优先级去kill掉目前Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件的执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件独立运行在后台中,这样进程更容易被杀死。比较好的方法就是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死

7. Activity的横竖屏切换

与横竖屏生命周期函数有关调用的属性是"android:configChanges",关于它的属性值设置影响如下:

  • orientation:消除横竖屏的影响
  • keyboardHidden:消除键盘的影响
  • screenSize:消除屏幕大小的影响

当我们设置Activity的android:configChanges属性orientation或者orientation|keyboardHidden或者不设置这个属性的时候,它的生命周期会走如下流程:
刚刚启动Activity的时候:
onCreate
onStart
onResume
由竖屏切换到横屏:
onPause
onSaveInstanceState //这里可以用来横竖屏切换的保存数据
onStop
onDestroy
onCreate
onStart
onRestoreInstanceState//这里可以用来横竖屏切换的恢复数据
onResume
横屏切换到竖屏:
onPause
onSaveInstanceState
onStop
onDestroy

onCreate
onStart
onRestoreInstanceState
onResume

当我们设置Activity的android:configChanges属性为orientation|screenSize或者为orientation|screenSize|keyboardHidden的时候
刚刚启动Activity的时候:
onCreate
onStart
onResume
由竖屏切换到横屏:
什么也没有调用
由横屏切换到竖屏:
什么也没有调用

而且需要注意一点的是设置了orientation|screenSize属性之后,在进行横竖屏切换的时候调用的方法是onConfigurationChanged(),而不会回调Activity的各个生命周期函数;

  • 可以屏蔽掉横竖屏的切换操作
 android:screenOrientation="portrait" 始终以竖屏显示 
 android:screenOrientation="landscape" 始终以横屏显示
  • 设置屏蔽某个Activity的横竖屏切换功能
Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);以竖屏显示

Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);以横屏显示

8. Activity的启动模式:

  • Standard
    标准的启动方式,没次都会创建一个新的Activity实例。

  • SingleTop
    若Activity位于栈顶位置则直接复用Activity,不位于栈顶就重新创建一个新的Activity实例。

  • SingleTask
    (栈内复用模式,是一种单例模式,系统会回调onNewIntent),当一个具有singleTask模式的Activity A请求启动的时候,系统会自动寻找是有具有A想要的任务栈,若有任务栈就看任务栈内有没有A,有的话就把A调用在栈顶(如果A不在栈顶,则把A之前的Actvity弹出栈,使得A位于栈顶)并调用onNewIntent方法,如果没有,直接将A创建并压栈顶;若没有A的任务栈,则直接创建A的任务栈并将A入栈。这种场景在IM应用中使用的比较多,比如QQ或者微信的聊天页面,当从聊天页面进入其他页面,然后在重新进入聊天页面时就会直接进入原来的聊天页面,同时销毁中间新创建的Activity页面,并刷新聊天页面的数据。

  • SingleInstance
    设置该模式可以保证该Activity所在的Task中有且仅有一个activity实例,当通过startActivity启动Activity A时,如果该Activity的实例已经存在,那么不再重新创建一个新的Activity实例,而是直接复用该实例,进入该Activity的onNewIntent方法。这种场景出现的比较少,该Activity在整个系统只有一个实例,一般用于系统应用,并且可以被其他应用共享使用(有点类似于操作系统概念中的临界资源),比方说来电呼叫页面,在整个系统中就只能有一个,因为同一时刻只能存在一个电话呼叫。
    可以发现后面三种模式的原理其实跟一些App中页面的交互流程比较类似,可能Android也是考虑到为了更方便实现这种交互方式而定义了这三种特殊的启动模式。其实不管是哪种启动方式最终都会影响到Activity的生命周期流程,因此我们在启动一个Activity页面的时候需要留意其对该activity生命周期回调的影响,并做相应的处理逻辑。此外这四种启动方式一般都会在AndroidManifest中写死,同时也可以根据需要在代码中动态配置,代码中可使用的启动标志一般有以下三个:

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

一般将Intent.FLAG_ACTIVITY_NEW_TASK和Intent.FLAG_ACTIVITY_CLEAR_TOP搭配使用实现类似SingleTask的效果,将Intent.FLAG_ACTIVITY_NEW_TASK和Intent.FLAG_ACTIVITY_SINGLE_TOP搭配使用实现类似SingleTop的效果。

你可能感兴趣的:(Android学习笔记——四大组件之Activity)