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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hacker_crazy/article/details/78399303
一、明确android的近期任务是什么:

我们的手机下方一般有三个键,一个是返回键,中间的是home键,另一个是RecentList键,也就是最近浏览记录的记录键,这个的实现在4.0及以上版本使用,android 5.0(api 21)之后,为了系统的安全性,不再允许被第三方开发人员使用,也就是api中不再被使用。但是,为了向前的兼容性,还是允许使用获得近期浏览记录的api,只是只能获得部分不敏感数据。

它的样子:

Android近期任务列表Recent List(Recents Screen)的实现方式_第1张图片
image

就是这样的一个列表,具体实现的原理,这里简单讲解一下:

我们的桌面简单讲就是一个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通信的原理,我这里画了一个图:

Android近期任务列表Recent List(Recents Screen)的实现方式_第2张图片
image

ActivityManagerService与ActivityManager之间的通信是通过Binder机制来完成的,具体如何实现的呢:

ActivityManagerNative中实现的代码是运行在Android应用程序的进程空间中的,可直接使用的对象,Intent会由应用程序通过这个类将方法对应的Binder命令发送出去,而它本身继承了Binder类,并实现了ActivityManager接口,源码如图:

Android近期任务列表Recent List(Recents Screen)的实现方式_第3张图片
image

所以它可以获得ActivityManager关于内存、任务等内部信息,而ActivityManagerService作为ActivityManagerNative的子类,自然也就可以获得这些信息。

例如:

ActivityManager中的方法getAppTasks()方法:


Android近期任务列表Recent List(Recents Screen)的实现方式_第4张图片
image

我们会发现这些方法都会先调用ActivityManagerNative的getDefault()方法来获得ActivityManager的代理接口对象。那么getDefault()方法又是什么呢?

我们打开这个方法会发现,如图:

Android近期任务列表Recent List(Recents Screen)的实现方式_第5张图片
image
Android近期任务列表Recent List(Recents Screen)的实现方式_第6张图片
image

我们会发现,它主要是调用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> 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 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 singleAppInfo = new HashMap();// 当个启动过的应用程序的信息
            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中是没有的。

如图:

Android近期任务列表Recent List(Recents Screen)的实现方式_第7张图片
image

(这是源码中的解释。在此之前,我尝试了很多种方法去解决删除task栈中的元素方法,但是发现没有removeTask方法,而通过停止运行process或者强制结束运行应用的方法都无法删除RecentList中的数据,因为它只是一个记录栈,而且属于系统级别的Task栈,必须获得系统的这个数据栈才能将它删除掉。)

另一种获得removeTask的方法是反射,我尝试了一下网上的方法,并不行,因为反射我也不会,所以不确定是个人问题还是方法的问题。

我这里实现的方式是导入系统的架包,直接获取的方法,如图:


Android近期任务列表Recent List(Recents Screen)的实现方式_第8张图片
image

(关于引入系统架包与本地SDK冲突的解决方式,比较简单的方式是更改项目下的编译时的获取api的加载顺序,以后我会专门写一个博客详细讲解。)
————————————————
版权声明:本文为CSDN博主「hacker_crazy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hacker_crazy/article/details/78399303

你可能感兴趣的:(Android近期任务列表Recent List(Recents Screen)的实现方式)