Android开发艺术探索(Activity)

来源:互联网 发布:犀牛软件雕花教程 编辑:程序博客网 时间:2024/06/05 15:04

一、Activity生命周期分析

1:典型情况下的生命周期分析

(1)、

onCreate:表示Activity正在被创建,这是生命周期的第一个方法onRestart:表示Acitivity正在重新启动,一般情况下,onRestart是在当前activity从不可见重新变为可见状态时被调用onStart:表示Activity正在被启动,在逻辑意义上Activity已经是可见的,但是我们用户并不能看到。onResume:Activity已经可见了,并且出现在前台且开始活动。onPause:表示Activity正在停止,正常情况下,紧接着onStop就会被调用。在这个函数里面我们可以做一些数据储存以及停止动画等工作,但是不可以耗时,因为新的Activity的onResume是必须等待旧activity的onPause执行完毕才可以执行。onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,但是也不可以过于耗时。onDestroy:表示Activity即将被销毁,这是Activity生命周期的最后一个回调,在这里我们可以做一些回收工作以及最终资源的释放。

这里写图片描述

(2):常见活动里Activity的生命流程

(1):当一个Activity第一次被启动的时候,以次:OnCreate->onStart->onResume(2):当用户打开别的Activity或者按home键的时候,onPause->onStop。(如果新的Activity采用了透明主题,那么这当前的Activity不会onStop)(3):用户再次返回到一个Activity的时候onRestart->onStart->onResume(4):当用户点击back键回退时:onPause->onStop->onDestroy(5):Destroy之后的activity在此打开生命周期是同第一次打开的,但是所用过程不是都相同

2:异常情况下的生命周期分析
(1):资源相关的系统配置发生改变导致Activity被杀死并重新创建。
举例:如果我们设定了landscape或者portrait状态下的图片,我们将手机横屏的时候和将手机竖屏的时候系统会根据当前的设备情况去加载合适的Resources资源。当当前的Activity处于竖屏状态,如果我们突然旋转屏幕,由于系统配置发生了改变,在默认的情况下,Activity就会被销毁并重新创建,其生命周期如图所示:
这里写图片描述
当系统配置发生改变后,Activity会被销毁,其onPause,onStop,onDestroy均会被调用,同时由于Activity是在异常的情况下终止的,系统会调用onSaveInstanceState来保存当前的Activity的状态。这个方法的调用是在onStop之前,但是它和onPause没有确定的时许关系。需要强调的是,这个方法只会出现在Activity被异常终止的情况下。当Activity被重新创建之后,系统会调用onRestoreInstanceState,斌且把Activity销毁时onSaveInstanceState所保存的Bundle对象作为参数同时传递给onRestoreInstancState和onCreate方法。因此我们可以通过onCreate方法和onRestoreInstanceState来判断Acitivity是否被重新创建了。从时序上来说,onRestoreInstanceState的调用时机在onStart之后。
除了这些,在onSaveInstanceState和onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作。当Activity在异常情况下需要重新创建的时候系统会默认为我们保存当前Activity的视图结构,并且在Activity重启之后为我们恢复这些数据(比如:文本框中的用户输入,ListView的滚动位置)
关于保存和恢复View的层次结构,系统的工作流程是这样的:首先Activity被意外终止时。

        1、Activity调用onSaveInstanceState        2、Activity委托Window去保存数据        3、Window再委托它的顶层容器去保存数据,顶层容器是一个ViewGroup,一般来说它是DecorView。        4、顶层容器一一通知它的子元素来保存数据。完成!

这种委托思想在Android里面有很多应用,View的绘制,事件的分发等,当然我们也可以阻止系统重新创建Activity。如果系统配置中有很多内容,如果当某项内容发生改变之后,我们不想让系统重新创建Activity,可以给Activity指定configChanges属性。比如Activity在屏幕旋转的时候被重新创建就可以给configChanges添加

    android:configChanges="orientation"

如果想指定多个值,多个值直接可以用 | 链接,下面是系统配置所含项目
这里写图片描述

(2):资源内存不足导致低优先级的Activity被杀死
这种情况下,Activity的数据恢复过程和情况1完全一致。这里我们描述一下Activity的优先级情况。Activity按照优先级的从高到低,有高到低可以分为如下三种:

1、前台Activity--正在交互2、可见但是非前台--比如由于对话框的弹出致使Activity可见但是位于后台,无法直接与用户交互3、后台Activity--比如执行过onStop的Activity

