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