Android提升篇系列:Activity recreate(Activity 重新创建/自我恢复)机制(一)
注:本文中的recreate是指当内存不足时,Activity被回收,但再次来到此Activity时,系统重新恢复的过程。
例如:当Activity A到Activity B时,如果内存不足,A被回收,但当用户按下Back键返回时,A又会被系统重新创建。
为了便于问题展开,我们首先来看一段最简单的代码
----------------代码片段1------------------
package com.example.corn.corntest; import android.app.Activity;
import android.os.Bundle; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
}
}
我们发现,onCreated()作为Activity的生命周期,在回调的过程中有一个Bundle类型的形参savedInstanceState,按照中文的大概翻译是“已经保存的实例状态”。
那么,相应的问题也就出来了,何为实例状态?为什么要保存实例状态?如何保存?
1.何为实例状态?
Android沿袭Java而来,很多概念与Java保持一致,只是在特性的Android场景中会有些稍微变化,如实例状态。首先回顾一下Java中的实例状态:实例及对象,状态指的是对象的成员属性。相应的,Android中以Activity实例状态为例,指的是Activity实例对象中的成员属性,但相应的有点区别的是,一般意义的理解上,此处的成员属性并不包括Activity中的视图级的成员(包括View级和Fragment级等)。
我们继续看一段代码
-------------------代码片段2-----------------
public class MainActivity extends Activity {
public static final String TAG = MainActivity.class.getSimpleName();
private EditText mContentEt;
private Button mCountBtn;
private TextView mCountTv;
private Button mGoBtn;
private String mContent;
private int mCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContentEt = (EditText) findViewById(R.id.content_et);
mCountBtn = (Button) findViewById(R.id.count_btn);
mCountTv = (TextView) findViewById(R.id.count_tv);
mGoBtn = (Button) findViewById(R.id.go_btn);
mCount = 100;
mCountTv.setText(mCount + "");
mCountBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCount ++;
mCountTv.setText(mCount + "");
}
});
mGoBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
}
对应的xml文件为:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="100dip"
android:paddingTop="20dip"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请输入内容" /> <EditText
android:id="@+id/content_et"
android:layout_width="200dip"
android:layout_height="40dip" /> <Button
android:id="@+id/count_btn"
android:layout_width="100dip"
android:layout_height="40dip"
android:layout_marginTop="40dip"
android:text="点击计数" /> <TextView
android:id="@+id/count_tv"
android:layout_width="200dip"
android:layout_height="40dip"
android:layout_marginTop="10dip"
android:background="#ccc"
android:gravity="center_vertical"
android:paddingLeft="20dip"
android:textColor="#ff0000"
android:textSize="13sp" /> <Button
android:id="@+id/go_btn"
android:layout_width="100dip"
android:layout_height="40dip"
android:layout_marginTop="40dip"
android:text="点击跳转到第二个Activity" /> </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-------------------
public class MainActivity extends Activity {
public static final String TAG = MainActivity.class.getSimpleName();
private EditText mContentEt;
private Button mCountBtn;
private TextView mCountTv;
private Button mGoBtn;
private String mContent;
private int mCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "in onCreate >> this" + this + " savedInstanceState:" + savedInstanceState);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContentEt = (EditText) findViewById(R.id.content_et);
mCountBtn = (Button) findViewById(R.id.count_btn);
mCountTv = (TextView) findViewById(R.id.count_tv);
mGoBtn = (Button) findViewById(R.id.go_btn);
mCount = 100;
mCountTv.setText(mCount + "");
mCountBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCount++;
mCountTv.setText(mCount + "");
}
});
mGoBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onSaveInstanceState(Bundle outState) {
Log.d(TAG, "in onSaveInstanceState >> this:" + this + " outState:" + outState);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.d(TAG, "in onRestoreInstanceState >> this:" + this +
" savedInstanceState:" + savedInstanceState);
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onDestroy() {
Log.d(TAG, "in onDestroy >> this" + this);
super.onDestroy();
}
}
再次运行程序,进行“操作过程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。
@Override
protected void onSaveInstanceState(Bundle outState) {
Log.d(TAG, "in onSaveInstanceState >> this:" + this + " outState:" + outState);
super.onSaveInstanceState(outState); // 保存mCount值
outState.putInt("extra_count", mCount);
} @Override
protected void onCreate(Bundle savedInstanceState) {
.... if(savedInstanceState == null){
mCount = 100;
} else {
mCount = savedInstanceState.getInt("extra_count");
} ... }
或者,如前文所说,onCreate也可以不作改动,直接在onRestoreInstanceState(Bundle)中恢复状态即可。
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.d(TAG, "in onRestoreInstanceState >> this:" + this +
" savedInstanceState:" + savedInstanceState);
super.onRestoreInstanceState(savedInstanceState); // 恢复mCount值及相应显示效果
mCount = savedInstanceState.getInt("extra_count");
mCountTv.setText(mCount + "");
}
问题二:为什么EditText能够记住之前的内容且能够理想的恢复状态?
这个问题就涉及到Activity中View级别的自我恢复机制,限于篇幅,将在下篇具体阐述。
最后,对本文的论述进行一下总结:
1.Activity中通过onSaveInstanceState(Bundle)回调函数去保存Activity实例状态,保存实例状态存在于系统Bundle中;
2.当系统内存不足时,Activity存在重新创建机制,场景的模拟可以通过打开开发者选项 >> 不保留活动进行
(当然,将app先设置成可以横竖屏切换,然后切换横竖屏也是可以的,不过,相对没有“不保留活动”方便);
3.Activity重新创建时具有完整的Activity生命周期,且onCreate(Bundle)中的Bundle不为null,同时也回调了onRestoreInstanceState(Bundle)方法,因此,相应的恢复Activity状态可以通过其中任意一种方式进行。
Android提升篇系列:Activity recreate(Activity 重新创建/自我恢复)机制(一)的更多相关文章
- Android提升篇系列:adb无法识别MX5等特殊机型
发现自己Ubuntu系统adb无法识别魅族 mx5机型.操作具体如下(其他机型依然适用): 一.Ubuntu环境 1.查看自己当前设备的idVendor lsusb命令直接查看当前usb设别列表,找到 ...
- Android提升篇系列:Android项目代码优化实践
Android开发中,不同的开发团队,不同的开发人员,在实际编码中会有一些不同的地方.但是,具有一定的更普适性的编码习惯,无疑还是相当重要的.本文主要罗列项目中常见的一些编码片段,并给出相关建议. 1 ...
- Android总结篇系列:Activity中几个主要函数详解
Activity作为Android系统中四大基本组件之一,包含大量的与其他的各大组件.intent.widget以及系统各项服务等之间的交互的函数.在此,本文主要选取实际项目开发中常用的,但完全理解又 ...
- 【转】Android总结篇系列:Activity Intent Flags及Task相关属性
[转]Android总结篇系列:Activity Intent Flags及Task相关属性 同上文一样,本文主要引用自网上现有博文,并加上一些自己的理解,在此感谢原作者. 原文地址: http:// ...
- 【转】Android总结篇系列:Activity启动模式(lauchMode)
[转]Android总结篇系列:Activity启动模式(lauchMode) 本来想针对Activity中的启动模式写篇文章的,后来网上发现有人已经总结的相当好了,在此直接引用过来,并加上自己的一些 ...
- 【转】Android总结篇系列:Activity生命周期
[转]Android总结篇系列:Activity生命周期 Android官方文档和其他不少资料都对Activity生命周期进行了详细介绍,在结合资料和项目开发过程中遇到的问题,本文将对Activity ...
- Android总结篇系列:Activity Intent Flags及Task相关属性
同上文一样,本文主要引用自网上现有博文,并加上一些自己的理解,在此感谢原作者. 原文地址: http://blog.csdn.net/liuhe688/article/details/6761337 ...
- Android总结篇系列:Activity生命周期
Android官方文档和其他不少资料都对Activity生命周期进行了详细介绍,在结合资料和项目开发过程中遇到的问题,本文将对Activity生命周期进行一次总结. Activity是由Activit ...
- Android总结篇系列:Android 权限
权限是一种安全机制.Android权限主要用于限制应用程序内部某些具有限制性特性的功能使用以及应用程序之间的组件访问.在Android开发中,基本上都会遇到联网的需求,我们知道都需要加上联网所需要的权 ...
随机推荐
- KnockoutJS 3.X API 第四章(13) template绑定
目的 template绑定(模板绑定)使用渲染模板的结果填充关联的DOM元素. 模板是一种简单方便的方式来构建复杂的UI结构 . 下面介绍两种使用模板绑定的方法: 本地模板是支持foreach,if, ...
- Change Git Default Editor in Windows
On 32 bit Win OS: git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' - ...
- SSM环境搭建(接口编程方式)
一直用ssm在开发项目,之前都是直接copy别人的项目,今天趁着项目刚刚交付,自己搭建一下ssm环境,做个记录 一.创建项目.引入jar包,因为版本不一样,就不贴出这部分的内容了.个人平时的习惯是,先 ...
- 快速入门系列--GIT版本控制工具
由于GIT刚刚开始使用不久,经常会在Merge时出现没有change-id的情况,在结合gerrit使用时,经常出现不能提交的情形,使得自己很困扰.最近有次熬夜加班,在代码完成后,由于多人在很短时间内 ...
- Winform实现Shp-栅格图形文件的读取与显示(外加shp转WKB格式存入oracle)附源码
前言:上学期GIS空间数据库课程设计时,老师让实现Shp-栅格图形文件的读取与显示,外加shp转WKB格式存入oracle,不使用第三方类库,花了一天时间在网上找了一些资料,实现了一个简单的栅格图形文 ...
- 使用替换shader渲染
相关函数: Camera.RenderWithShader(shader: Shader, replacementTag: string) 使用指定shader渲染,只影响一帧 Camera.SetR ...
- javase基础复习攻略《九》
本篇将为大家总结JAVA中的线程机制,谈到线程,大家一定会问线程和进程有什么区别?刚接触进程时我也有这样的疑问,今天就为大家简单介绍一下进程和线程.进程(Process)是计算机中的程序关于某数据集合 ...
- Oracle Concept
1. Truncate Truncate是DDL命令.表的物理位置是保存在数据字典中表的定义的一部分.首次创建时,在数据库的数据文件内给表分配了一个固定大小的空间.这就是所谓的区间并且为空.那么当插入 ...
- 自己的JS框架--Amy框架。
这是我根据司徒正美<JavaScript框架设计>一书然后百度了很多东西之后自己写的一个JS框架,满足了司徒正美文中的种子模块部分,包含了命名空间.对象扩展.数组化.类型判断.选择器.多库 ...
- 如何在Notepad++ 中成功地安装Emmet 插件
对于前端来说,Emmet 是一个好东西,但是好几次在 “Notepad++” 中安装后不能使用.今天认认真真地查找了失败原因,配置完成后,终于可以在 “Notepad++” 下正常使用了.故把过程记录 ...