Activity系列--Activity生命周期

这节主要介绍Activity生命周期相关知识

我所理解的Activity生命周期

android设计者把Activity设计的有生命周期,就犹如它类似于生物一样要经历生老病死,是的Activity也确实如此,比如用户打开一个Activity代表它创建了,当用户按返回按键的时候代表这Activity销毁死掉了。

在Activity从生到死的生命周期内,它要经历很多的状态,比如当前显示的Activity被另外的Activity遮挡住了,被遮挡的Activity是有一个状态的;又比如当前显示的Activity被一个对话框遮挡住了一半的时候它的状态又是啥。

Activity不同的状态也是在告诉Activity需要针对不同的状态做不同的事情了,比如Activity销毁的时候需要把关键的资源销毁掉等。

那Activity的状态,是由谁发出的,又是通过什么方式通知Activity的呢?

Activiity状态发出者是系统(更详细的说是系统进程中的ActivityManagerService(简称AMS)服务),AMS管理着所有的Activity,Activity的状态需要它来通知,它来统一协调。

通过调用Activity的不同的回调方法来把状态通知到Activiity,调用回调方法进行通知这是最靠谱最准确的方式,不管什么样的状态AMS都会即时通知到对应Activity。在实现调用回调方法的时候用到了面向对象的两个特性:继承多态,多态:如果子类重写了父类的方法,在运行的时候会调用子类中的重写的方法,而不会调用父类的被重写方法。

Activity的生命周期

现在就来看下Activity的生命周期内不同的状态和对应的回调方法以及不同状态之间的切换,回调方法都是以on开头的(如onCreate)。

先来看下官方的一张图


Activity生命周期

1. create状态

create状态代表系统会通知Activity处于开始创建的状态,它对应的回调方法是
onCreate
此方法在Activity生命周期内只调用一次,此方法需要调用setContentView方法来设置Activity对应的view只有设置view会才会在屏幕上显示,否则就是一个空白的界面。因为这方法被调用一次,还可以在这方法中初始化一些属性,view等

2. start状态

onCreate方法退出后,进入start状态,代表系统通知Activity进入”已启动状态“对于用户可见,但是不能与用户进行交互,它对应的回调方法是
onStart
此方法很简单

3. resume状态

onStart方法退出后,进入resume状态,代表系统Activity的界面已经显示并且用户的输入事件都交给当前Activity的对应view处理,Activity处于整个Activity堆栈的最前面。它对应的回调方法是
onResume
onResume方法执行完毕后,Activity就处于运行状态。

4. pause状态

当前Activity被别的Activity或dialog遮挡,或者用户点击了返回键,系统都会通知Activity进入pause状态,进入该状态代表Activity还是可见的,但是不能与用户进行交互。它下个进入的状态是stop或者resume状态。它对应的回调方法是
onPause
onPause方法执行特别快,不要在这个方法中保存数据或者请求网络,否则会影响app的运行(如是启动一个Activity导致进入该状态,则会影响下个Activity的启动,否则会影响当前Activity的销毁)

5. stop状态

当前的Activity被别的全屏及非透明的Activity遮挡,或者用户点击了返回键,系统都会通知Activity进入stop状态,进入该状态就代表该Activity完全不可见了。它下个进入的状态是destory或者start状态(如是用户点击返回键导致的则进入destory,否则当Activity重新显示恢复的时候进入start状态),它对应的回调方法是
onStop
该方法做的事情是比如暂停或者关闭甚至销毁一些资源,比如播放视频Activity,进入该界面后就需要把视频暂停掉,并且释放占用的与声音相关的资源。假如Activity没有销毁,当Activity再次进入onResume方法的时候,需要在该方法中把视频继续播放,并且申请与声音有关的资源。

6. restart状态

Activity并非由销毁状态而进入stop状态的,当Activity再次回到前台(被用户看到),就会先进入restart状态,随后onStart,onResume方法被依次调用保证Activity进入resume状态,它所对应的回调方法是
onRestart

7. destory状态

代表Activity进入销毁状态,它对应的回调方法是
onDestory
此方法是Activity生命周期最后一个执行的回调方法,在此方法中执行Activity所占据的资源的销毁工作。

小结

Activity的生命周期内在收到系统通知的对应状态时候,就需要根据对应的状态做出不同的事情,在onPause方法中不能进行网络请求等操作,在onStop方法中需要停掉一些与界面无关系的资源,这方法更不能进行网络请求的操作,在onDestory方法中释放Activity所占有的资源(这样才不至于出现内存泄漏等导致app crash等情况)。以上的操作可以让app做到尽可能少的消耗流量,尽可能少的消耗电量,做到更好的用户体验。这样app被用户卸载的几率就降低很多。

