三、基础组件(一) 深入理解Activity

来源:互联网 发布:签证 生物识别数据 编辑:程序博客网 时间:2024/06/06 01:58

1.Activity基础

1.1配置

在AndroidManifest文件中进行配置

<activity android:name=".ui.activity.WelcomeActivity"      android:icon="@drawable/welcome.png"      android:label="@string/welcome_word"      android:exported="true"      android:launchMode="singleTask"/>

属性含义:

name:指定Activity的实现类的类名icon:指定Activity的对应图标label:指定Activity的标签exported:指定Activity是否允许被其他应用调用,当设为true时可以被其他应用调用launchMode:指定Activity的加载模式,共有4种加载模式(standard,singleTop,singleTask,singleInstance)

1.2启动

启动共有两种方式:

startActivity(Intent intent); 启动其他的ActivitystartActivityForResult(Intrent intent,int requestCode); 指定请求码启动Activity,并且获取新启动Activity的返回结果

1.3交换数据

Intent是Activity之间的信使,将需要将交换的数据放入Intent中即可。Bundle是放入Intent中的简单的数据携带包。
当程序调用Intent的putExtra(String name,Xxx value)方法向Intent中存入数据的时候,如果该Intent中已经携带了Bundle对象,则直接向Intent所携带的Bundle中存入数据;如果还没有携带Bundle对象,purExtra(String name , Xxx value)方法会先为Intent创建一个Bundle,再向Bundle中存入数据

例:

Persong p = new Person("王二""男");Bundle data = new Bundle();data.putSerializable("person",p);Intent intent = new Intent(this,ResultActivity.class);intent.putExtras(data);startActivity(intent);

2.Activity典型情况的生命周期

这里写图片描述

onCreate : 该方法是在Activity被创建时回调,它是生命周期第一个调用的方法,我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。

onStart : 此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见摆了。

onResume : 当此方法回调时,则说明Activity已在前台可见,可与用户交互了(处于前面所说的Active/Running形态),onResume方法与onStart的相同点是两者都表示Activity可见,只不过onStart回调时Activity还是后台无法与用户交互,而onResume则已显示在前台,可与用户交互。当然从流程图,我们也可以看出当Activity停止后(onPause方法和onStop方法被调用),重新回到前台时也会调用onResume方法,因此我们也可以在onResume方法中初始化一些资源,比如重新初始化在onPause或者onStop方法中释放的资源。

onPause : 此方法被回调时则表示Activity正在停止(Paused形态),一般情况下onStop方法会紧接着被回调。但通过流程图我们还可以看到一种情况是onPause方法执行后直接执行了onResume方法,这属于比较极端的现象了,这可能是用户操作使当前Activity退居后台后又迅速地再回到到当前的Activity,此时onResume方法就会被回调。当然,在onPause方法中我们可以做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause方法执行完成后,新Activity的onResume方法才会被执行。

onStop : 一般在onPause方法执行完成直接执行,表示Activity即将停止或者完全被覆盖(Stopped形态),此时Activity不可见,仅在后台运行。同样地,在onStop方法可以做一些资源释放的操作(不能太耗时)。

onRestart :表示Activity正在重新启动,当Activity由不可见变为可见状态时,该方法被回调。这种情况一般是用户打开了一个新的Activity时,当前的Activity就会被暂停(onPause和onStop被执行了),接着又回到当前Activity页面时,onRestart方法就会被回调。

onDestroy :此时Activity正在被销毁,也是生命周期最后一个执行的方法,一般我们可以在此方法中做一些回收工作和最终的资源释放。

调用说明
(1)对于一个特定的Activity,第一次启动,回调如下:onCreate -> onStart -> onResume
(2)当用户打开新的Activity或者切换回到桌面的时候,回调如下:onPause -> onStop。这里有特殊情况,如果新的Activity采用了透明主题,那么当前Activity不会调用onStop
(3)当用户再次回到原Activity时,回调如下:onRestart -> onStart -> onResume
(4)当用户按back键回退时,回调如下:onPause -> onStop -> onDestory
(5)从整个生命周期来说,onCreate和onDestory是配对的,分别标识Activity的创建和销毁,并且只能被调用一次;从Activity是否可见的来说,onStart和onStop是配对的,分别标识Activity是否可见但不可操作;onResume和onPause是配对的,分别标识Activity是否可见并可操作

3.Activity异常情况下的生命周期

3.1情况一:资源相关的系统配置发生改变导致Activity被杀死并重新创建

应用程序启动的时候,系统会根据当前设备的情况去加载合适的Resources资源,比如说横屏手机和竖屏手机会使用不同的图片,当一个Activity处于竖屏状态时,如果突然旋转屏幕,由于系统配置发生了变化,默认情况下Activity就会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity。

