本章,我们会使用隐式intent创建一个替换android默认启动器的应用。名为NerdLauncher。
NerdLauncher应用能列出设备上的其他应用,点选任意列表项会启动相应应用。
1. 解析隐式intent
可启动的主 activity 都有包含 MAIN 操作和 LAUNCHER 类别的 intent 过滤器,一般在 AndroidManifest.xml 中的形式如下:
1 <activity android:name=".XXXXActivity"> 2 <intent-filter> 3 <action android:name="android.intent.action.MAIN"/> 4 <category android:name="android.intent.category.LAUNCHER"/> 5 </intent-filter> 6 </activity>
可以建立一个Intent,然后从PackageManager那里获取匹配它的所有activity。
1 Intent startupIntent = new Intent(Intent.ACTION_MAIN); 2 startupIntent.addCategory(Intent.CATEGORY_LAUNCHER); 3 4 PackageManager pm = getActivity().getPackageManager(); 5 6 //查询包含Main操作和LAUNCHER类别的activity总数 7 List<ResolveInfo> activities = pm.queryIntentActivities(startupIntent, 0); 8 9 Log.i(TAG, "Found " + activities.size() + "activities");
在CriminalIntent应用中,为使用隐式intent发送crime报告,我们先创建隐式intent,再将其封
装在选择器intent中,最后调用 startActivity(Intent) 方法发送给操作系统:
Intent i = new Intent(Intent.ACTION_SEND); ... // Create and put intent extras i = Intent.createChooser(i, getString(R.string.send_report)); startActivity(i);
这里没有使用类似的处理方式。原因是MAIN/LAUNCHER intent过滤器可能无法与通过 startActivity(...) 方法发送的 MAIN/LAUNCHER 隐式intent相匹配。
startActivity(Intent)意味着启动匹配隐式intent的默认activity。调用 startActivity(Intent) 方法(或 startActivity-ForResult(...) 方法)发送隐式intent时,操作系统会悄悄为目标intent添加 Intent.CATEGORY_DEFAULT 类别。
因此,如果希望intent过滤器匹配 startActivity(...) 方法发送的隐式intent,就必须在对应的intent过滤器中包含 DEFAULT 类别。
然而定义了 MAIN/LAUNCHER intent过滤器的activity是应用的主要入口点。它只负责做好作为应用主要入口点要处理的工作。它通常不关心自己是否为默认的主要入口点,所以可以不包含CATEGORY_DEFAULT 类别。
所以,我们转而使用intent直接向PackageManager 查询带有 MAIN/LAUNCHER intent过滤器的activity。
2. 在运行时创建显示intent
要创建启动activity的显示intent,需要从ResolveInfo对象中获取activity的包名和类名。这些信息可以从ResloveInfo对象的ActivityInfo中获取。
接下来实现用户点击任意列表项,启动对应的activity,我们需要使用显式intent来启动activity。
设置列表项的点击事件
1 @Override 2 public void onClick(View v){ 3 ActivityInfo activityInfo = mResloveInfo.activityInfo; 4 /* 5 * 发送了ACTION_MAIN操作。发送的intent是否包含操作,对大多数app来说没什么区别。 6 * 不过,有些应用的启动行为可能会有所不同。取决于不同的启动要求,同样的activity可能会显示不同的用户界面。开发人员最好能明确启动意图,以便让activity完成它应该完成的任务。 7 */ 8 Intent i = new Intent(Intent.ACTION_MAIN) 9 .setClassName(activityInfo.applicationInfo.packageName,activityInfo.name); 10 11 startActivity(i); 12 }
在使用包名和类名创建显式intent时,我们使用了以下intent方法:
public Intent setClassName(String packageName, String className);
此方法和Intent构造方法public Intent(Context packageContext, Class<?> cls)结果相同,都是为 Intent 添加了 ComponentName。
也可以自己通过包名和类名创建 ComponentName ,然后使用下面的 Intent 方法创建显式:
public Intent setComponent(ComponentName component)
不过setClassName方法能够自动创建组建名,所以使用该方法需要的实现代码相对较少。
2.任务与后退栈
任务是用户比较关心的activity栈。栈底部的activity通常称为基activity。用户可以看到栈顶的activity。用户点击后退键时,栈顶activity会弹出栈外。如果当前屏幕上显示的是基activity,点击后退键,系统会退回主屏幕。在当前任务中启动activity的好处是,用户可以在任务内而不是在应用层级间导航返回.默认情况下,新 activity 都在当前任务中启动。在 CriminalIntent 应用中,无论何时启动新 activity,它都会被添加到当前任务中。即使要启动的 activity 不属于本应用,它同样也在当前任务中启动。
2.1在任务间切换
在不影响各个任务状态的情况下,overview screen可以让我们在任务间切换。例如,如果进入联系人应用,然后切换到Twitter应用查看信息,这时我们就启动了两个任务。如果再切换回联系人应用,我们在两项任务中所处的状态位置都会被保存下来。
清除任务就是从应用回退栈中清除所有activity。
2.2 启动新任务
我们需要NerdLauncher在新任务中启动activity,这样,点击NerdLauncher启动器中的应用列表项可以让应用拥有自己的任务,用户因此可以在运行的应用间自由切换。
为了在启动新activity时启动新任务,需要为intent添加一个标识。
1 Intent i = new Intent(Intent.ACTION_MAIN) 2 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 3 .setClassName(activityInfo.applicationInfo.packageName,activityInfo.name);
2.3 使用 NerdLauncher 应用作为设备主屏幕
只需要在项目的配置文件AndroidManifest.xml,对找代码清单向主过滤器添加以下节点
<category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" />
通过添加 HOME 和 DEFAULT 类别定义,NerdLauncher应用的activity会成为可选的主界面。
3. 进程与任务
进程是操作系统创建的,供应用对象生存以及应用运行的地方。
每一个activity实例都仅存在于一个进程和一个任务中,这也是进程与任务的唯一相似之处。任务只包含activity,这些activity通常来自于不同的应用。而进程则包含了应用的全部运行代码和对象。
进程与任务很容易让人混淆,主要原因在于它们不仅在概念上有某种重叠,而且通常都是以其所属应用的名称被人提及的。我们以短信应用和联系人应用为例,看看以下具体场景就会明白了(首先清理掉 overview screen 中的所有任务)。
- 打开短信应用:这里我们新建了一个任务,也新建了一个短信的进程
- 点击选择收件人,这会打开联系人应用让我们选择目标联系人: 我们仍然只有一个短信任务,其中包含了两个应用的 activity,也就是说新建了联系人的进程,这样便有了两个进程
- 直接切回主界面(而不是后退回去),打开联系人应用:这样,我们多了一个联系人的任务,并且在联系人进程中新增了一个联系人 activity 的实例。
此外,Android 并没有提供方法用来终止任务,不过,我们可以终止进程。应用商店中那些宣称自己是任务终止器的应用,实际上都是进程终止器。
4. 并发文档
在Lollipop设备上,对以 android.intent.action.SEND 或 action.intent.action.SEND_MULTIPLE 操作启动的activity,隐式intent选择器会创建独立的新任务。(在旧设备上,Gmail的activity是直接添加给CriminalIntent应用任务的。)
这种现象要归因于Lollipop中叫作并发文档(concurrent documents)的新概念。
有了并发文档,我们就可以在应用运行时动态创建任意数目的任务。在Lollipop之前,应用任务只能预先定义好,而且还要在manifest文件中明确指定。
在Lollipop设备上,如果需要应用启动多个任务,可采用两种方式:给intent打上 Intent.FLAG_ACTIVITY_NEW_DOCUMENT 标签,再调用 startActivity(...) 方法;或者在manifest文件中,为activity设置如下 documentLaunchMode :
<activity android:name=".CrimePagerActivity" android:label="@string/app_name" android:parentActivityName=".CrimeListActivity" android:documentLaunchMode="intoExisting" />
使用上述方法,一份文档只会对应一个任务。(如果发送带有和已存在任务相同数据的intent,系统就不会再创建新任务。)如果无论如何都想创建新任务,那就给intent同时打上Intent.FLAG_ACTIVITY_NEW_DOCUMENT 和 Intent.FLAG_ACTIVITY_MULTIPLE_TASK 标签,或者把manifest文件中的 documentLaunchMode 属性值修改为 always 。