Rickwan's Blog

Android知识体系(一)--四大组件之Activity

为之于未有,治之于未乱。

Activity作为Android四大组件之首,它的生命周期和启动模式在平时的开发中,重要而又常见,熟练的掌握相关知识,对于Activity的灵活运用会有很大的帮助,下面将对相关重要知识点做简单梳理。

Activity的生命周期

GUIDEREFERENCE

  • Activtivity的生命周期:
  • Fragment的生命周期:

Tips:

  • Activity采用透明主题时,不会回调onStop
  • 旋转屏幕使Activity不会重新创建的方法:
    android:configChanges=”orientation|keyboardHidden|screenSize”

Activity的启动模式

Activity的LaunchMode

有时候为了满足项目特殊需求,我们就必须使用Activity的启动模式,下面将简单的介绍一下四种启动模式:standard、singleTop、singleTask、singleInstance。

这些模式分为两大类,“standard”和“singleTop”Activity 为一类,“singleTask”和“singleInstance”为另一类。前两者启动模式的Activity可以被实例化多次。

  • standard:标准模式
    每次启动一个Activity都会创建一个新的实例
  • singleTop:栈顶复用模式
    如果目标任务的顶部已存在一个Activity实例则系统会通过调用该实例的onNewIntent()方法向其传送Intent,而不是创建新的Activity实例。
  • singleTask:栈内复用模式
    如果任务栈中已存在该Activity的实例,则系统会通过调用该实例的onNewIntent()方法向其传送Intent,而不是创建新的Activity实例,同时会将位于该实例顶部的其他实例全部移除出栈。
    此处将受TaskAffinity属性影响(用于指定Activity所关联的Task),系统将查询是否有该Activity需要的任务栈,如果不存在,就会重新创建一个任务栈,然后创建Activity的实例;如果该任务栈已存在,则继续查询是否已存在该Activity的实例,如果不存在,则创建实例并放入栈中,如果存在,则将该实例顶部的其他实例全部推出栈。
  • singleInstance:单例模式
    此模式具备singleTask模式的所有特性,此外具有此种模式的Activity只能单独地位于一个任务栈中。

taskAffinity:标识一个Activity所需要的任务栈的名称,默认情况下,所有Activity所需的任务栈的名称为应用的包名;通常和allowTaskReparenting属性结合使用。

allowTaskReparenting:允许其转移任务栈,值为字符串,中间必须含有包名分隔符“.”。
当一个应用A启动了应用B的某个Activity后,点击home键回到桌面,再点击B应用桌面图标,如果该Activity的allowTaskReparenting属性为true时,该Activity会直接从应用A的任务栈转移到B的任务栈中,同时B应用将直接进入该Activity,而不是B应用的主Activity。

Activity的Flags

Activity的Flags有很多,详情请查看官方文档,这里介绍几种比较常用的标记位。标记位的作用有很多,有的可以设定Activity的启动模式。

  • FLAG_ACTIVITY_NEW_TASK:
    为Activity指定“singleTask”启动模式
  • FLAG_ACTIVITY_SINGLE_TOP:
    相当于LauchMode中的singleTop。
  • FLAG_ACTIVITY_CLEAR_TOP:
    在同一个任务栈中所有位于它上面的Activity都要出栈。这个标记位一般会和singleTask启动模式一起出现,在这种情况下,被启动的Activity的实例如果已经存在,系统就会调用它的onNewIntent。如果被启动的Activity采用standard启动模式,那么它连同它之上的Activity都要出栈,系统会创建新的实例并放入栈顶。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:
    具有这个标记位的Activity不会出现在历史Activity的列表中,如果调出的Activtivity只是一个功能片段,并没有实际的意义,此时长按Home键调出最近使用过的程序类表中,将不会出现此Activity。
  • FLAG_ACTIVITY_NO_HISTORY:
    使用这个FLAG启动的Activity,一旦退出,它不会存在于栈中,比如说,原来是A,B,C这个时候在C中以这个FLAG启动D的,D再启动E,这个时候栈中情况为A,B,C,E。
LaunchMode和Flags的运用