异常情况下Activity的重建过程如下图所示:
这里写图片描述

当系统配置发生变化后,Activity会被销毁,也就是onPause,onStop,onDestroy会被依次调用,同时因为Activity是在异常情况下销毁的,android系统会自动调用onSaveInstanceState方法来保存当前Activity的状态信息,因此我们可以在onSaveInstanceState方法中存储一些数据以便Activity重建之后可以恢复这些数据,当然这个方法的调用时机必须在onStop方法之前,也就是Activity停止之前。至跟onPause方法的调用时机可以随意。
当Activity被重新创建之后,系统还会去调用onRestoreInstanceState方法,并把Activity销毁时通过onSaveInstanceState方法保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法,因此我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重新创建,倘若被重建了,我们就可以对之前的数据进行恢复。从时序上来说,onRestoreInstanceState方法的调用时机是在onStart之后的。
这里有点需要特别注意,onSaveInstanceState和onRestoreInstanceState只有在Activity异常终止时才会被调用的,正常情况是不会调用这两个方法的。

onRestoreInstanceState和onCreate方法都可以进行数据恢复,那到底用哪个啊?其实两者都可以,两者的区别在于,onRestoreInstanceState方法一旦被系统回调,其参数Bundle一定不为空,无需额外的判断,而onCreate的Bundle却不一定有值,因为如果Activity是正常启动的话,Bundle参数是不会有值的,因此我们需要额外的判断条件,当然虽说两者都可以数据恢复,官方文档的建议是采用onRestoreInstanceState方法。

3.2情况二:资源内存不足导致低优先级的Activity被杀死

这种情况不好模拟,但是其数据存储和恢复过程和情况一是完全一致的。重点是当系统内存不足的时候,系统会按照一定的优先级去杀死目标Activity的进程来回收内存,并且此时的Activity的onSaveInstanceState方法会被调用来存储数据,并且后续Activity恢复时调用onRestoreInstanceState方法来恢复数据。Activity的优先级从高到低分以下三种情况:
(1)前台Activity:正在和用户进行交互的Activity,优先级最高
(2)可见但非前台Activity:比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法与用户直接交互
(3)后台Activity:已经被暂停的Activity,比如执行了onStop,优先级最低

3.3解决Activity销毁重建问题

通过上面的分析我们知道当系统配置发生变化后,Activity会被重建,那有没有办法使其不重建呢?方法自然是有的,那就是我们可以给Activity指定configChange属性,当我们不想Activity在屏幕旋转后导致销毁重建时,可以设置configChange=“orientation”;当SDK版本大于13时,我们还需额外添加一个“screenSize”的值,对于这两个值含义如下:
orientation:屏幕方向发生变化,配置该参数可以解决横竖屏切换时,Activity重建问题(API<13)
screenSize:当设备旋转时,屏幕尺寸发生变化,API>13后必须配置该参数才可以保证横竖切换不会导致Activity重建。
说白了就是设置了这两个参数后,当横竖屏切换时,Activity不会再重建并且也不会调用之前相关的方法,取而代之的是回调onConfigurationChanged方法。
例:

4.Android的启动模式

4.1Activity的LaunchMode

Activity目前有四种启动模式:standard,singleTop,singleTask和singleInstance
(1)standard:标准模式,也是默认模式。每次启动一个Activity都会重新创建一个实例,不管这个实例是否存在。这是一种典型的多实例实现,一个任务栈可以有多个实例,每个实例也可以属于不同的任务栈。比如:在Activity A中启动了Activity B,那么B就会进入到A所在的任务栈中,在Activity B中又启动Activity A ,那么A又会进入任务栈中(Activity A和Activity B都是standard模式)。如图:
这里写图片描述
(2)singleTop:栈顶复用模式。在这模式下,如果Activity已经位于任务栈的栈顶,那么该Activity将不会被重新创建。比如,目前任务栈内有Activity A,在Activity A中启动Activity B,任务栈中会有A、B。如果再启动B,任务栈中任是A、B。其中B的启动模式为singleTop,如图:
这里写图片描述

(3)singleTask:栈内复用模式。在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例。
如果当前任务栈S1有ABCD四个Activity,这个时候B以singleTask模式请求启动,那么任务栈会将B以上的Activity实例全部出栈,此时S1任务栈中的情况为AB。
如果目前任务栈S1有ABC三个Activity,这个时候D以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D的实例都不存在,所以系统会先创建任务栈S2,然后再创建D的实例并将其入栈到S2。
如果目前任务栈S1有ABC三个Activity,这个时候D以singleTask模式请求启动,其所需要的任务栈为S1。由于S1已经存在,系统会直接创建D的实例并将其入栈到S1。
如图:
这里写图片描述

