使用隐式Intent,创建一个启动器应用来替换Android默认的启动器应用。
MAIN/LAUNCHER intent过滤器可能无法与通过startActivity(…)方法发送的MAIN/LAUNCHER隐式intent相匹配。
实际上,调用startActivity(intent)方法意味着“启动与发送的隐式intent相匹配的默认activity”,而不是想当然的“启动与发送的隐式intent相匹配的activity”。调用startActivity(intent)方法(或调用startActivityForResult(…)方法)发送隐式intent时,操作系统会悄然地将Intent.CATEGORY_DEFAULT类别添加给目标intent。
因而,如果希望一个intent过滤器能够与通过startActivity(intent)方法发送的隐式intent相匹配,那么必须在对应的intent过滤器中包含DEFAULT类别。
定义了MAIN/LAUNCHER intent过滤器的activity是应用的主要入口点。它只关心作为应用主要入口点处理的工作。它通常不关心自己是否属于默认的主要入口点,因此,它不必包含CATEGORY_DEFAULT类别。
MAIN/LAUNCHER intent过滤器并不一定包含CATEGORY_DEFAULT类别,因此,是否可以与通过startActivity(intent)方法发送的隐式intent相匹配,谁也说不准。所以,我们转而使用intent直接向PackageManager查询具有MAIN/LAUNCHER intent过滤器的activity。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent startupIntent = new Intent(Intent.ACTION_MAIN);
startupIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PackageManager pm = getActivity().getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(startupIntent,
0);
/** * activity标签即用户可以识别的显示名称。既然查到的activity都是启动activity,标签名通常也就是应用名。 * 按activity标签的字母顺序进行排序。 */
Collections.sort(activities, new Comparator<ResolveInfo>() {
@Override
public int compare(ResolveInfo lhs, ResolveInfo rhs) {
PackageManager pm = getActivity().getPackageManager();
return String.CASE_INSENSITIVE_ORDER.compare(lhs.loadLabel(pm)
.toString(), rhs.loadLabel(pm).toString());
}
});
ArrayAdapter<ResolveInfo> adapter = new ArrayAdapter<ResolveInfo>(
getActivity(), android.R.layout.simple_list_item_1, activities) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
PackageManager pm = getActivity().getPackageManager();
TextView tv = (TextView) view;
ResolveInfo resolveInfo = getItem(position);
tv.setText(resolveInfo.loadLabel(pm));
return view;
}
};
setListAdapter(adapter);
}
使用ActivityInfo对象中的数据信息,创建一个显式intent并启动目标activity。
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
ResolveInfo resolveInfo = (ResolveInfo) l.getAdapter().getItem(position);
ActivityInfo activityInfo = resolveInfo.activityInfo;
if(activityInfo == null) return;
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(activityInfo.applicationInfo.packageName, activityInfo.name);
/** * 在新任务中启动activity(任务即activity栈) * FLAG_ACTIVITY_NEW_TASK标志控制着每个activity仅创建一个任务。 */
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
从以上代码可以看到,作为显式intent的一部分,我们还发送了ACTION_MAIN操作。发送的intent是否包含操作,对于大多数应用来说没有什么差别。不过,有些应用的启动行为可能会有所不同。取决于不同的启动要求,同样的activity可能会显示不同的用户界面。开发人员最好能明确启动意图,以便让activity完成它应该完成的任务。
在上述代码中,使用获取的包名与类名创建一个显式intent时,我们使用了以下方法:
Public Intent setClassName(String packageName, String className)
这不同于以往创建显式intent的方式。在这之前,我们都是使用一个接受Content和Class对象的Intent构造方法:
Public Intent (Context packageContext , Class<?> cls)
该构造方法使用传入的参数来获取Intent需要的ComponentName。ComponentName由包名和类名共同组成。传入Activity和Class创建Intent时,构造方法会通过Activity类自行确定全路径包名。
也可以自己通过包名和类名创建一个ComponentName,然后使用下面的Intent方法创建一个显式intent
Public Intent setComponent(ComponentName component)
<activity android:name="com.example.nerdlauncher.NerdLauncherActivity" android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<!--通过添加HOME与DEFAULT类别定义,该应用的activity会成为可选的主界面。点击Home键,可以选择。 -->
</intent-filter>
</activity>
ResolveInfo类还提供了另一个名为loadIcon(…)的方法。可以使用该方法为每一个应用加载显示图标。
使用Activity.ACTIVITY_SERVICE常量调用Activity.getSystemService()方法,来获取ActivityManager。然后调用ActivityManager的getRunningTasks()方法,得到按运行时间由近及早排序的运行任务列表。再调用moveTaskToFront()方法实现将任意任务切换到前台。最后,要提醒的是,关于任务间的切换,还需要在配置文件中增加其他权限配置。
每一个activity实例都仅存在于一个进程和一个任务中。这也是进程与任务的唯一类似的地方。任务只包含activity,这些activity通常来自于不同应用。而进程则包含了应用的全部运行代码和对象。
activity赖以生存的进程与任务有可能不同。例如,在A应用中启动B应用(不开启新任务)。虽然B应用是在A应用任务中启动的,但它是在B应用的进程中运行的。
Android无法终止任务或替换Android默认的任务管理器,我们只能终止进程。
代码地址