Activity生命周期和启动模式入门总结

30 篇文章 0 订阅

第一章 Activity生命周期和启动模式

1. Activity的生命周期全面分析启动

Activity有四种生命状态 ,分别是:running、paused、stopped、killed。

1.1 正常生命周期

  1. onCreate(): 表示此时activity正在创建,通常进行一些初始化的方法。

  2. onStart(): 表示此时activity已经被创建,但没有位于前台,处于可见但不可交互的状态。

  3. onResume(): 表示此时activity处于前台running的状态,既可见又可交互的状态。

  4. onPause(): 表示此时activity处于paused的状态,一般紧接着会调用onStop(),此时处于可见但不可交互。在这个方法中进行一些存储、暂停的工作,但不能耗。

  5. onStop(): 表示Activity处于stopped状态,此时位于后台,处于不可见也不可交互状态。这个方法中能继续做一些不耗时的回收工作。

  6. onDestroy(): 表示Activity即将被销毁。在这个最终的方法中,进行剩余所有资源的释放和回收。

在这里插入图片描述

Q&A

1.在Activity A中打开Activity B 调用方法顺序是什么?

这个问题的核心其实在于 Activity A的onPause()和Activity B 的onCreate谁先执行的问题。在Activity启动流程中,可以看到在新activity创建入栈之前,原栈顶的activity会先调用onPause,然后新activity依次调用onCreate onStart onResume。最后Activity B入栈,Activity A失去栈顶的位置,退入后台调用onStop。

因此我们可以看到,onPause方法应该尽快结束,避免影响后面Activity的创建,所以不能执行耗时的操作。

2.如果是透明主题或者dialog呢?

如果是透明或者dialog的话,则不会执行onStop方法,核心在于是否可见的问题。此时虽然Activity A虽然被遮盖,但是由于透明或者是dialog不能完全遮盖Activity A,因此处于可见也不可交互的状态,也就是会调用onPause(),而不会调用onStop。

1.2异常声明周期

除了正常的生命周期以外,还有一些异常情况的生命周期,以下分析两种较为常见的情况。

(1)资源相关的系统配置改变导致Activity被杀死并重新创建。

比如一张图片放在drawable里,为了兼容不同的手机分辨率,可能需要在其他不同的目录比如drawable-mdpi、drawable-hdpi、drawable-land等等。这样当app启动时,会自动根据手机的配置加载不同的图片。可是当发生一些配置的改变时,比如横屏和竖屏显示的图片肯定是不一样的,此时Activity会被销毁并创建,根据当前的配置重新加载合适的图片。当然我们也可以阻止发生配置改变时,销毁activity。

Activity的生命周期图如图所示,当发生意外情况时,首先会调用onSaveInstanceState方法的作用顾名思义,显然是存储此时activity中的数据,防止销毁后数据丢失,便于之后的重建复原,然后原Activity被销毁。这个方法会在onStop之前调用,和onPause没有先后的关系。当Activity被重建后,会调用onRestoreInstanceState,显然这个是用来恢复数据的,从时序上来看,这个方法发生在onStart之后。这两个方法通过bundle进行存取数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SE2mxrC1-1597049785395)(/Users/bjhl/Library/Application Support/typora-user-images/image-20200804162853011.png)]

这两个方法系统会为我们自动做一些存储和恢复工作,保存当前的视图结构,比如说文本框内用户输入的数据、Listview滚动的当前位置等等。具体哪一些View会为我们做那些恢复工作可以查看源码,所有的view都重写了这两个方法,可以查看一下具体实现就知道如何恢复了。这里不进行一一的列举,简单看一下比如ListView的源码,ListView本身没有重写onSaveInstanceState,而是使用它的父类AbsListView的onSaveInstanceState方法,由于代码很长,就只贴一小部分,如下所示。

@Override
public Parcelable onSaveInstanceState() {
    /*
     * This doesn't really make sense as the place to dismiss the
     * popups, but there don't seem to be any other useful hooks
     * that happen early enough to keep from getting complaints
     * about having leaked the window.
     */
    dismissPopup();

    Parcelable superState = super.onSaveInstanceState();

    SavedState ss = new SavedState(superState);

    if (mPendingSync != null) {
        // Just keep what we last restored.
        ss.selectedId = mPendingSync.selectedId;
        ss.firstId = mPendingSync.firstId;
        ss.viewTop = mPendingSync.viewTop;
        ss.position = mPendingSync.position;
        ss.height = mPendingSync.height;
        ss.filter = mPendingSync.filter;
        ss.inActionMode = mPendingSync.inActionMode;
        ss.checkedItemCount = mPendingSync.checkedItemCount;
        ss.checkState = mPendingSync.checkState;
        ss.checkIdState = mPendingSync.checkIdState;
        return ss;
    }
    …………
}