(4)singleInstance:单实例模式。这是一直加强的singleTask模式,它除了具有singleTask模式的所以特性以外,还加强了一点,那就是具有这种模式的Activity只能单独地位于一个任务栈中。
如图(B为singleInstance模式):
这里写图片描述

4.2Activity的Flags

Activity的Flags有很多,主要分析一些比较常用的标记位。标记位的作用很多,有些标记位可以设定为Activity的启动模式,比如FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_SINGLE_TOP等;还有的标记位可以影响Activity的运行状态,比如FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS等。大部分情况下我们不需要为Activity指定标记位,因次对于标记位理解即可。下面介绍几个比较常用的标记位:
(1)FLAG_ACITIVITY_NEW_TASK
这个标记位的作用是为Activity指定“singleTask”启动模式,其效果和在XML中指定该启动模式相同
(2)FLAG_ACITIVITY_SINGLE_TOP
这个标记位的作用是为Activity指定“singleTop”启动模式,其效果和在XML中指定该启动模式相同
(3)FLAG_ACITIVITY_CLEAR_TOP
具有次标记位的Activity,启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般要和FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况下,被启动Activity的实例如果已经存在,那么系统就会调用他的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例放入栈顶。
(4)FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记位的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。它等同于在XML中指定Activity的属性android:excludeFromRecents=”true”

5.IntentFilter的匹配规则

Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。
Intent可分为隐式(implicitly)和显式(explicitly)两种:
(1)显式调用:即在构造Intent对象时就指定接收者,它一般用在知道目标组件名称的前提下,一般是在相同的应用程序内部实现的,如下:

Intent intent = new Intent(MainActivit.this, NewActivity.class); startActivity(intent );

(2)隐式调用:即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于降低发送者和接收者之间的耦合,它一般用在没有明确指出目标组件名称的前提下,一般是用于在不同应用程序之间,如下:

Intent intent = new Intent();intent.setAction("com.action.test"); startActivity(intent);

Intent解析机制主要是通过查找已注册在AndroidManifest.xml中的所有IntentFilter及其中定义的Intent,最终找到匹配的Intent。在这个解析过程中,Android是通过Intent的action、type、category这三个属性来进行匹配判断的。一个过滤列表中的action、type、category可以有多个,所有的action、type、category分别构成不同类别,同一类别信息共同约束当前类别的匹配过程。只有一个Intent同时匹配action、type、category这三个类别才算完全匹配,只有完全匹配才能启动Activity。另外一个组件若声明了多个Intent Filter,只需要匹配任意一个即可启动该组件。 例如:

<action android:name="com.wooyun.project.SHOW_CURRENT" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="video/mpeg" android:scheme="http" . . . /> <data android:mimeType="image/*" /> <data android:scheme="http" android:type="video/*" />

5.1action的匹配规则

action是一个字符串,如果Intent指明定了action,则目标组件的IntentFilter的action列表中就必须包含有这个action,否则不能匹配。一个Intent Filter中可声明多个action,Intent中的action与其中的任一个action在字符串形式上完全相同(注意,区分大小写,大小写不同但字符串内容相同也会造成匹配失败),action方面就匹配成功。可通过setAction方法为Intent设置action,也可在构造Intent时传入action。需要注意的是,隐式Intent必须指定action。比如我们在Manifest文件中为MyActivity定义了如下Intent Filter:

<intent-filter>          <action android:name="android.intent.action.SEND"/>         <action android:name="android.intent.action.SEND_TO"/> </intent-filter>

那么只要Intent的action为“SEND”或“SEND_TO”,那么这个Intent在action方面就能和上面那个Activity匹配成功。比如我们的Intent定义如下:

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

5.2category的匹配规则

category也是一个字符串,但是它与action的过滤规则不同,它要求Intent中个如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。也就是说,Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中的定义了的category。当然,Intent中也可以没有category(若Intent中未指定category,系统会自动为它带上“android.intent.category.DEFAULT”),如果没有,仍然可以匹配成功。category和action的区别在于,action要求Intent中必须有一个action且必须和过滤规则中的某几个action相同,而category要求Intent可以没有category,但是一旦发现存在category,不论你有多少,每个都要能够和过滤规则中的任何一个category相同。我们可以通过addCategory方法为Intent添加category。

特别说明:

<intent-filter>         <action android:name="android.intent.action.MAIN" />    //决定一个应用程序最先启动那个组件        <category android:name="android.intent.category.LAUNCHER" />    //决定应用程序是否显示在程序列表里(说白了就是是否在桌面上显示一个图标) </intent-filter>

这二者共同出现,标明该Activity是一个入口Activity,并且会出现在系统应用列表中,二者缺一不可。

5.3 data的匹配规则

