Android-Activity生命周期较详细理解实践

说起生命周期,基本上小白们都还是知道点,不过时间长了就忘了?为什么就容易忘了?事实上我们还是没能真正的理解,没能比较深入的理解,I think.

基本上图啥的都可以摆出来看看:

image

理解的第一步是,我们亲自打印一遍日志看看:

搞两个Activity,FirstActivity和SecondActivity, 我们在FirstActivity里面打印下相关日志信息, 这里我还列出了另外三个生命周期onWindowFocusChanged, onNewIntent, onRestoreInstanceState, 后面两个和启动模式有些关系哟。至于onWindowFocusChanged,小白再三年前首次做android项目的时候用到过(当时是给U3D做Android插件的时候,想要利用Android进行版本检查的弹窗等操作,发现在onCreate或者onResume中都不行,

image

)。 这个时候真正的焦点可用就是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

image

大概就是说焦点的失去和获取时调用,当焦点失去后不要进行什么交互操作。如果在该页面弹窗,该页面将会失去焦点。而如果是系统级的顶层弹窗,比如状态栏通知或者系统警告提示等都将会暂时接管输入焦点,但是不会暂停前台页面。一会可以验证下弹窗的情况。

另外以前小白首次使用Android在onCreate中想获取控件的宽高(直接调用getWidth()发现为0,然后又换了别的获取控件宽高的方法,像View.post中获取)。其实还有一个地方就是这个里面肯定也是可以的,当焦点一旦获取就可以拿到, 下面就验证下获取宽高的情况(onResume算是较晚的周期了吧..那就在onResume中试试):

activity_first.xml

  

    
image
image
image

看结果赛:

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. **这是一个基本被忽视的生命周期回调,官方基本好像也没说啥

image

简单说了下在onCreate之后,或者onRestart之后。onSart之后都会跟随onResume.多数时候我们可以这样认为,onStart对应onStop, onResume对应onPause,然后再联想下周期图,拔过来的:

image

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...

image

什么情况下走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);
    }

什么回调也不会走,焦点回调也不走?

image

然后小白发现是因为没有设置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);
    }

这下就可以了。。。原来如此!哇咔咔...

image

弹alertdialog样式的activity自然就是正常的周期了啦.....这个自己也可以试试.

***第3种情况: ***退出

image

***第4种情况: ***退出.从第二个页面或者桌面返回

image
E/FirstActivity: onRestart
E/FirstActivity: onStart
E/FirstActivity: onResume
E/FirstActivity: onWindowFocusChanged hasFocus=true

***第5种情况: ***打开透明主题新页面

设置透明主题先:

 
    

使用一下:

   

还是这个时候onStop就不会走了,我们暂停就行,因为下层页面实际上是可见的,也就符合了生命周期的特征。哇哦!

image

第o种情况:

当前页面显示时,息屏,亮屏

image

*打开透明页面后息屏,亮屏 *- 打开透明主题只是走了onPause, 息屏后再走onStop, 恢复走下(onRestart, onStart,这个地方并没有走onResume. 因为当前页面还是处在被其他页面覆盖的情况,只不过该页面透明罢了) - 这个需要留意下。

image

小结一下: Android开发艺术探索,里面有关于onStart、onStop , onResume、onPause的阐述:

image

从上面我们打印的一系列日志也可以看出。被覆盖,再回来,就是onResume, onPause的配合, 是否在前台!可见性就是看不见了,被遮挡了也好,返回桌面了也好。 正常页面的跳转页面,返回的操作基本这四个配合使用:

而我们从生命周期图中可以看到中间有个部分是:

image

这种就是其中提到的跳转新的透明主题的页面的情况(那种情况下onStop是不会触发的)。 这样简单过一遍下来,我们就大概知道了每个流程的情况,周期回调。

还有两个onNewIntent, onRestoreInstanceState,我们也简单了解下,因为有可能你在做某些启动方式,或者崩溃恢复某些数据等的时候会用到。

**onNewIntent - **当任务栈顶已经存在该页面,当再次启动该页面时,不会再走onCreate方法,而是走onNewIntent方法(此时你可以在这个地方进行这个view视图的更新操作)

前提是启动模式设置为singleTask

image
image

此时可以设置跳转参数

  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"));
    }
image

这种有什用途呢? 我们为了不让页面每次重新创建,实现页面复用,视图动态更新的效果。小白想到了自己做的项目的一个案例(默认的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,小白测试自己的模拟器,没有调用,呵呵

image

这个后面看源码的时候再看吧。。。

另外补充一下,小白实际项目中的fragment的存储和恢复记录一下:HomeActivity中多个fragment的tag的保存,以及崩溃重启后进行恢复的操作。

FragmentTransaction的add方法有一个参数用于存储碎片标记....

image

然后就可以进行恢复

  /**
     * 获取崩溃保存的碎片
     * @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);
        }
    }

以上可以解决崩溃重启后花屏的问题?因为如果你只是单纯的崩溃重启,而不处理随便隐藏逻辑,你会发现重启后界面会重叠花屏,因为碎片恢复后产生错了乱,你并没有恢复之前的显示状态。

所以处理是关键:

image

关于捕获异常重启,另外一篇简单记录下吧。网上也很多类似的。也可以看下官方文档....对了,我是在onCreate中调用的getSavedInstanceState方法,实际上了解了上面的知识,可以放到onRestoreInstanceState中,那也就可以不用判断savedInstanceState是否为空了~!啊哈哈...

Last, 这个周期这个地方简单先记录到这里。小白时觉得,要理解一个东西,除了场景的实践,可能还需要一些项目实战,那样印象和理解更深些。我也是再想,如何能更好的理解并记忆这些周期,这些方法,目前这一流程走一下,感觉好多了。后面准备开源码的专栏的了,要开始源码级的学习了。。。。

还是那句,懒人多做事,开心快乐,早点休息,早点起床,一家人开开心心比什么都重要! - 黄磊

你可能感兴趣的:(Android-Activity生命周期较详细理解实践)