天天看點

Android近期任務清單Recent List(Recents Screen)的實作方式

一、明确android的近期任務是什麼:

我們的手機下方一般有三個鍵,一個是傳回鍵,中間的是home鍵,另一個是RecentList鍵,也就是最近浏覽記錄的記錄鍵,這個的實作在4.0及以上版本使用,android 5.0(api 21)之後,為了系統的安全性,不再允許被第三方開發人員使用,也就是api中不再被使用。但是,為了向前的相容性,還是允許使用獲得近期浏覽記錄的api,隻是隻能獲得部分不敏感資料。

它的樣子:

Android近期任務清單Recent List(Recents Screen)的實作方式

就是這樣的一個清單,具體實作的原理,這裡簡單講解一下:

我們的桌面簡單講就是一個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)的實作方式

ActivityManagerService與ActivityManager之間的通信是通過Binder機制來完成的,具體如何實作的呢:

ActivityManagerNative中實作的代碼是運作在Android應用程式的程序空間中的,可直接使用的對象,Intent會由應用程式通過這個類将方法對應的Binder指令發送出去,而它本身繼承了Binder類,并實作了ActivityManager接口,源碼如圖:

Android近期任務清單Recent List(Recents Screen)的實作方式

是以它可以獲得ActivityManager關于記憶體、任務等内部資訊,而ActivityManagerService作為ActivityManagerNative的子類,自然也就可以獲得這些資訊。

例如:

ActivityManager中的方法getAppTasks()方法:

Android近期任務清單Recent List(Recents Screen)的實作方式

我們會發現這些方法都會先調用ActivityManagerNative的getDefault()方法來獲得ActivityManager的代理接口對象。那麼getDefault()方法又是什麼呢?

我們打開這個方法會發現,如圖:

Android近期任務清單Recent List(Recents Screen)的實作方式
Android近期任務清單Recent List(Recents Screen)的實作方式

我們會發現,它主要是調用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中是沒有的。

如圖:

Android近期任務清單Recent List(Recents Screen)的實作方式

(這是源碼中的解釋。在此之前,我嘗試了很多種方法去解決删除task棧中的元素方法,但是發現沒有removeTask方法,而通過停止運作process或者強制結束運作應用的方法都無法删除RecentList中的資料,因為它隻是一個記錄棧,而且屬于系統級别的Task棧,必須獲得系統的這個資料棧才能将它删除掉。)

另一種獲得removeTask的方法是反射,我嘗試了一下網上的方法,并不行,因為反射我也不會,是以不确定是個人問題還是方法的問題。

我這裡實作的方式是導入系統的架包,直接擷取的方法,如圖:

Android近期任務清單Recent List(Recents Screen)的實作方式

(關于引入系統架包與本地SDK沖突的解決方式,比較簡單的方式是更改項目下的編譯時的擷取api的加載順序,以後我會專門寫一個部落格詳細講解。)