设置Activity启动模式的两种方法:

  • 通过在AndroidMenifest.xml中设置:

    1
    2
    3
    4
    <activity
    android:name=".ui.activity.MainActivity"
    android:screenOrientation="portrait"
    android:launchMode="singleTask"/>
  • 通过Intent设置标志位

    1
    2
    3
    Intent intent = new Intent(this,MainActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

以上两种方式都可以为Activity设置启动模式,当两者同时存在时,将以第二种为准,第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种则无法为Activity指定singleInstance模式。

IntentFilter的匹配规则 官方地址

启动Activity分为显示调用和隐式调用,显示调用需要明确地指定被启动对象的组件信息,包括包名和类名,而隐式调用不需要明确指定组件信息,但需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息。只有同时匹配过滤列表中的action、category、data信息,才能完成匹配并成功启动目标Activity,一个Activity中可以拥有多个IntentFilter,一个Intent只要能匹配任何一组IntentFilter即可成功启动对应的Activity。

action的匹配规则 官方地址

action是一个字符串,系统预定义了一些action,同时我们也可以自定义,每个过滤规则中可以有多个action。只要Intent中的action与过滤规则中的任何一个action字符串值完全一样(区分大小写),即可匹配成功,即action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同。

category的匹配规则 官方地址

category是一个字符串,系统预定义了一些category,同时我们也可以自定义,每个过滤规则中可以有多个category。Intent中所有的category必须和过滤规则其中一个category相同(字符串值完全一样,区分大小写)。系统在调用startActivity或者startActivityForResult时会默认为Intent加上“android.intent.category.DEFAULT”这个category,所以为了我们的activity能够接收隐式调用,就必须在intent-fileter中指定“android.intent.category.DEFAULT”。

data的匹配规则官方地址

data的匹配规则和action类似,如果过滤规则中定义了data,那么Itent中必须也要有定义可匹配的data。data由mimeType和URI组成,mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/* 等,可以表示图片、文本、视频等不同的媒体格式;而URI中包含数据比较多一些,结构如下:

1
2
3
4
<scheme>://<host>:<post>/[path]/[pathPrefix]/[pathPattern]
content://com.example.projce:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
1
2
3
4
5
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" android:mimeType="image/*" />
</intent-filter>

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

data的匹配规则和action类似,它要求Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data。

实例一:

1
2
3
4
<intent-filter>
<data android:mimeType="image/*" />
...
</intent-filter>

这种规则指定了媒体类型为所有类型的图片,那么Intent中的mimeType属性必须为“ image/* ”才能匹配,此时虽然过滤规则没有指定URI,但Intent中的URI部分的scheme必须为content或者file才能匹配,如下所示:

1
intent.setDataAndType(Uri.parse("file://abc"),"image/png");

另外,如果要为intent指定完整的data,必须要调用setDataAndType方法,setData和setType方法会清除对方的值。

实例二:

1
2
3
4
5
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" .../>
<data android:mimeType="audio/mpeg" android:scheme="http" .../>
...
</intent-filter>

这种规则指定了两组data规则,且每组都指定了完整的属性值,既有URI又有mimeType,

1
2
3
intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg");
或者
intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg");

关于data还有一个特殊情况,这也是它和action不同的地方,如下两种写法作用是一样的:

1
2
3
4
5
6
7
8
9
<intent-filter . . . >
<data android:scheme="something" android:host="project.example.com" />
. . .
</intent-filter>
<intent-filter . . . >
<data android:scheme="something" />
<data android:host="project.example.com" />
. . .
</intent-filter>

最后,当我们通过隐式方式启动一个Activity的时候,可以先判断是否有Activity能够匹配我们的隐式Intent,判断方法有两种:采用PackageManager的queryIntentActivities方法,或者Intent的resolveActivity方法,如果找不到匹配的Activity就会返回null。

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

两个方法中的flags需要使用MATCH_DEFAULT_ONLY这个标记位,这个标记位的含义是仅仅匹配那些在intent-filter中声明了“android.intent.category.DEFAULT”这个category的Activity,其中原因在category匹配规则中已有说明。