二、Activity的启动模式

1、Activity的LaunchMode

Activity为什么需要启动模式。默认情况下,当我们多次启动同一个Activity的时候,系统默认会创建多个实例,并把它们一一放入任务栈中,当我们点击back键,会发现这些Activity会一一回退。多次启动同一个Activity,系统重复创建多个实例,这样很浪费资源。但是我们可以修改系统的默认行为,目前有四种启动模式:standard,singleTop,singleTask,singleInstance。

(1)standard:标准模式

 这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下Activity的生命周期。这是一种典型的多实例的实现。一个任务栈里面可以有多个实例,而且每个实例也可以属于不同的任务栈。在这种模式下,谁启动了Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如Activity A 启动了 Activity B(B是标准模式),那么B就会进入到A所在的栈中。所以当我们的Activity是standard模式下,我们不可以用ApplicationContext去启动Activity,不然会报错如下:

这里写图片描述

(2)singleTop:栈顶复用模式。

 在这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前的请求的信息。需要注意的是,这个Activity的onCreate,onStart不会被系统调用,因为它们没有发生改变。如果新Activity的实例已经存在,但是不是位于栈顶,那么新的Activity仍然会被重新创建。

(3)singleTask:栈内复用模式。

 这是一种单实例模式,在这种情况下,只要Activity在一个栈中存在,那么多次启动这个Activity都不会重新创建实例,和singleTop一样,系统也会调用其onNewIntent。具体的来说,当一个singleTask模式的Activity请求启动后,系统会首先寻找是否存在A想要的任务栈。如果不存在,那么重新创建一个任务栈,然后创建A的实例,并把A放到任务栈中。如果存在A所需的任务栈,这时候会查看任务栈里面是否有A的实例,如果有,那么系统会把A的实例调到栈顶并调用它的onNewIntent方法。如果不存在,创建A的实例,并把它压栈。下面是例子:


1、目前任务栈S1中的情况为ABC,这时候Activity D以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例,并将其入栈到S2。
2、假设D所需的任务栈为S1,S1中是ABC,那么系统会直接创建D的实例,并将它压入到S1中。
3、如果D所需的任务栈为S1,S1现在有ABDC,系统会把D切换到栈顶并调用D的onNewIntent方法,同时由于singleTask默认具有clearTop效果,这会导致所有在D上面的Activity全部出栈。所以C会出栈,S1最终为ABD

(4)singleInstance:单实例模式。

 这是一种加强版的singleTask模式,它除了具有singTask模式的所有特性之外,还加强了一点,那就是具有这种模式的Activity只能单独的位于一个任务栈中,换句话说,比如Activity A 是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A肚子在这个新的任务栈中,由于栈内的复用特性,后续的请求均不会创建新的Activity,除非这个任务栈被系统所销毁。

上面的几种启动模式,这里还需要指出一种情况。目前有2个任务栈,前台任务栈的情况为AB,而后台任务栈的情况为CD,假设CD的启动模式均为singleTask,现在请求启动D,那么整个后台任务栈的任务都会被切换到前台,这时候整个后退列表变成了ABCD。当用户按back键的时候,列表中的Activity会一一出栈。

这里写图片描述

如果不是请求启动D而是启动C,那么情况就不一样了,如下图
这里写图片描述

另外一个问题是,在singleTask启动模式中,多次提到某个Activity所需的任务栈,什么是Activity所需的任务栈?这需要从一个参数说起,TaskAffinity,任务相关性,这个参数标示了一个Activity所需要的任务栈名字。默认的情况下,所有的Activity所需的任务栈名字应该为应用的包名。当然,我们可以为每个Activity都单独指定TaskAffinity属性。这个属性值不能和包名相同,否则就相当于没有指定。TaskAffinity属性主要和singleTask启动模式或者allowTaskReparening属性配对使用,在其他情况下没有意义。另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调入前台。

当TaskAffinity和singleTask启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity名字相同的任务栈中。

如何指定Activity的启动模式?

1、第一种是通过AndroidManifest为Activity指定启动模式,直接在activity的属性里面增加

<activity    android:name="com.....Activity"    android:launchMode="singleTask"/>

2、另外一种情况是通过在Intent中设置标志为来为Activity指定启动模式,比如:

