zoukankan      html  css  js  c++  java
  • Android提升篇系列:Activity recreate(Activity 重新创建/自我恢复)机制(一)

    注:本文中的recreate是指当内存不足时,Activity被回收,但再次来到此Activity时,系统重新恢复的过程。
    例如:当Activity A到Activity B时,如果内存不足,A被回收,但当用户按下Back键返回时,A又会被系统重新创建。

    为了便于问题展开,我们首先来看一段最简单的代码

    ----------------代码片段1------------------

     1 package com.example.corn.corntest;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 
     6 public class MainActivity extends Activity {
     7 
     8     @Override
     9     protected void onCreate(Bundle savedInstanceState) {
    10         super.onCreate(savedInstanceState);
    11 
    12         setContentView(R.layout.activity_main);
    13     }
    14 }

    我们发现,onCreated()作为Activity的生命周期,在回调的过程中有一个Bundle类型的形参savedInstanceState,按照中文的大概翻译是“已经保存的实例状态”。

    那么,相应的问题也就出来了,何为实例状态?为什么要保存实例状态?如何保存?

    1.何为实例状态?
    Android沿袭Java而来,很多概念与Java保持一致,只是在特性的Android场景中会有些稍微变化,如实例状态。首先回顾一下Java中的实例状态:实例及对象,状态指的是对象的成员属性。相应的,Android中以Activity实例状态为例,指的是Activity实例对象中的成员属性,但相应的有点区别的是,一般意义的理解上,此处的成员属性并不包括Activity中的视图级的成员(包括View级和Fragment级等)。

    我们继续看一段代码

    -------------------代码片段2-----------------

     1 public class MainActivity extends Activity {
     2     public static final String TAG = MainActivity.class.getSimpleName();
     3 
     4     private EditText mContentEt;
     5     private Button mCountBtn;
     6     private TextView mCountTv;
     7     private Button mGoBtn;
     8 
     9     private String mContent;
    10     private int mCount;
    11 
    12 
    13     @Override
    14     protected void onCreate(Bundle savedInstanceState) {
    15         super.onCreate(savedInstanceState);
    16 
    17         setContentView(R.layout.activity_main);
    18 
    19         mContentEt = (EditText) findViewById(R.id.content_et);
    20         mCountBtn = (Button) findViewById(R.id.count_btn);
    21         mCountTv = (TextView) findViewById(R.id.count_tv);
    22         mGoBtn = (Button) findViewById(R.id.go_btn);
    23 
    24         mCount = 100;
    25         mCountTv.setText(mCount + "");
    26         mCountBtn.setOnClickListener(new View.OnClickListener() {
    27             @Override
    28             public void onClick(View v) {
    29                 mCount ++;
    30                 mCountTv.setText(mCount + "");
    31             }
    32         });
    33         
    34         mGoBtn.setOnClickListener(new View.OnClickListener() {
    35             @Override
    36             public void onClick(View v) {
    37                 Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    38                 startActivity(intent);
    39             }
    40         });
    41     }
    42 }


    对应的xml文件为:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:orientation="vertical"
     6     android:paddingLeft="100dip"
     7     android:paddingTop="20dip">
     8 
     9     <TextView
    10         android:layout_width="wrap_content"
    11         android:layout_height="wrap_content"
    12         android:text="请输入内容" />
    13 
    14 
    15     <EditText
    16         android:id="@+id/content_et"
    17         android:layout_width="200dip"
    18         android:layout_height="40dip" />
    19 
    20     <Button
    21         android:id="@+id/count_btn"
    22         android:layout_width="100dip"
    23         android:layout_height="40dip"
    24         android:layout_marginTop="40dip"
    25         android:text="点击计数" />
    26 
    27     <TextView
    28         android:id="@+id/count_tv"
    29         android:layout_width="200dip"
    30         android:layout_height="40dip"
    31         android:layout_marginTop="10dip"
    32         android:background="#ccc"
    33         android:gravity="center_vertical"
    34         android:paddingLeft="20dip"
    35         android:textColor="#ff0000"
    36         android:textSize="13sp" />
    37 
    38     <Button
    39         android:id="@+id/go_btn"
    40         android:layout_width="100dip"
    41         android:layout_height="40dip"
    42         android:layout_marginTop="40dip"
    43         android:text="点击跳转到第二个Activity" />
    44 
    45 </LinearLayout>

    在此代码片段中,Android中的实例状态一般指的是mContent和mCount。

    2.为什么要保存实例状态?

    有人可能会有这样的疑惑,为什么要保存实例状态,单纯的Java代码中好像没有这一说法啊。具体的原因还要从Android中本身的内存回收机制说起。手机中给每个Android应用分配的内存都是具有一个上限的,不同的手机此上限值可能不同。Android虚拟机优先确保前台Activity或前台Service等具有的正常内存分配,这使得进入到后台的Activity在内存不足的情况下将会被系统主动销毁并回收,此时当用户按下Back键等返回时,系统将重新创建此Activity对应的实例。同时,为了确保良好的用户体验和逻辑的一致性,在系统主动回收Activity时,为程序提供可能的保存实例状态的机制,以便当后续重新返回时系统能够恢复实例状态。

    3.如何保存和恢复?

    Android中为Actvity的实例状态保存和恢复提供了相应的机制,通过提供相应的实例状态保存和恢复回调,将状态数据存储到系统中的Bundle中。相应的回调函数分别为:onSaveInstanceState(Bundle)和onRestoreInstanceState(Bundle)。当然,对于实例状态的恢复,也可以直接通过onCreate(Bundle)中的Bundle参数进行。onCreate(Bundle)和onRestoreInstanceState(Bundle)都能恢复实例状态,且一般情况下,两种方式恢复实例状态功能相同,唯一比较有差别的地方,在于恢复实例状态的时机,onRestoreInstanceState(Bundle)回调时机更加靠后。


    下面,以代码2片段为例,来具体看一下如何保存和恢复Activity实例状态。

    这段代码很容易理解,一个是EditText输入框,同时还有一个计算器按钮,其初始值是100,每点击一次计数按钮显示的计数增加1。另一个跳转按钮点击后跳转到其他Activity。首先,我们运行一下,看看代码2片段显示的效果。

    在请输入内如下方的输入框中输入内容“Corn”,然后点击三下“点击计算”按钮,此时点击计数下面的TextView显示为103,然后点击“点击跳转”,来到SecondActivity,再按下返回键,返回到MainActivity(注:为论述方便,本文中将此操作过程简称为“操作过程A”)。一般情况下(手机内存足够),此时MainActivity就是跳转之前的MainActivity,且显示效果及内容没有任何区别。

    为了模拟当Activity处于后台时,可能因内存不足而被销毁,而当再次返回到此Activity时,又会自动创建场景,我们可以进行如下操作:打开开发者选项 >> 不保留活动。

    接下来代码2片段适当修改

    -----------------代码片段3------------------- 

     1 public class MainActivity extends Activity {
     2     public static final String TAG = MainActivity.class.getSimpleName();
     3 
     4     private EditText mContentEt;
     5     private Button mCountBtn;
     6     private TextView mCountTv;
     7     private Button mGoBtn;
     8 
     9     private String mContent;
    10     private int mCount;
    11 
    12 
    13     @Override
    14     protected void onCreate(Bundle savedInstanceState) {
    15         Log.d(TAG, "in onCreate >> this" + this + " savedInstanceState:" + savedInstanceState);
    16         super.onCreate(savedInstanceState);
    17 
    18         setContentView(R.layout.activity_main);
    19 
    20         mContentEt = (EditText) findViewById(R.id.content_et);
    21         mCountBtn = (Button) findViewById(R.id.count_btn);
    22         mCountTv = (TextView) findViewById(R.id.count_tv);
    23         mGoBtn = (Button) findViewById(R.id.go_btn);
    24 
    25         mCount = 100;
    26         mCountTv.setText(mCount + "");
    27         mCountBtn.setOnClickListener(new View.OnClickListener() {
    28             @Override
    29             public void onClick(View v) {
    30                 mCount++;
    31                 mCountTv.setText(mCount + "");
    32             }
    33         });
    34 
    35         mGoBtn.setOnClickListener(new View.OnClickListener() {
    36             @Override
    37             public void onClick(View v) {
    38                 Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    39                 startActivity(intent);
    40             }
    41         });
    42     }
    43 
    44 
    45     @Override
    46     protected void onSaveInstanceState(Bundle outState) {
    47         Log.d(TAG, "in onSaveInstanceState >> this:" + this + " outState:" + outState);
    48         super.onSaveInstanceState(outState);
    49     }
    50 
    51     @Override
    52     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    53         Log.d(TAG, "in onRestoreInstanceState >> this:" + this + 
    54                           " savedInstanceState:" + savedInstanceState);
    55         super.onRestoreInstanceState(savedInstanceState);
    56     }
    57 
    58     @Override
    59     protected void onDestroy() {
    60         Log.d(TAG, "in onDestroy >> this" + this);
    61         super.onDestroy();
    62     }
    63 }

    再次运行程序,进行“操作过程A”,此时,我们发现当回到MainActiviyt时,页面执行短暂的白屏了下,然后才马上显示出MainActivity界面,但是,之前有所不同的是,此时MainActivity中的点击计算下方的TextView显示内容还原成了100。但请输入内容中的EditText内容却依然是“Corn”,咦,这到底是怎么回事呢?

    为了一探究竟,我们打开AS中的logcat,看看关键的执行过程。

    ------不保留活动 关闭 >>  对应内存相对足够的一般情况------

    D/MainActivity: in onCreate >> thiscom.example.corn.corntest.MainActivity@41e69568 savedInstanceState:null
    D/MainActivity: in onSaveInstanceState >> this:com.example.corn.corntest.MainActivity@41e69568 outState:Bundle[{}]

    ------不保留活动  开启 >>  对应内存不足销毁相应Activity------

    D/MainActivity: in onCreate >> thiscom.example.corn.corntest.MainActivity@41ec5bc8 savedInstanceState:null
    D/MainActivity: in onSaveInstanceState >> this:com.example.corn.corntest.MainActivity@41ec5bc8 outState:Bundle[{}]
    D/MainActivity: in onDestroy >> thiscom.example.corn.corntest.MainActivity@41ec5bc8
    D/MainActivity: in onCreate >> thiscom.example.corn.corntest.MainActivity@41eef8c0 savedInstanceState:Bundle[mParcelledData.dataSize=620]
    D/MainActivity: in onRestoreInstanceState >> this:com.example.corn.corntest.MainActivity@41eef8c0 savedInstanceState:Bundle[{android:viewHierarchyState=Bundle[mParcelledData.dataSize=540]}]

    通过打印出的日志我们发现:

    1.当系统内存不足而将Aticity销毁时,调用了其对应的生命周期回调方法onDestory,当返回时自动进行了Activity的自动创建过程,但重新创建的Activity与之前的Activity不再是同一个对象实例;

    2.Activity重新创建时,重新执行了完整的生命周期方法的回调;

    3.与Activity初始进来创建时不同的是,Activity重新创建的onCreate()回调方法中,Bundle形参不再是null,而是已经保存了实例状态的Bundle对象。同时,我们也发现,onRestoreInstanceState(Bundle)在Activity重新创建过程中也进行了回调,且传入了已经保存了实例状态的Bundel对象;

    由此,已经很自然的解释了为什么当Activity重新创建时TextView里面的内容被还原成了100(因为onCreate()方法重新执行了一遍)。

    那么我们如何在Activity重新创建时使得TextView中的内容与之前保持一致?

    同时我们发现了一个令人不太理解的现象,为什么EditText能够记住之前的内容且能够理想的恢复状态?

    下面我们逐一回答:
    问题一:如何在Activity重新创建时使得TextView中的内容与之前保持一致呢?

    Activity中提供了onSaveInstanceState(Bundle)回调方法,至于onSaveInstanceState具体回调时机,本人在“Android总结篇系列”相关博文中已经做过总结,在此不再赘述。

    我们可以通过此方法保存需要保存并恢复的Activiy实例状态,如本文中的TextView中的内容变量mCount。

     1     @Override
     2     protected void onSaveInstanceState(Bundle outState) {
     3         Log.d(TAG, "in onSaveInstanceState >> this:" + this + " outState:" + outState);
     4         super.onSaveInstanceState(outState);
     5 
     6         // 保存mCount值
     7         outState.putInt("extra_count", mCount);
     8     }
     9 
    10     @Override
    11     protected void onCreate(Bundle savedInstanceState) {
    12     ....
    13 
    14         if(savedInstanceState == null){
    15             mCount = 100;
    16         } else {
    17             mCount = savedInstanceState.getInt("extra_count");
    18         }
    19 
    20     ...
    21     
    22     
    23      }

    或者,如前文所说,onCreate也可以不作改动,直接在onRestoreInstanceState(Bundle)中恢复状态即可。

    1     @Override
    2     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    3         Log.d(TAG, "in onRestoreInstanceState >> this:" + this + 
    " savedInstanceState:" + savedInstanceState); 4 super.onRestoreInstanceState(savedInstanceState); 5 6 // 恢复mCount值及相应显示效果 7 mCount = savedInstanceState.getInt("extra_count"); 8 mCountTv.setText(mCount + ""); 9 }

    问题二:为什么EditText能够记住之前的内容且能够理想的恢复状态?

    这个问题就涉及到Activity中View级别的自我恢复机制,限于篇幅,将在下篇具体阐述。

    最后,对本文的论述进行一下总结:

    1.Activity中通过onSaveInstanceState(Bundle)回调函数去保存Activity实例状态,保存实例状态存在于系统Bundle中;

    2.当系统内存不足时,Activity存在重新创建机制,场景的模拟可以通过打开开发者选项 >> 不保留活动进行
    (当然,将app先设置成可以横竖屏切换,然后切换横竖屏也是可以的,不过,相对没有“不保留活动”方便);

    3.Activity重新创建时具有完整的Activity生命周期,且onCreate(Bundle)中的Bundle不为null,同时也回调了onRestoreInstanceState(Bundle)方法,因此,相应的恢复Activity状态可以通过其中任意一种方式进行。

  • 相关阅读:
    指针和引用作为函数参数传递
    cv::Mat.type()
    Matlab 双目标定工具箱
    invalid conversion from `const void*' to `void*'
    error: 'vector' is not a member of cv
    单例模式与静态成员
    RGBD SLAM V2 +Ubuntu 16.04+ROS KINETIC配置及运行
    EntityFrameworkCore + MySQL 主从复制应用读写分离
    Docker 搭建 MySQL8.0 主从复制环境
    Asp.Net Core 项目中使用 Serilog 输出日志到 Elasticsearch
  • 原文地址:https://www.cnblogs.com/lwbqqyumidi/p/5276815.html
Copyright © 2011-2022 走看看