如果Intent没有提供type,系统将从data中得到数据类型。和action一样,同action类似,只要Intent的data只要与Intent Filter中的任一个data声明完全相同,data方面就完全匹配成功。
data由两部分组成:mimeType和URI
MineType指的是媒体类型:例如imgage/jpeg,auto/mpeg4和viedo/*等,可以表示图片、文本、视频等不同的媒体格式
uri则由scheme、host、port、path | pathPattern | pathPrefix这4部分组成

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern >]

例如:

content://com.taotie.org:200/folder/etchttp://www.taotie.org:80/search/info

Intent的uri可通过setData方法设置,mimetype可通过setType方法设置。
需要注意的是:
若Intent Filter的data声明部分未指定uri,则缺省uri为content或file,Intent中的uri的scheme部分需为content或file才能匹配;
另外,若要为Intent指定完整的data,必须用setDataAndType方法,究其原因在,setData和setType方法的源码中我们发现:这两个方法会彼此互相清除对方的值,即setData会把mimeType置为null,setType会把uri置为null。

使用案例
首先我们先来看一下Intent Filter中指定data的语法:

<data android:scheme="String.“ android:host="String" android:port="String" android:path="String" android:pathPattern="String" android:pathPrefix="String" android:mimeType="String"/> 

其中scheme、host等各个部分无需全部指定。

(1)如果我们想要匹配 http 以 “.pdf” 结尾的路径,使得别的程序想要打开网络 pdf 时,用户能够可以选择我们的程序进行下载查看。
我们可以将 scheme 设置为 “http”,pathPattern 设置为 “.*//.pdf”,整个 intent-filter 设置为:

<intent-filter>          <action android:name="android.intent.action.VIEW"></action>          <category android:name="android.intent.category.DEFAULT"></category>          <data android:scheme="http" android:pathPattern=".*//.pdf"></data> </intent-filter>

如果你只想处理某个站点的 pdf,那么在 data 标签里增加 android:host=”yoursite.com” 则只会匹配 http://yoursite.com/xxx/xxx.pdf,但这不会匹配 www.yoursite.com,如果你也想匹配这个站点的话,你就需要再添加一个 data 标签,除了 android:host 改为 “www.yoursite.com” 其他都一样。

(2)如果我们做的是一个IM应用,或是其他类似于微博之类的应用,如何让别人通过 Intent 进行调用出现在选择框里呢?我们只用注册 android.intent.action.SEND 与 mimeType 为 “text/plain” 或 “/” 就可以了,整个 intent-filter 设置为:

<intent-filter>          <action android:name="android.intent.action.SEND" />           <category android:name="android.intent.category.DEFAULT" />          <data mimeType="*/*" /> </intent-filter>

这里设置 category 的原因是,创建的 Intent 的实例默认 category 就包含了 Intent.CATEGORY_DEFAULT ,google 这样做的原因是为了让这个 Intent 始终有一个 category。

(3)如果我们做的是一个音乐播放软件,当文件浏览器打开某音乐文件的时候,使我们的应用能够出现在选择框里?这类似于文件关联了,其实做起来跟上面一样,也很简单,我们只用注册 android.intent.action.VIEW 与 mimeType 为 “audio/*” 就可以了,整个 intent-filter 设置为:

<intent-filter>          <action android:name="android.intent.action.VIEW" />          <category android:name="android.intent.category.DEFAULT" />          <data android:mimeType="audio/*" /> </intent-filter>

5.4注意事项

(1)当我们通过隐式方式启动一个Activity的时候,可以做一下判断,看是否有Activity能够匹配我们的隐式Intent,如果不做判断就有可能出现未找到想要启动的Activity导致崩溃的错误。判断方法有如下两种:
采用PackageManager的resolveActivity或者Intent的resolveActivity方法会获得最适合Intent的一个Activity

public abstract ResolveInfo resolveActivity(Intent intent,int flags);

调用PackageManager的queryIntentActivities会返回所有成功匹配Intent的Activity

public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);

(2)关于隐式Intent
每一个通过 startActivity() 方法发出的隐式 Intent 都至少有一个 category,就是“android.intent.category.DEFAULT”,所以只要是想接收一个隐式 Intent 的 Activity 都应该包括 “android.intent.category.DEFAULT” category,不然将导致 Intent 匹配失败.
比如说一个activity组件要想被其他组件通过隐式intent调用, 则其在manifest.xml中的声明如下:

<activity android:name="com.taotie.org.MainActivity">       <intent-filter>          <action android:name="com.google.test" />          <category android:name="android.intent.category.DEFAULT" />      </intent-filter> </activity>

(3)intentFilter的匹配优先级
查看Intent的过滤器(intent-filter),按照以下优先关系查找:action->data->category