引起Activity生命周期变更的情况

引起Activity生命周期变更主要有下面几种:

配置发生了更改
配置包含的内容很多:比如手机横竖屏切换,在设置中语言的切换,系统文字大小切换,输入法切换,当前应用的权限发生变更等。

配置发生了变更后,系统会通知Activity的生命周期从onCreate到onResume重新执行(大家有兴趣可以试验下,在自己重写的Activity的生命周期方法中使用Log日志工具加上日志,更改以上提到的配置,看Activity的回调方法是否执行了)

当然了针对配置发生变更不单单会引起Activity重新执行生命周期流程,还有个比较不好的影响:会影响用户体验。拿横竖屏切换来举例子:当手机由竖屏切换为横屏后,会看到当前的界面和竖屏的时候界面状态不一致了(比如竖屏时候当前界面很长超出了一屏,这时候停留在50%的进度位置,当横屏后界面停留在了0%的位置),这其实非常影响用户体验。

当然针对配置引起Activity状态变更的情况,系统也允许有另外一种选择,就是可以不对某些配置做响应,在AndroidManifest清单文件中对Activity配置时候增加对应的配置:android:configChanges="keyboardHidden|screenSize|orientation" , orientation是当横竖屏切换时候,当前Activity不响应状态变更。

当然还有另外一种选择就是保存恢复界面状态机制,对应Activity的回调方法onSaveInstanceState和onRestoreInstanceState,在这就不详细介绍了。

启动了一个全屏非透明的Activiity
这里强调全屏非透明的Activity意味着和非全屏或者透明甚至半透明Activity,会影响被遮挡Activity进入不同的生命周期状态。

启动全屏非透明的Activity,系统会通知被遮挡Activity的onPause, onStop方法依次被调用。

启动非全屏或者透明半透明Activity或者显示一个Dialog
这种情况下,下层的Activity会依然显示,这时候系统会通知它的onPause方法会被执行。

用户点击返回按钮
这种情况,代表用户不需要当前Activity了可以销毁了。系统会通知Activity的onPause, onStop, onDestory方法依次执行,并且Activity会从系统内存中移除。

低内存导致Activity进程被杀
在处于低内存的情况下,为了释放更多的内存,需要把一些处于后台的进程杀掉。在这种情况下需要使用保存恢复界面状态机制,以便在Activity重新建立后,从保存的界面状态中进行恢复。

Activity状态和进程状态及进程被终止

下面是官方的关于 Activity状态和进程状态及进程被终止 的一张图


Activity-Process

对上面几个概念进行解释:
前台进程:指的是app中有一个Activity处于或者即将处于resume状态。
后台失去焦点的进程:指的是app中有一个Activity处于pause状态。
后台不可见进程:指app中所有的Activity都处于stop状态,这种情况进程就为不可见后台进程。

随着手机上打开的app越来越多,系统的内存越来越紧张,随之带来整个手机越来越卡。系统就需要释放尽量多的内存,在释放内存的时候,系统会依据上面的图,来依次释放内存。

后台不可见的进程自然成为最有可能被终止的目标(app都退居后台了,留着这样的进程干啥),当然了这说的知识可能性最大,进程本来就有优先级一说,优先级低的被终止的可能性要高于优先级高的。并且进程还有系统进程和普通进程一说,普通进程肯定会先被终止掉。

后台失去焦点进程被终止的可能性较大,毕竟这种进程的Activity还是可以被用户看到的,只是失去焦点了而已。

使用lifecycle组件来感知生命周期状态的变化

lifecycle组件的作用是啥?只有弄清了它的作用才会加深对它的理解,万事万物不可能凭白无故的产生,它可定是解决了先前存在的问题。那我们来看下现在的Activity生命周期回调方法里面存在啥问题?
比如一个Activity中包含了定位功能,播放视频功能,使用相机功能。来看段它的代码(下面都是伪代码 为了说明问题,当然一般一个Activity中是不会出现camera,location,video同时都用到的情况,只是为了说明问题)

public class MainActivity extends Activity{

    //定位功能的类
    private Location mLocation;
    //camera 这都是伪代码
    private Camera mCamera;
    //播放视频的类
    private Video mVideo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //对location,camera,video 进行初始化
    }

    @Override
    protected void onResume() {
        super.onResume();
        //因为进入resume状态,因此把相应的模块打开,假如它们都有start方法
        mCamera.start();
        mLocation.start();
        mVideo.start();
    }

    @Override
    protected void onStop() {
        super.onStop();
        //因为进入stop状态,因此把相应的模块stop,假如它们都有stop方法
        mCamera.stop();
        mLocation.stop();
        mVideo.stop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //因为进入destory状态,因此把相应的模块释放掉,假如它们都有destory方法
        mCamera.destory();
        mLocation.destory();
        mVideo.destory();
    }
}

