1. 程式人生 > >Android近期任務列表Recent List(Recents Screen)的實現方式

Android近期任務列表Recent List(Recents Screen)的實現方式

一、明確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的載入順序,以後我會專門寫一個部落格詳細講解。)