待学的
- 任务栈
Activity的 lauchMode
在android系统默认状态下,当我们启动activity的时候,系统会创建多个实例,并把它们放到一个任务栈中,当我们回退的时候,这些Activity 会一一回退。 任务栈是一个“先进后出”的站结构,当栈中内容为空的时候,系统会回收这个任务栈。 在创建 activity 的时候,有四种启动方式,下面以一一列举:
standard 标准的启动模式,也系统默认启动模式。 每次启动都会重建一个新的实例。它的 onCreate、 onStart、onResume 都会被调用。
一个任务栈可以有多个实例,每个实例可以属于不同的任务栈。 在这种模式下,谁启动了这个activity,那么这个activity就运行在谁的任务栈中。
当使用 ApplicationContext 去启动 standard 模式的activity时候,会报错;
这是因为非 activity 类型的 context 并没有所谓的任务栈,所以会出现问题。解决方法就是为待启动的activity指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就会新建一个任务栈,这时候,实际以 singleTask 模式启动的。singleTop 栈顶复用模式。这种模式下,如果该 Activity 已经有实例位于栈顶, 那么该 activity 的实例不会被重新重建,同时它的 onNewIntent 方法会被回调,但是 onCreate、onStart 等不会被调用。
singleTask 栈内复用模式。这是一种单例模式,在这种模式下,只要栈存在于需要的任务栈中,多次调用都不会重新创建实例, 系统也会回调 OnNewIntent。
具体说:当一个具有 singleTask 模式的activity A请求启动后, 系统首先会寻找是否存在 A 想要的任务栈, 如果不存在,就新建一个任务栈,然后创建 A的实例后放入栈中。 如果A所需的任务栈存在,这时要看A是否在栈中有实例存在, 如果有实例存在,系统会把A调到栈顶并调用它的 onNewIntent, 如果实例不存在,就创建A的实例,并压入栈中。
singleTask 默认具有 clearTop 的效果, 会导致已存在的实例之上的activity全部出栈。
- singleInstance 单实例模式,这是一种加强的 singleTask 模式,它除了具有 singleTask 模式的所有特性以外, 还加强了一点,那就是具有此种模式的 activity 只能单独地位于一个栈中。比如 Activity A是singleInstance 模式, 当A启动以后,系统会为它创建一个新的任务栈,然后 A 独自存在在这个新的任务栈中,由于栈内复用的特点, 后续均不会创建新的实例,除非这个独特的任务栈被系统销毁了。
几个问题
问题1: 文中提到的某个 activity所需的任务栈, 什么是 activity 所需要的任务栈呢?
这要从一个参数说起: TaskAffinity 翻译为任务相关性。这个参数标识了一个 activity 所需要的任务栈的名字,默认情况下,所有 activity 所需的任务栈的名字为应用的包名。 当然我们可以为每个 activity 单独指定 taskaffinity 属性,这个属性要和包名不同, 否则相当于没指定。
TaskAffinity 属性主要和 singleTask 或者 allowTaskReparenting 属性配对使用,其他情况下没意义。
当TaskAffinity和singleTask启动模式配对使用的时候, 他是具有该模式的 Activity 的目前任务栈的名字,待启动的 activity 会运行在名字和 taskAffinity 名字相同的任务栈中。
问题2 什么情况下 TaskAffinity 会生效?
只有Activity设置了 lauchMode = "singleTask" 或者 allowTaskReparenting 才起作用
Activity 的 Flags
Activity 的标记位有很多,列举一些常用的标记位。标记位有多种作用。可以设定Activity的启动模式,也可以影响 activity 的运行状态。
影响启动模式的:
- FLAG_ACTIVITY_NEW_TASK 等同于xml设置 "singleTask"启动模式
- FLAG_ACTIVITY_SINGLE_TOP 等同于 xml 中设置 "singleTop"启动模式
影响运行状态的:
- FLAG_ACTIVITY_CLEAR_TOP 如果启动模式为 "singleTask",且该activity已存在,则清除栈中该activity之上的,只调用 onNewIntent() ,不重新启动。
如果启动模式为 “standard”,那么它连同它之上的都要出栈,系统会创建新的 activity并放入栈顶。 - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 具有这个标记的不会出现在历史 activity 列表中,相当于在 xml 中设置属性 android:excludeFromRecents="true"
IntentFilter 的匹配规则
启动 Activity 有两种方式,显式启动和隐式启动。
显式启动要指定被启动的组件信息,比较简单具体不谈。
隐式启动则需要 intent 能够匹配目标组件的 IntentFilter 中所设置的过滤信息。
IntentFilter 中的过滤信息有 action、category、data 三个类别,给了匹配过滤列表需要同时匹配列表中的 action, category, data信息。一个activity可以有多个 IntentFilter, 一个intent只要能匹配其中一组 intentFilter 就可以启动对应的 activity。
- action 的匹配规则
action 是一个字符串,一个intent一般只携带一个action。如果携带多个action,则都要在 IntentFilter中包含才能正常启动,但如果 Intent 中没有指定 action 则匹配失败。 匹配时严格区分大小写。 intent.setAction("..........")
- category 的匹配规则
category 是一个字符串。 如果 intent 中携带 category,不管携带几个,都要在 IntentFilter 中找到相匹配的才能启动,若不携带, 系统会默认设置 "android.intent.category.DEFAULT"这个 category,所以在 IntentFilter 中至少要设置 <category android:name="android.intent.category.DEFAULT"/> 这一个配置项。否则启动失败。
- data 的匹配规则
data 的匹配规则和 action 类似, 但是 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由两部分组成: mimeType 和 URL.
mimeType 指媒体类型,如 text/plain,image/jpeg, audio/mpeg4-generic 和 video/* 等,可以表示 文本,图像,音频,视频等不同的媒体格式。
URL 包含的数据略多, 下面是 URL 的结构:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
例子:
content://com.example.project:200/floder/subfloder/etc
http://www.baidu.com:80/search/info
每个数据的含义:
scheme: URL的模式,如 http, file, content 等,如果URL中没有指定 scheme,那么整个 URL 的其他参数无效,也就意味着URL无效。
Host: URL的主机名,比如 www.baidu.com, 如果 host 未指定,则其他参数无效,URL也无效。
Port : 端口号, 只有指定了 scheme 和 host 时候, port 才有意义。
Path, PathPrefix, PathPattern: 这三个参数表述路径信息,其中 path 表示完整的路径信息;pathPattern
示例1:
<intent-filter>
<data android:mimeType=" image/* " />
......
</intent-filter>
这种规则制定了媒体类型为照片,那么intent中的mimeType为“ image/* ”才能匹配,这种情况下没有指定URL, 但是有默认值, 默认值是 content 和 file, 也就是说,虽然没有指定 URL,但是 intent 中的URL部分的 scheme 必须为 content 或者 file 才能匹配。
另外,想要为 Intent 指定完整的data,必须要使用 inent.setDataAndType, 不能先使用 setData 后使用 setType ,这样会清除对方的置。
实例2:
<intent-filter>
<data android scheme="http" .... android:mimeType=" video/mpeg " />
<data android scheme="http" .... android:mimeType=" audio/mpeg " />
......
</intent-filter>
这种规则指定了两组 data, 为了匹配实例2的规则,正确的 intent 应该如下:
intent.setDataAndType(Url.parse("http://abc"), "video/mpeg");
或者是
intent.setDataAndType(Url.parse("http://abc"), "audio/mpeg");
最后, 如果,匹配不到合适的 activity 怎么办?只能奔溃么?
当我们隐式启动一个 Activity 的时候,可以做一下判断,看是否能够匹配我们的 Intent 。方法有两种, 都在 Context 中:
public abstract List<ResolveInfo> getPackageManager().queryIntentActivities(Intent intent,int flag)
或者
public abstract ResolveInfo getPackageManager().resolveActivity(Intent intent,int flag);
上述两个方法,第一个参数没什么好说的, 第二个参数,我们要使用 PackageManager.MATCH_DEFAULT_ONLY 这个标记, 这个标记的含义是仅仅匹配那些在 intent-filter 中声明了 <category android:name="android.intent.category.DEFAULT"> 这个 Catrgory 的 activity, 是要返回不为null, 那么就有匹配到的activity, 就一定能隐式启动成功。因为不含有 DEFAULT 这个category 的activity 是无法接受隐式intent的。
在匹配规则中, 有一类 action 和 category 比较重要,那就是:
<action android:name="anroid.intent.action.MAIN"/>
<categoty android:name="android.intent.category.LAUCHER"/>
这两者公共来表明一个入口 activity,并且会出现在Home Launcher上。少了任何一个都不行。如果在 Intent-Filter 中还有data选项, 则不匹配限制条件,不能出现在 Home Laucher上。 如下面这种 intent-filter:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="urlscheme"
android:host="auth_activity">
</intent-filter>
</activity>
针对 service 和 BroadcastReceiver ,PacketManager 同样提供了类似的方法去获取成功匹配的组件信息。