说起生命周期,基本上小白们都还是知道点,不过时间长了就忘了?为什么就容易忘了?事实上我们还是没能真正的理解,没能比较深入的理解,I think.
基本上图啥的都可以摆出来看看:
理解的第一步是,我们亲自打印一遍日志看看:
搞两个Activity,FirstActivity和SecondActivity, 我们在FirstActivity里面打印下相关日志信息, 这里我还列出了另外三个生命周期onWindowFocusChanged, onNewIntent, onRestoreInstanceState, 后面两个和启动模式有些关系哟。至于onWindowFocusChanged,小白再三年前首次做android项目的时候用到过(当时是给U3D做Android插件的时候,想要利用Android进行版本检查的弹窗等操作,发现在onCreate或者onResume中都不行,
)。 这个时候真正的焦点可用就是onWindowFocusChanged中hasFocus为true的时候。So, 某些时候不了解深点还不行。
FirstActivity.java
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class FirstActivity extends AppCompatActivity {
private static final String Tag = FirstActivity.class.getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e(Tag, "onRestoreInstanceState");
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e(Tag, "onNewIntent");
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e(Tag, "onWindowFocusChanged");
}
@Override
protected void onStart() {
super.onStart();
Log.e(Tag, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.e(Tag, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.e(Tag, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.e(Tag, "onStop");
}
@Override
protected void onRestart() {
super.onRestart();
Log.e(Tag, "onRestart");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(Tag, "onDestroy");
}
public void JumpToSecond(View view) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
}
我们分别进行 0.首次启动 1. 跳转到第二个页面/回到桌面、2. 弹一个窗体、3. 退出、4.从第二个页面或者桌面返回、5.打开透明主题新页面。
Let's do it .
***第0种情况: ***首次启动
E/FirstActivity: onCreate
E/FirstActivity: onStart
E/FirstActivity: onResume
E/FirstActivity: onWindowFocusChanged hasFocus=true
onCreate里面可以做一些布局初始化,界面初始化,但是不要做太多的事情。
onResume一般不会做什么事情,但是如果某些页面要求刷新频率比较高(比如每次回到该页面都需要刷新一次,可以在这个页面进行刷新操作(比如进行网络请求刷新数据来刷新),当然很多时候我们通过发送广播或者Eventbus发送广播来通知该页面刷新。
关于onResume,小白目前的项目中,个人中心页面涉及到算力,涉及到积分,涉及到奖励信息等,所以每次都是在onResume中进行了刷新,也没搞什么定时器啥的,当然或许有些需要搞定时器或者别的做实时刷新, 比如直播的聊天室等。
onWindowFocusChanged了,小白目前没有用到。不过早期的U3D的Android插件的项目中用到了(当时的那种项目交叉的情况下如果在onCreate中版本检查,会出现is activity ruanning的错误),当时是用来做版本检查和更新, 大概逻辑是:
private boolean bHasChecked = false;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e(Tag, "onWindowFocusChanged hasFocus=" + hasFocus);
if (hasFocus && !bHasChecked){
bHasChecked = true;
///< chekcVersion操作
}
}
首先是页面处于首次启动且可见的情况下进行版本检查?因为如果你从其他页面回到该页面,也是会获取焦点,所以我们不能每次回到该页面都进行版本检查。你懂的!关于onWindowFocusChanged有相关官网可以看 Activity | Android Developers
大概就是说焦点的失去和获取时调用,当焦点失去后不要进行什么交互操作。如果在该页面弹窗,该页面将会失去焦点。而如果是系统级的顶层弹窗,比如状态栏通知或者系统警告提示等都将会暂时接管输入焦点,但是不会暂停前台页面。一会可以验证下弹窗的情况。
另外以前小白首次使用Android在onCreate中想获取控件的宽高(直接调用getWidth()发现为0,然后又换了别的获取控件宽高的方法,像View.post中获取)。其实还有一个地方就是这个里面肯定也是可以的,当焦点一旦获取就可以拿到, 下面就验证下获取宽高的情况(onResume算是较晚的周期了吧..那就在onResume中试试):
activity_first.xml
看结果赛:
E/FirstActivity: onCreate
E/FirstActivity: onStart
E/FirstActivity: onResume
E/FirstActivity: onResume width=0
E/FirstActivity: onResume height=0
E/FirstActivity: onWindowFocusChanged hasFocus=true
E/FirstActivity: onWindowFocusChanged width=443
E/FirstActivity: onWindowFocusChanged height=126
最后来看**onStart. **这是一个基本被忽视的生命周期回调,官方基本好像也没说啥
简单说了下在onCreate之后,或者onRestart之后。onSart之后都会跟随onResume.多数时候我们可以这样认为,onStart对应onStop, onResume对应onPause,然后再联想下周期图,拔过来的:
Android可能就是这样设计的吧,虽然onStart很少涉及,但是还是要有所了解的吧....
***第1种情况: ***跳转到第二个页面/回到桌面
E/FirstActivity: onPause
E/FirstActivity: onWindowFocusChanged hasFocus=false
E/FirstActivity: onStop
可以发现onPause(暂停后就无法响应交互了...也就是失去了焦点了)、onStop按顺序调用。而onWindowFocusChanged是onPause之后调用,可以参考网友简单分析 Activity之onWindowFocusChanged - Matrix_Ran - 博客园
你可以在这种情况你可以在onPause中做一些暂停操作,或者onStop中做。这个得看具体需求。比如你弹窗如果为了不影响播放,那就是onStop中,因为弹窗可能也会导致页面走onPause方法。
***第2种情况: ***弹一个窗体
AlertDialog.Builder
public void JumpToSecond(View view) {
// Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// startActivity(intent);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("test");
builder.setMessage("test");
builder.create().show();
}
弹出和消失,只走了焦点回调...并没有onPause...
什么情况下走onPause呢?
只有包含dialog或者dialog样式的activity弹窗时才会走onPause方法。
而如果是PopuWindow呢?
public void JumpToSecond(View view) {
// Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// startActivity(intent);
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("test");
// builder.setMessage("test");
// builder.create().show();
View contentView = LayoutInflater.from(this).inflate(R.layout.activity_second, null);
PopupWindow popWnd = new PopupWindow (this);
popWnd.setContentView(contentView);
popWnd.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
popWnd.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
popWnd.showAsDropDown(view);
}
什么回调也不会走,焦点回调也不走?
然后小白发现是因为没有设置popuwindow的focuse。
public void JumpToSecond(View view) {
// Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// startActivity(intent);
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("test");
// builder.setMessage("test");
// builder.create().show();
View contentView = LayoutInflater.from(this).inflate(R.layout.activity_second, null);
PopupWindow popWnd = new PopupWindow (this);
popWnd.setContentView(contentView);
popWnd.setBackgroundDrawable(new ColorDrawable(0xffeeeeff));
popWnd.setOutsideTouchable(true);
popWnd.setFocusable(true);
popWnd.setWidth(200);
popWnd.setHeight(200);
popWnd.showAsDropDown(view);
}
这下就可以了。。。原来如此!哇咔咔...
弹alertdialog样式的activity自然就是正常的周期了啦.....这个自己也可以试试.
***第3种情况: ***退出
***第4种情况: ***退出.从第二个页面或者桌面返回
E/FirstActivity: onRestart
E/FirstActivity: onStart
E/FirstActivity: onResume
E/FirstActivity: onWindowFocusChanged hasFocus=true
***第5种情况: ***打开透明主题新页面
设置透明主题先:
使用一下:
还是这个时候onStop就不会走了,我们暂停就行,因为下层页面实际上是可见的,也就符合了生命周期的特征。哇哦!
第o种情况:
当前页面显示时,息屏,亮屏
*打开透明页面后息屏,亮屏 *- 打开透明主题只是走了onPause, 息屏后再走onStop, 恢复走下(onRestart, onStart,这个地方并没有走onResume. 因为当前页面还是处在被其他页面覆盖的情况,只不过该页面透明罢了) - 这个需要留意下。
小结一下: Android开发艺术探索,里面有关于onStart、onStop , onResume、onPause的阐述:
从上面我们打印的一系列日志也可以看出。被覆盖,再回来,就是onResume, onPause的配合, 是否在前台!可见性就是看不见了,被遮挡了也好,返回桌面了也好。 正常页面的跳转页面,返回的操作基本这四个配合使用:
而我们从生命周期图中可以看到中间有个部分是:
这种就是其中提到的跳转新的透明主题的页面的情况(那种情况下onStop是不会触发的)。 这样简单过一遍下来,我们就大概知道了每个流程的情况,周期回调。
还有两个onNewIntent, onRestoreInstanceState,我们也简单了解下,因为有可能你在做某些启动方式,或者崩溃恢复某些数据等的时候会用到。
**onNewIntent - **当任务栈顶已经存在该页面,当再次启动该页面时,不会再走onCreate方法,而是走onNewIntent方法(此时你可以在这个地方进行这个view视图的更新操作)
前提是启动模式设置为singleTask
此时可以设置跳转参数
public void JumpToSecond(View view) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
intent.putExtra("name", "皮皮虾");
startActivity(intent);
}
然后走onNewIntent的时候获取 - 进而实现界面复用,视图更新的目的
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e(Tag, "onNewIntent" + intent.getStringExtra("name"));
}
这种有什用途呢? 我们为了不让页面每次重新创建,实现页面复用,视图动态更新的效果。小白想到了自己做的项目的一个案例(默认的standard启动模式),就是新闻资讯列表,点击列表中某个新闻后会进入详情页面A,详情页面底部有推荐文章和热门文章,此时再点击推荐文件或热门文章,再次启动资讯详情页面A,如果一直点下去,可能会产生10来个,甚至更多的详情页面。此时,如果想要返回主页面,难道我们一个个的退出?
这个时候就可以设置singletask启动模式,复用界面,将数据更新放到onNewInstance中请求一次,当不停浏览详情退出后就直接退出该页面了,棒棒哒!有没有。或许产品就是这样的需求...
而关于onSaveInstanceState(之前的生命周期忘了打印了,不过没有关系)....和onRestoreInstanceState....
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
Log.e(Tag, "onSaveInstanceState");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e(Tag, "onRestoreInstanceState");
}
当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,而当再次恢复时就会走onRestoreInstanceState。此时可以分别进行数据的存储和恢复操作。比如备忘录,记事本,当然这些应用可能是实时存储,所以不用担心数据丢失,至少本地是不会丢失的。
正常情况不会走,当出现异常,崩溃什么的就会触发。模拟器还可以进行这个设置 测试 onSaveInstanceState(Bundle)方法 干得漂亮。
but,小白测试自己的模拟器,没有调用,呵呵
这个后面看源码的时候再看吧。。。
另外补充一下,小白实际项目中的fragment的存储和恢复记录一下:HomeActivity中多个fragment的tag的保存,以及崩溃重启后进行恢复的操作。
FragmentTransaction的add方法有一个参数用于存储碎片标记....
然后就可以进行恢复
/**
* 获取崩溃保存的碎片
* @param savedInstanceState
*/
private void getSavedInstanceState(Bundle savedInstanceState){
if (null != savedInstanceState){
newsFragment = getSupportFragmentManager().findFragmentByTag("news");
flashFragment = getSupportFragmentManager().findFragmentByTag("flash");
mineFragment = getSupportFragmentManager().findFragmentByTag("mine");
tgHeziFragment = getSupportFragmentManager().findFragmentByTag("tghezi");
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
///< 崩溃重启,隐藏fragment,重新运行之前的主页面的显示逻辑;否则会发现碎片重叠...
if (null != newsFragment &&
newsFragment.isAdded() &&
!newsFragment.isHidden()){
transaction.hide(newsFragment);
transaction.commit();
}
if (null != mineFragment &&
mineFragment.isAdded() &&
!mineFragment.isHidden()){
transaction.hide(mineFragment);
transaction.commit();
}
if (null != flashFragment &&
flashFragment.isAdded() &&
!flashFragment.isHidden()){
transaction.hide(flashFragment);
transaction.commit();
}
if (null != tgHeziFragment &&
tgHeziFragment.isAdded() &&
!tgHeziFragment.isHidden()){
transaction.hide(tgHeziFragment);
transaction.commit();
}
Log.e("home", "infofrag=" + newsFragment);
Log.e("home", "infofrag=" + flashFragment);
Log.e("home", "mineFragment=" + mineFragment);
Log.e("home", "tgHeziFragment=" + tgHeziFragment);
}
}
以上可以解决崩溃重启后花屏的问题?因为如果你只是单纯的崩溃重启,而不处理随便隐藏逻辑,你会发现重启后界面会重叠花屏,因为碎片恢复后产生错了乱,你并没有恢复之前的显示状态。
所以处理是关键:
关于捕获异常重启,另外一篇简单记录下吧。网上也很多类似的。也可以看下官方文档....对了,我是在onCreate中调用的getSavedInstanceState方法,实际上了解了上面的知识,可以放到onRestoreInstanceState中,那也就可以不用判断savedInstanceState是否为空了~!啊哈哈...
Last, 这个周期这个地方简单先记录到这里。小白时觉得,要理解一个东西,除了场景的实践,可能还需要一些项目实战,那样印象和理解更深些。我也是再想,如何能更好的理解并记忆这些周期,这些方法,目前这一流程走一下,感觉好多了。后面准备开源码的专栏的了,要开始源码级的学习了。。。。
还是那句,懒人多做事,开心快乐,早点休息,早点起床,一家人开开心心比什么都重要! - 黄磊