上面代码中根据Activity不同的生命状态会对camera,video,location模块进行不同的处理,比如当前进入stop状态,那就非常有必要把它们都停止掉,不停掉就会有问题(比如video不停掉,就会发现界面已经看不到了,但是还有声音,并且还一直消耗着手机的电量和流量,这完全不是一个好的用户体验的app),比如在进入destory状态,需要三个模块把占用的资源销毁掉。

上面的Activity的代码还比较简单,但是当业务越来越多的时候,不同的生命周期回调方法里面会充斥着更多的代码,这就会为以后代码维护,复用等带来极高的成本。就比如camera,video,location模块中的某个模块需要在别的Activity中使用,那是不是需要在这个Activity的对应生命周期回调方法里面对模块进行处理,这根本就没有达到模块复用的效果。

造成以上问题的原因就是因为 只能在Activity的生命周期回调方法里面才能知道状态的变化,那只能把与状态有关的代码写在Activity的回调方法里面,最终导致代码越来越多。那官方给出了lifecycle组件来解决此问题,解决此问题的核心就是回调,大概原理是:只要把监听生命周期变化的回调传递给它,它就能把关心的状态通知到你。

lifecycle组件
该组件是Android Jetpack的一部分,位于androidx.lifecycle包下。主要的几个关键类和接口:LifecycleDefaultLifecycleObserverLifecycleOwnerLifecycleRegistryLifecycleEventObserver

lifecycle的使用非常简单,主要分为:

  1. 实现DefaultLifecycleObserver或者LifecycleEventObserver 这俩接口中的任意一个
  2. 编写的Activity需要继承FragmentActivity或者它的子类,调用它的addObserver方法把第一步的实例作为参数

下面来看下例子,对上面MainActivity的改造

public class MainActivity extends FragmentActivity {

    private Camera myCamera = new Camera(this);
    private Video myVideo = new Video(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
public class Video {

    private Context mContext;
    private MyLifecycleObserver myLifecycleObserver = new MyLifecycleObserver();

    private class MyLifecycleObserver implements LifecycleEventObserver {

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_CREATE) {
                //初始化方法
                init();
            } else if (event == Lifecycle.Event.ON_DESTROY) {
                destory();
            }  else if (event == Lifecycle.Event.ON_RESUME) {
                start();
            } else if (event == Lifecycle.Event.ON_STOP) {
                stop();
            }
        }
    }

    public Video(Context context) {
        mContext = context;
        if (mContext instanceof LifecycleOwner) {
            ((LifecycleOwner)mContext).getLifecycle().addObserver(myLifecycleObserver);
        }
    }
}

public class Camera {
    private Context mContext;
    private MyLifecycleObserver myLifecycleObserver = new MyLifecycleObserver();

    private class MyLifecycleObserver implements DefaultLifecycleObserver {
        private final String TAG = "MyCamera$LifecycleObserver";

        @Override
        public void onCreate(@NonNull LifecycleOwner owner) {
            init();
        }

        @Override
        public void onDestroy(@NonNull LifecycleOwner owner) {
            destory();
        }

        @Override
        public void onResume(@NonNull LifecycleOwner owner) {
            start();
        }

        @Override
        public void onStop(@NonNull LifecycleOwner owner) {
            stop();
        }
    }

    public Camera(Context context) {
        mContext = context;
        if (mContext instanceof LifecycleOwner) {
            ((LifecycleOwner)mContext).getLifecycle().addObserver(myLifecycleObserver);
        }
    }
}

Location和上面的代码基本一致,就不写了。

看了上面的代码,有没有感觉MainActivity清爽多了,Camera,Video,Location在自己类中就可以收到Activity生命周期的变化,这样它们就是高内聚低耦合,可复用的模块了。

有了lifecycle这个组件后,很多的库都使用上了,比如Glide库,在加载图片时候,若Activity进入stop状态,应该停止请求网络,是不是在Activity的onStop方法里面没有看到它的身影,那是因为它已经在自己模块内处理了,把细节隐藏在自己模块内。还有LiveData,ViewModel等Jetpack的库都用了这个组件。

总结

这节介绍了Activity的生命周期及Activity不同的状态和状态对应的回调方法,以及不同状态的切换。并且介绍了影响Activity状态变更的情况。还介绍了前台进程,失去焦点的后台进程和不可见后台进程,以及它们与被系统终止的可能性。最后介绍了lifecycle组件,强烈建议大家使用这个组件,不要在Activity里面写过多的逻辑(能作为一个模块独立出来就尽量独立出来)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容