轉自 :
http://www.cnblogs.com/coding-way/archive/2013/06/05/3118732.html
這裏的近期任務列表就是長按Home鍵出來的那個Dialog,裏面放着近期打開過的應用,當然3.0以上系統的多任務切換鍵也是。
這個Dialog的實現在Android源碼的/frameworks/base/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java中。
接下來就對這個源碼分析一下。
- public class RecentApplicationsDialog extends Dialog implements OnClickListener {
- // Elements for debugging support
- // private static final String LOG_TAG = "RecentApplicationsDialog";
- private static final boolean DBG_FORCE_EMPTY_LIST = false;
- static private StatusBarManager sStatusBar;
- private static final int NUM_BUTTONS = 8;
- private static final int MAX_RECENT_TASKS = NUM_BUTTONS * 2; // allow for some discards
- final TextView[] mIcons = new TextView[NUM_BUTTONS];
- View mNoAppsText;
- IntentFilter mBroadcastIntentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- class RecentTag {
- ActivityManager.RecentTaskInfo info;
- Intent intent;
- }
- Handler mHandler = new Handler();
- Runnable mCleanup = new Runnable() {
- public void run() {
- // dump extra memory we're hanging on to
- for (TextView icon: mIcons) {
- icon.setCompoundDrawables(null, null, null, null);
- icon.setTag(null);
- }
- }
- };
- public RecentApplicationsDialog(Context context) {
- super(context, com.android.internal.R.style.Theme_Dialog_RecentApplications);
- }
- /**
- * We create the recent applications dialog just once, and it stays around (hidden)
- * until activated by the user.
- *
- * @see PhoneWindowManager#showRecentAppsDialog
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Context context = getContext();
- if (sStatusBar == null) {
- sStatusBar = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE);
- }
- Window window = getWindow();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
- window.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- window.setTitle("Recents");
- setContentView(com.android.internal.R.layout.recent_apps_dialog);
- final WindowManager.LayoutParams params = window.getAttributes();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.MATCH_PARENT;
- window.setAttributes(params);
- window.setFlags(0, WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- //默認顯示8個
- mIcons[0] = (TextView)findViewById(com.android.internal.R.id.button0);
- mIcons[1] = (TextView)findViewById(com.android.internal.R.id.button1);
- mIcons[2] = (TextView)findViewById(com.android.internal.R.id.button2);
- mIcons[3] = (TextView)findViewById(com.android.internal.R.id.button3);
- mIcons[4] = (TextView)findViewById(com.android.internal.R.id.button4);
- mIcons[5] = (TextView)findViewById(com.android.internal.R.id.button5);
- mIcons[6] = (TextView)findViewById(com.android.internal.R.id.button6);
- mIcons[7] = (TextView)findViewById(com.android.internal.R.id.button7);
- mNoAppsText = findViewById(com.android.internal.R.id.no_applications_message);
- //關鍵在哪,你懂得...
- for (TextView b: mIcons) {
- b.setOnClickListener(this);
- }
- }
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_TAB) {
- // Ignore all meta keys other than SHIFT. The app switch key could be a
- // fallback action chorded with ALT, META or even CTRL depending on the key map.
- // DPad navigation is handled by the ViewRoot elsewhere.
- final boolean backward = event.isShiftPressed();
- final int numIcons = mIcons.length;
- int numButtons = 0;
- while (numButtons < numIcons && mIcons[numButtons].getVisibility() == View.VISIBLE) {
- numButtons += 1;
- }
- if (numButtons != 0) {
- int nextFocus = backward ? numButtons - 1 : 0;
- for (int i = 0; i < numButtons; i++) {
- if (mIcons[i].hasFocus()) {
- if (backward) {
- nextFocus = (i + numButtons - 1) % numButtons;
- } else {
- nextFocus = (i + 1) % numButtons;
- }
- break;
- }
- }
- final int direction = backward ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD;
- if (mIcons[nextFocus].requestFocus(direction)) {
- mIcons[nextFocus].playSoundEffect(
- SoundEffectConstants.getContantForFocusDirection(direction));
- }
- }
- // The dialog always handles the key to prevent the ViewRoot from
- // performing the default navigation itself.
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
- /**
- * Dismiss the dialog and switch to the selected application.
- */
- public void dismissAndSwitch() {
- final int numIcons = mIcons.length;
- RecentTag tag = null;
- for (int i = 0; i < numIcons; i++) {
- if (mIcons[i].getVisibility() != View.VISIBLE) {
- break;
- }
- if (i == 0 || mIcons[i].hasFocus()) {
- tag = (RecentTag) mIcons[i].getTag();
- if (mIcons[i].hasFocus()) {
- break;
- }
- }
- }
- if (tag != null) {
- switchTo(tag);
- }
- dismiss();
- }
- /**
- * Handler for user clicks. If a button was clicked, launch the corresponding activity.
- */
- public void onClick(View v) {
- for (TextView b: mIcons) {
- if (b == v) {
- RecentTag tag = (RecentTag)b.getTag();
- switchTo(tag);
- break;
- }
- }
- dismiss();
- }
- //
- private void switchTo(RecentTag tag) {
- if (tag.info.id >= 0) {
- // This is an active task; it should just go to the foreground.
- final ActivityManager am = (ActivityManager)
- getContext().getSystemService(Context.ACTIVITY_SERVICE);
- am.moveTaskToFront(tag.info.id, ActivityManager.MOVE_TASK_WITH_HOME);
- } else if (tag.intent != null) {
- tag.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
- | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- try {
- getContext().startActivity(tag.intent);
- } catch (ActivityNotFoundException e) {
- Log.w("Recent", "Unable to launch recent task", e);
- }
- }
- }
- /**
- * Set up and show the recent activities dialog.
- */
- @Override
- public void onStart() {
- super.onStart();
- reloadButtons();
- if (sStatusBar != null) {
- sStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
- }
- // receive broadcasts
- getContext().registerReceiver(mBroadcastReceiver, mBroadcastIntentFilter);
- mHandler.removeCallbacks(mCleanup);
- }
- /**
- * Dismiss the recent activities dialog.
- */
- @Override
- public void onStop() {
- super.onStop();
- if (sStatusBar != null) {
- sStatusBar.disable(StatusBarManager.DISABLE_NONE);
- }
- // stop receiving broadcasts
- getContext().unregisterReceiver(mBroadcastReceiver);
- mHandler.postDelayed(mCleanup, 100);
- }
- /**
- * Reload the 6 buttons with recent activities
- */
- private void reloadButtons() {
- final Context context = getContext();
- final PackageManager pm = context.getPackageManager();
- final ActivityManager am = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(MAX_RECENT_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
- ActivityInfo homeInfo =
- new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
- .resolveActivityInfo(pm, 0);
- IconUtilities iconUtilities = new IconUtilities(getContext());
- // Performance note: Our android performance guide says to prefer Iterator when
- // using a List class, but because we know that getRecentTasks() always returns
- // an ArrayList<>, we'll use a simple index instead.
- int index = 0;
- int numTasks = recentTasks.size();
- for (int i = 0; i < numTasks && (index < NUM_BUTTONS); ++i) {
- final ActivityManager.RecentTaskInfo info = recentTasks.get(i);
- // for debug purposes only, disallow first result to create empty lists
- if (DBG_FORCE_EMPTY_LIST && (i == 0)) continue;
- Intent intent = new Intent(info.baseIntent);
- if (info.origActivity != null) {
- intent.setComponent(info.origActivity);
- }
- // Skip the current home activity.
- if (homeInfo != null) {
- if (homeInfo.packageName.equals(
- intent.getComponent().getPackageName())
- && homeInfo.name.equals(
- intent.getComponent().getClassName())) {
- continue;
- }
- }
- intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- 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);
- if (title != null && title.length() > 0 && icon != null) {
- final TextView tv = mIcons[index];
- tv.setText(title);
- icon = iconUtilities.createIconDrawable(icon);
- tv.setCompoundDrawables(null, icon, null, null);
- RecentTag tag = new RecentTag();
- tag.info = info;
- tag.intent = intent;
- tv.setTag(tag);
- tv.setVisibility(View.VISIBLE);
- tv.setPressed(false);
- tv.clearFocus();
- ++index;
- }
- }
- }
- // handle the case of "no icons to show"
- mNoAppsText.setVisibility((index == 0) ? View.VISIBLE : View.GONE);
- // hide the rest
- for (; index < NUM_BUTTONS; ++index) {
- mIcons[index].setVisibility(View.GONE);
- }
- }
- /**
- * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that
- * we should close ourselves immediately, in order to allow a higher-priority UI to take over
- * (e.g. phone call received).
- */
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
- String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
- if (! PhoneWindowManager.SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)) {
- dismiss();
- }
- }
- }
- };
- }
- RecentApplicationsDialog.java完整源碼
從源碼可以看出,關鍵部分有三處。
一個很關鍵的內部類:
// 每個任務都包含一個Tag,這個Tag保存着這個App的一些非常有用的信息 class RecentTag { ActivityManager.RecentTaskInfo info; Intent intent; }
這個RecentTag保存在每個近期任務的圖標裏,並且保存着這個任務的原始信息。
剛啓動Dialog時對每個任務的初始化:
- private void reloadButtons() {
- final Context context = getContext();
- final PackageManager pm = context.getPackageManager();
- final ActivityManager am = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
- //拿到最近使用的應用的信息列表
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(MAX_RECENT_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
- //自制一個home activity info,用來區分
- ActivityInfo homeInfo =
- new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
- .resolveActivityInfo(pm, 0);
- IconUtilities iconUtilities = new IconUtilities(getContext());
- int index = 0;
- int numTasks = recentTasks.size();
- //開始初始化每個任務的信息
- for (int i = 0; i < numTasks && (index < NUM_BUTTONS); ++i) {
- final ActivityManager.RecentTaskInfo info = recentTasks.get(i);
- //複製一個任務的原始Intent
- Intent intent = new Intent(info.baseIntent);
- if (info.origActivity != null) {
- intent.setComponent(info.origActivity);
- }
- //跳過home activity
- if (homeInfo != null) {
- if (homeInfo.packageName.equals(
- intent.getComponent().getPackageName())
- && homeInfo.name.equals(
- intent.getComponent().getClassName())) {
- continue;
- }
- }
- intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- 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);
- if (title != null && title.length() > 0 && icon != null) {
- final TextView tv = mIcons[index];
- tv.setText(title);
- icon = iconUtilities.createIconDrawable(icon);
- tv.setCompoundDrawables(null, icon, null, null);
- //new一個Tag,保存這個任務的RecentTaskInfo和Intent
- RecentTag tag = new RecentTag();
- tag.info = info;
- tag.intent = intent;
- tv.setTag(tag);
- tv.setVisibility(View.VISIBLE);
- tv.setPressed(false);
- tv.clearFocus();
- ++index;
- }
- }
- }
- ...//無關緊要的代碼
- }
這裏的過程是:先用ActivityManager獲取RecentTasks並生成一個List,然後利用這個List爲Dialog中的每個任務初始化,並生成對應的信息RecentTag。
需要注意的是,RecentTag中的Intent是從對應任務的原始Intent複製過來的,這意味着那個原始Intent的一些Extra參數將會一併複製過來,
我來舉個例子:比如我的App支持從第三方啓動,並且第三方要提供一個token,當然這個token就以Extra參數的形式放進Intent裏,然後通過startActivity()啓動我的App;然後我的App根據這個token來處理,注意這裏,當我的App退出後,再從近期任務裏啓動這個App,之前的那個token還會傳遞給我的App,這裏就會出現錯誤了,原因就是上面分析的。這就是爲什麼從第三方跳轉的應用不會出現在近期任務的列表裏(比如點擊短信裏的url啓動一個瀏覽器,之後近期任務裏只有短信app,不會出現瀏覽器app)。要想不出現在近期任務裏,可以給Intent設置FLAG_ACTIVITY_NO_HISTORY標誌。
響應每個任務的點擊事件:
- private void switchTo(RecentTag tag) {
- if (tag.info.id >= 0) {
- // 這個Task沒有退出,直接移動到前臺
- final ActivityManager am = (ActivityManager)
- getContext().getSystemService(Context.ACTIVITY_SERVICE);
- am.moveTaskToFront(tag.info.id, ActivityManager.MOVE_TASK_WITH_HOME);
- } else if (tag.intent != null) {
- //task退出了的話,id爲-1,則使用RecentTag中的Intent重新啓動
- tag.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
- | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- try {
- getContext().startActivity(tag.intent);
- } catch (ActivityNotFoundException e) {
- Log.w("Recent", "Unable to launch recent task", e);
- }
- }
- }
如果該Task沒有退出,只是切到後臺,則切換到前臺;如果已經退出,就要重新啓動了。
這裏的Intent就是之前說的,重複使用的舊Intent了,這裏注意,系統添加了FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY和FLAG_ACTIVITY_TASK_ON_HOME標誌,所以我們可以在App中通過判斷Intent的flag是否包含這兩個來判斷是否是從近期任務裏啓動的。注意FLAG_ACTIVITY_TASK_ON_HOME標誌是Api 11添加的,所以11一下的之判斷FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY就行了。
============================================================================================================================
本節內容是如何獲取Android系統中應用程序的信息,主要包括packagename、label、icon、佔用大小等。具體分爲兩個 部分,計劃如下:
第一部分: 獲取應用程序的packagename、label、icon等 ;
第二部分: 獲取應用程序的佔用大小,包括:緩存大小(cachsize)、數據大小(datasize)。
每部分都爲您準備了簡單豐富的實例,您一定不會錯過。
Android系統爲我們提供了很多服務管理的類,包括ActivityManager、PowerManager(電源管理)、AudioManager(音頻管理)
等。除此之外,還提供了一個PackageManger管理類,它的主要職責是管理應用程序包。 通過它,我們就可以獲取應用程序信息。
引入: AnroidManifest.xml文件節點說明:
相關類的介紹
PackageItemInfo類
說明: AndroidManifest.xml文件中所有節點的基類,提供了這些節點的基本信息:a label、icon、 meta-data。它並不
直接使用,而是由子類繼承然後調用相應方法。
常用字段:
public int icon 獲得該資源圖片在R文件中的值 (對應於android:icon屬性)
public int labelRes 獲得該label在R文件中的值(對應於android:label屬性)
public String name 獲得該節點的name值 (對應於android:name屬性)
public String packagename 獲得該應用程序的包名 (對應於android:packagename屬性)
常用方法:
Drawable loadIcon(PackageManager pm) 獲得當前應用程序的圖像
CharSequence loadLabel(PackageManager pm) 獲得當前應用程序的label
ActivityInfo類 繼承自 PackageItemInfo
說明: 獲得應用程序中<activity/>或者 <receiver />節點的信息 。我們可以通過它來獲取我們設置的任何屬性,包括
theme 、launchMode、launchmode等
常用方法繼承至PackageItemInfo類中的loadIcon()和loadLabel()
ServiceInfo 類
說明: 同ActivityInfo類似 ,同樣繼承自 PackageItemInfo,只不過它表示的是<service>節點信息。
ApplicationInfo類 繼承自 PackageItemInfo
說明:獲取一個特定引用程序中<application>節點的信息。
字段說明:
flags字段: FLAG_SYSTEM 系統應用程序
FLAG_EXTERNAL_STORAGE 表示該應用安裝在sdcard中
常用方法繼承至PackageItemInfo類中的loadIcon()和loadLabel()
ResolveInfo類
說明:根據<intent>節點來獲取其上一層目錄的信息,通常是<activity>、<receiver>、<service>節點信息。
常用字段:
public ActivityInfo activityInfo 獲取 ActivityInfo對象,即<activity>或<receiver >節點信息
public ServiceInfo serviceInfo 獲取 ServiceInfo對象,即<activity>節點信息
常用方法:
Drawable loadIcon(PackageManager pm) 獲得當前應用程序的圖像
CharSequence loadLabel(PackageManager pm) 獲得當前應用程序的label
PackageInfo類
說明:手動獲取AndroidManifest.xml文件的信息 。
常用字段:
public String packageName 包名
public ActivityInfo[] activities 所有<activity>節點信息
public ApplicationInfo applicationInfo <application>節點信息,只有一個
public ActivityInfo[] receivers 所有<receiver>節點信息,多個
public ServiceInfo[] services 所有<service>節點信息 ,多個
PackageManger 類
說明: 獲得已安裝的應用程序信息 。可以通過getPackageManager()方法獲得。
常用方法:
public abstract PackageManager getPackageManager()
功能:獲得一個PackageManger對象
public abstrac tDrawable getApplicationIcon(StringpackageName)
參數: packageName 包名
功能:返回給定包名的圖標,否則返回null
public abstract ApplicationInfo getApplicationInfo(String packageName, int flags)
參數:packagename 包名
flags 該ApplicationInfo是此flags標記,通常可以直接賦予常數0即可
功能:返回該ApplicationInfo對象
public abstract List<ApplicationInfo> getInstalledApplications(int flags)
參數:flag爲一般爲GET_UNINSTALLED_PACKAGES,那麼此時會返回所有ApplicationInfo。我們可以對ApplicationInfo
的flags過濾,得到我們需要的。
功能:返回給定條件的所有PackageInfo
public abstract List<PackageInfo> getInstalledPackages(int flags)
參數如上
功能:返回給定條件的所有PackageInfo
public abstractResolveInfo resolveActivity(Intent intent, int flags)
參數: intent 查尋條件,Activity所配置的action和category
flags: MATCH_DEFAULT_ONLY :Category必須帶有CATEGORY_DEFAULT的Activity,才匹配
GET_INTENT_FILTERS :匹配Intent條件即可
GET_RESOLVED_FILTER :匹配Intent條件即可
功能 :返回給定條件的ResolveInfo對象(本質上是Activity)
public abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags)
參數同上
功能 :返回給定條件的所有ResolveInfo對象(本質上是Activity),集合對象
public abstract ResolveInfo resolveService(Intent intent, int flags)
參數同上
功能 :返回給定條件的ResolveInfo對象(本質上是Service)
public abstract List<ResolveInfo> queryIntentServices(Intent intent, int flags)
參數同上
功能 :返回給定條件的所有ResolveInfo對象(本質上是Service),集合對象