可以看到,主要包括了选择的item 当前位置 ,选择item的id等信息,具体的这些参数看不太懂,如果有人懂得话可以分享一下谢谢。

(2)资源内存不足时,会先回收低优先级的Activity。

这种情况下,一般按照 后台activity->可见但不可交互的activity(比如被对话框遮盖)->前台Activity的优先级进行回收。

Q&A

1.如何禁止因为资源配置,而重建Activity?

比如不想因为屏幕旋转的时候重新创建,就可以通过配置configChanges添加orientation这个值。

android:configChanges="orientation"

如果想同时指定多个值,可以通过|进行连接。这个下面的属性值有很多,常用的有locale(代表地理位置的改变,一般指切换了系统语言)、orientation(当手机屏幕发生旋转)、keyboardHidden(用户调出键盘等),其余的就不列举了,需要的时候查询一下即可。如果api大于13的话,还需要加上screenSize(屏幕尺寸发生改变),所以最终如下。

android:configChanges="orientation|screenSize">

此时旋转屏幕时,不会进行activity的重建,自然也不会用调用onSaveInstanceState和onRestoreInstanceState,而是会调用onConfiguarationChanged,顾名思义就是当配置发生改变进行回调的方法

2. Activity的启动模式

首先有一个android的概念叫做任务栈,系统会为每一个应用程序都分配一个任务栈,它就是我们常见的后进先出的栈结构。每次创建一个新的activity就入栈,每次按一下back键activity就出栈。

2.1LaunchMode

Activity的启动模式有四种分别是standard、singleTop、singleTask、singleInstance。

(1)standard

这也是默认的启动模式,每次启动activity都会新建一个实例入栈。谁启动了一个新的activity,那个新的activity就会入谁所在的栈。

(2)singleTop

standard存在一个问题,就是启动几次activity,就有几个新的实例,显然一些场景下是不合理的,因此有后面的几种启动模式。singleTop模式采用栈顶复用的情况,如果此时activity位于栈顶,启动自己时不会重新创建实例入栈,同时onNewIntent方法会被回调。注意如果不在栈顶,仍然会进行重建。

(3)singleTask

singleTop会出现一个问题就是,如果Actvity A 启动了Activity B,然后在Activity B中又启动Activity A。此时任务栈中的顺序是 A B A,这显然也是不合理的。用户从A中按一次back回到B,再按一次B又回到了A,显然是很奇怪的用户体验,因此singleTask便是如果activity处于栈内,就会将该activity调到栈顶,并且由于singleTask默认具有clearTop的效果,还附带将它顶部的所有activity全部出栈,然后调用onNewIntent。如果不在栈内的话,就新建一个入栈。如果需要的栈不存在,就新建一个栈然后再入栈。

(4)singleInstance

如果说singleTask是栈内唯一实例的话,singleInstance就是全局唯一,会为该实例单独分配一个任务栈,因此singleInstance也是。同一时间内,系统只会存在这一个实例。

TaskAffinity

此外存在一个属性 TaskAffinity,可以通过设置这个属性,来指定activity需要所在的任务栈。如果此时系统内不存在该任务栈,就会新建一个任务栈,从而实现一个应用程序多个栈的情况。默认情况下,所有activity所需要的任务栈名称为应用的包名,如果需要指定的话,必须不能和包名相同。该属性必须和singleTask或者allowTaskReparenting配对使用。

allowTaskReparenting和TaskAffinity配合使用时,如果allowTaskReparenting设置为true时,能够实现activity的迁移。比如app A 启动了app B中的某个activity C,此时C处于app A的任务栈中,但是如果一旦系统中出现了app B的任务栈(比如此时打开了app B),那么C就会从A的任务栈中,转移到B的任务栈,此时打开app B直接是activity c【突发奇想,是不是类似于有人在朋友圈分享知乎,然后点进去打开知乎就直接是该界面是不是就是这种实现思路?】。用生活中的形象比喻就是,你在路上捡到了一条走丢的狗,可以带回家养着,但是如果狗主人出现了,你还是得把狗还给原主人。

