一、明确android的近期任务是什么:
我们的手机下方一般有三个键,一个是返回键,中间的是home键,另一个是RecentList键,也就是最近浏览记录的记录键,这个的实现在4.0及以上版本使用,android 5.0(api 21)之后,为了系统的安全性,不再允许被第三方开发人员使用,也就是api中不再被使用。但是,为了向前的兼容性,还是允许使用获得近期浏览记录的api,只是只能获得部分不敏感数据。
它的样子:
就是这样的一个列表,具体实现的原理,这里简单讲解一下:
我们的桌面简单讲就是一个launch,桌面上的每一个图标代表一个application,每次启动一个app,都会往系统的一个叫做RecentTask的栈中传入一个task(任务),当我们退出app的时候,不论home退出还是按返回键退出,它在离开当前的显示界面,也就是不在onResume状态,就是不在前台的时候,系统都会截图离开时候的状态,并记录下当前的状态信息,包括具体的Activity,以便于从后台直接调到前台来使用。这个Task栈保存的是Activity的活动状态,但是不全是一个app的,而是不同app的。
(之所以要将我们要清楚这个是什么,在于我们要认识到android系统常用的几种状态:process、task和app。因为我在开发的初期阶段,认为这是一个running application列表,一直在调用系统中运行的process,所以走偏了很久。)
具体详细介绍请查阅官方文档
二、AMS与ActivityManager的通信原理:
android系统的所以服务、进程等管理都是通过SysytenService来实现的,而管理是通过ActivityManager.java。关于它的含义,上篇博客中已经做了介绍,ActivityManager只是一个传递信息的接口,它的目的是传递需要的东西给ActivityManagerService,后者才是真正实现的方法。
关于AMS通信的原理,我这里画了一个图:
ActivityManagerService与ActivityManager之间的通信是通过Binder机制来完成的,具体如何实现的呢:
ActivityManagerNative中实现的代码是运行在Android应用程序的进程空间中的,可直接使用的对象,Intent会由应用程序通过这个类将方法对应的Binder命令发送出去,而它本身继承了Binder类,并实现了ActivityManager接口,源码如图:
所以它可以获得ActivityManager关于内存、任务等内部信息,而ActivityManagerService作为ActivityManagerNative的子类,自然也就可以获得这些信息。
例如:
ActivityManager中的方法getAppTasks()方法:
我们会发现这些方法都会先调用ActivityManagerNative的getDefault()方法来获得ActivityManager的代理接口对象。那么getDefault()方法又是什么呢?
我们打开这个方法会发现,如图:
我们会发现,它主要是调用SystemService对象,并进行它的方法调用,比如它的getService(“activity”)的调用。
而关于ServiceManager类,它是系统最最基本的一个管理类,所有的服务都是通过getService方法得到的,这里的AMS和ActivityManager的通信,就是通过得到相关的Binder来实现的。
在得到了Binder之后,就可以通过ActivityManagerProxy类来进行与AMS通信,ActivityManagerProxy继承了ActivityManager,可以看做是ActivityManager的一个代理。由此就可以通过transact传递数据给ActivityManagerService(AMS)来进行具体的处理了,处理完之后再打包成相应的Binder返回给ActivityManager。
三、RecentList的获取和删除功能的实现。
1.RecentList列表的获取:
使用的方法是ActivityManager的getRecentTasks()方法,它有两个参数,一个是最大获取的数量值,另一个是flag标志位,具体实现代码:
public static void reloadButtons(Activity activity, List<HashMap<String, Object>> appInfos,
int appNumber) {
int MAX_RECENT_TASKS = appNumber; // allow for some discards
int repeatCount = appNumber;// 保证上面两个值相等,设定存放的程序个数
/* 每次加载必须清空list中的内容 */
appInfos.removeAll(appInfos);
// 得到包管理器和activity管理器
final Context context = activity.getApplication();
final PackageManager pm = context.getPackageManager();
final ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
// 从ActivityManager中取出用户最近launch过的 MAX_RECENT_TASKS + 1 个,以从早到晚的时间排序,
// 注意这个 0x0002,它的值在launcher中是用ActivityManager.RECENT_IGNORE_UNAVAILABLE
// 但是这是一个隐藏域,因此我把它的值直接拷贝到这里
final List<ActivityManager.RecentTaskInfo> recentTasks = am
.getRecentTasks(MAX_RECENT_TASKS + 1, 0x0002);
//.getRecentTasks(MAX_RECENT_TASKS + 1, 8);
// 这个activity的信息是我们的launcher
ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(
Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
int numTasks = recentTasks.size();
for (int i = 1; i < numTasks && (i < MAX_RECENT_TASKS); i++) {
HashMap<String, Object> singleAppInfo = new HashMap<String, Object>();// 当个启动过的应用程序的信息
final ActivityManager.RecentTaskInfo info = recentTasks.get(i);
Intent intent = new Intent(info.baseIntent);
if (info.origActivity != null) {
intent.setComponent(info.origActivity);
}
/**
* 如果找到是launcher,直接continue,后面的appInfos.add操作就不会发生了
*/
if (homeInfo != null) {
if (homeInfo.packageName.equals(intent.getComponent()
.getPackageName())
&& homeInfo.name.equals(intent.getComponent()
.getClassName())) {
MAX_RECENT_TASKS = MAX_RECENT_TASKS + 1;
continue;
}
}
// 设置intent的启动方式为 创建新task()【并不一定会创建】
intent.setFlags((intent.getFlags() & ~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
| Intent.FLAG_ACTIVITY_NEW_TASK);
// 获取指定应用程序activity的信息(按我的理解是:某一个应用程序的最后一个在前台出现过的activity。)
final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
if (resolveInfo != null) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
final String title = activityInfo.loadLabel(pm).toString();
Drawable icon = activityInfo.loadIcon(pm);
//&& info.id != -1
if (title != null && title.length() > 0 && icon != null ) {
singleAppInfo.put("title", title);
singleAppInfo.put("icon", icon);
singleAppInfo.put("tag", intent);
singleAppInfo.put("packageName", activityInfo.packageName);
singleAppInfo.put("id", info.persistentId);
appInfos.add(singleAppInfo);
}
}
}
MAX_RECENT_TASKS = repeatCount;
}
(代码原文博客:
http://blog.csdn.net/benyoulai5/article/details/48447079)
2.删除具体某个应用的记录的方法:removeTask(),这里传入的参数是int型的id,这个id在RecentTaskInfo中指的是persistentId。
关于removeTask方法,这个方法只能在有系统权限下才能使用,官方API中是没有的。
如图:
(这是源码中的解释。在此之前,我尝试了很多种方法去解决删除task栈中的元素方法,但是发现没有removeTask方法,而通过停止运行process或者强制结束运行应用的方法都无法删除RecentList中的数据,因为它只是一个记录栈,而且属于系统级别的Task栈,必须获得系统的这个数据栈才能将它删除掉。)
另一种获得removeTask的方法是反射,我尝试了一下网上的方法,并不行,因为反射我也不会,所以不确定是个人问题还是方法的问题。
我这里实现的方式是导入系统的架包,直接获取的方法,如图:
(关于引入系统架包与本地SDK冲突的解决方式,比较简单的方式是更改项目下的编译时的获取api的加载顺序,以后我会专门写一个博客详细讲解。)