Intent intent = new Intent();intent.setClass(MainActivity.this,SecondActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//注意这个flag就是singleTaskstartActivity(intent);

这两种方式都可以为Activity指定启动方式,但是二者还是有区别的。
优先级上: 2 > 1,两种同时存在,按照第2种为准
限定范围上:
第一种无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP
第二种无法直接为Activity指定为singleInstance模式。

三、Activity的Flags

Activity的flag有很多,这里主要分析一些比较常用的标志位。标志位的作用有很多,有的可以设定Activity的启动模式,比如FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_SINGLE_TOP等。还有的标记位可以影响Activity的运行状态,比如FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS等。

FLAG_ACTIVITY_NEW_TASK

这个标记位的作用是为Activity指定singleTask启动模式。其效果和在XML中指定该启动模式相同。同理FLAG_ACTIVITY_SINGLE_TOP

FLAG_ACTIVITY_CLEAR_TOP

具有此标记为的Activity,当它启动时,在同一个任务栈中所谓位于它上面的Activity都要出栈。这个模式一般和FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况下,被启动ACTIVITY的实例如果已经存在,系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。singTask启动模式默认具有此效果。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

具有这个标记的Activity不会出现在历史Activity的列表中,当某些情况下,我们不希望用户通过历史回到我们的Activity的时候,这个标记比较有用。它等同于在XML中指定Activity的属性android:excludeFromRecents=”true”.

四、IntentFilter的匹配规则

Activity的启动分为两种,显式调用和隐式调用。

隐式不明确指定启动哪个Activity,而是在Intent Filter中设置Action、Data、Category,让系统来筛选出合适的Activity。

Intent intent = new Intent();  intent.setAction("android.intent.action.SEND");  startActivity(intent);  

显式调用是我们平常用到比较多的一种

Intent intent = new Intent();intent.setClass(MainActivity.this,SecondActivity.class);startActivity(intent);

 显式调用需要明确的指定被启动对象的组件信息,包含包名和类名,而隐式调用则不需要指定组件信息。原则上一个Intent不应该即是显式调用又是隐式调用,如果二者共存的话,那么以显式调用为主。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。IntentFilter中的过滤信息有action,category,data,下面是一个过滤实例:

这里写图片描述

为了匹配过滤列表,需要同时匹配过滤列表中的action,category,data信息,否则匹配失败。一个过滤列表中的action,category和data可以有多个,所有的action,category,data分别构成不同类别,同意类别的信息共同约束当前类别的匹配过程。只有Intent完全匹配成功才能成功启动目标Activity。另外一点,一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功的启动对应的activity。
这里写图片描述

1、Action的匹配规则

action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action。action的匹配规则是Intent中的action必须能够和过滤规则中的acton匹配,这里说的匹配是指action的字符串值完全一样。一个过滤规则中可以含有多个action,只要Intent中的action能够匹配任意一个即可。如果intent里面没有指定action,那么匹配总是失败,而且action区分大小写。

2、category匹配规则

category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的一个category相同,换句话说,Intent中如果出现了category,不管有category,对于每一个category来说,他必须是过滤规则中已经定义了的category。当然,Intent中可以没有category,如果没有category的话,按照上面的描述,这个Intent仍然可以匹配成功。为什么不设置category也可以成功匹配呢?原因是系统哦你在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFALULT”这个category。同时,为了我们的activity能够接收隐式调用,就必须在intent-filter里面指定“android.intent.category.DEFAULT”这个category

3、data的匹配规则

data的匹配规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。在介绍data的匹配规则之前,我们需要先了解data的结构,因为data稍微有些复杂。

data的语法如下:
<data    android:scheme="string"    android:host="string"    android:port="string"    android:path="string"    android:pathPattern="string"    android:pathPrefix="string"    android:mimeType="string" />

data由两部分组成,mimeTye和URI。mimeType指的是媒体类型,比如image/jpeg等,可以用来表示图片,文本,视频等不同的媒体格式,而URI中包含的数据就比较多了。

URI结构:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
实例:
content://com.example.project:200/folder/subfolder/etchttps://www.baidu.com

Scheme:URI的模式,比如https,file,content,如果URI中没有指定scheme,那么整个URI的其他参数无效
Host:URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数设置无效。
Port:URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。
Path,pathPatter和pathPrefix:这三个参数表示路径信息。其中Path表示完整的路径信息。pathPatter也表示完整的路径信息,但是它里面可以包含通配符“”,这个符号表示0或者多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实字符串,那么‘’要写成”\*”,”\”要写成“\\”。pathPrefix表示的是路径前缀信息。

备注:节选自Android开发艺术探索–任玉刚

阅读全文
0 0