另外任务栈分为前台任务栈和后台任务栈,后台任务栈的activity处于paused状态。

2.2Flags

除了在manifest中设置launchMode以外,还可以在代码中调用addFlags指定启动模式。另外后者的优先级大于前者,两者同时指定时,addFlags会覆盖launchMode.

android:launchMode="singleTask"
Intent intent=new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

Activity的Flags有很多,这里主要介绍比较常用的。标记位的作用很多,除了能指定启动方式以外,还能影响运行状态。

1.FLAG_ACTIVITY_SINGLE_TOP

效果等同于直接指定 singleTop

2.FLAG_ACTIVITY_NEW_TASK

效果等同于singleTask

3.FLAG_ACTIVITY_CLEAR_TOP

具有此标志位的activity启动时,通常会将此标识位和FLAG_ACTIVITY_NEW_TASK配合使用。

4.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

具有此标志位的activity不会出现在历史activity的列表中,简而言之就是按back不会回退到该activity。

3 IntentFilter匹配规则

actvitiy分为两种启动模式,分别是显式调用和隐式调用。隐式调用需要指定IntentFilter,其中包含了action、category、data。只有三个同时匹配时(不需要三个必须同时存在,如果没有则默认匹配成功),才能算IntentFilter匹配成功。此外一个activity可以有多个IntentFilter,一个intent只需要匹配成功一个就算匹配成功,能够启动对应的activity。

action

action是一个字符串。一个IntentFilter可以有多个action,只需要匹配任意一个action,就算action项匹配成功。action的匹配规则是,intent中的action字符串必须和IntentFilter中的action的字符串值完全相同,并且区分大小写。

category

category也是一个字符串。系统预定义了一些category,也可以自定义category。一个IntentFilter中可以有多个category,但在匹配时,intent中添加的所有category必须匹配所有的category才能算匹配成功。此外如果intent不设置category,则有默认值default

data

data由两部分组成,分别是miniType和URI,miniType指的是媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*。而uri的结构如下所示。

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
举几个例子
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.comn:80/search/info

scheme:代表url的模式,比如http、file、content等,这是必填项

host:代表uri的主机名,如www.baidu.com,这也是必填项。

port:uri中的端口号,这是选填项。

path、pathPrefix、pathPattern:这三个参数表示路径的信息,其中path表示完整路径,pathPrefix表示带有路径的前缀信息,pathPattern代表有通配符“*”表达式来匹配路径。

匹配符号:

  1. “*” 用来匹配0次或更多,如:“a*” 可以匹配“a”、“aa”、“aaa”…
  2. “.” 用来匹配任意字符,如:“.” 可以匹配“a”、“b”,“c”…
  3. 因此 “.*” 就是用来匹配任意字符0次或更多,如:“.*html” 可以匹配 “abchtml”、“chtml”,“html”,“sdf.html”…

转义:

因为当读取 Xml 的时候,“\” 是被当作转义字符的(当它被用作 pathPattern 转义之前),因此这里需要两次转义,读取 Xml 是一次,在 pathPattern 中使用又是一次。如:“” 这个字符就应该写成 “\”,“\” 这个字符就应该写成 “\\”。

比如:http://example.com/blog/abc.html

path只需要写 path=blog/abc.html,pathPrefix只需要pathPrefix=/blog

比如相匹配以html的文件,可以".*\\.html" 其中.*代表匹配之前任意长度的任意字符,然后\\.代表转义后的.

data的匹配规则

和action一样,只需要匹配manifest中任意一个即可。

比如如下所示的代码,image/*代表匹配所有类型的图片,此时虽没有制定uri,但是有默认值content和file,也就是说intent中的data的schema必须为content或者file才能匹配。

<intent-filter>
	……
  <data android:mimeType="image/*"/>
</intent-filter>

注意当需要同时设置uri和type的时候,必须使用setDataAndType。如果分开调用setData和setType,都会先清除对方的值,在进行赋值。

intent.setDataAndType(Uri.parse("file://abc"),"image/*");
Q&A

1.uri和url有什么区别?

uri是为了能唯一标识某个资源,url则是通过地址的方式,标识某个资源,因此url是uri的子集,是一种具体的实现方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值