欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Activity生命周期和启动模式的介绍

程序员文章站 2022-04-06 20:13:01
第一章 Activity生命周期和启动模式1. Activity的生命周期全面分析启动Activity有四种生命状态 ,分别是:running、paused、stopped、killed。1.1 正常生命周期onCreate(): 表示此时activity正在创建,通常进行一些初始化的方法。onStart(): 表示此时activity已经被创建,但没有位于前台,处于可见但不可交互的状态。onResume(): 表示此时activity处于前台running的状态,既可见又可交...

第一章 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即将被销毁。在这个最终的方法中,进行剩余所有资源的释放和回收。

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进行存取数据。

Activity生命周期和启动模式的介绍

这两个方法系统会为我们自动做一些存储和恢复工作,保存当前的视图结构,比如说文本框内用户输入的数据、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的子集,是一种具体的实现方式。

本文地址:https://blog.csdn.net/mrkyee/article/details/107917063

相关标签: java android