Android近期任务列表Recent List(Recents Screen)的实现方式

来源:互联网 发布:知柏地黄丸 多少钱 编辑:程序博客网 时间:2024/06/06 17:28

一、明确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的加载顺序,以后我会专门写一个博客详细讲解。)

阅读全文
0 0