近期任务框(就是近期打开过的应用)其实也就是一个系统级别的对话框,就是长按手机的HOME键弹出的视图。
源码中的路径为:D:\tools\android4.0.1\frameworks\base\policy\src\com\android\internal\policy\impl\RecentApplicationsDialog.java
1,显示方式,该对话框在PhoneWindowManager (路径和RecentApplicationsDialog.java同样)类中的showRecentAppsDialog方法
/** * Create (if necessary) and launch the recent apps dialog, or hide it if it is * already shown. */ void showOrHideRecentAppsDialog(final int heldModifiers, final boolean dismissIfShown) { mHandler.post(new Runnable() { @Override public void run() { if (mRecentAppsDialog == null) { mRecentAppsDialog = new RecentApplicationsDialog(mContext); } if (mRecentAppsDialog.isShowing()) { if (dismissIfShown) { mRecentAppsDialog.dismiss(); } } else { mRecentAppsDialog.setHeldModifiers(heldModifiers); mRecentAppsDialog.show(); } } }); }
2,创建对话框时配置一些参数:比如填充父窗体,没有标题,更重要的一点就是设成系统级别的对话框。
/** * 我们创建最近的应用程序对话框只是一次,它会留下(隐藏),直到用户激活。 * * @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 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); 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); //为每个TextView设置点击事件 for (TextView b : mIcons) { b.setOnClickListener(this); } }
3,为八个按钮载入最近的活动,如果最近打过的应用任务栈中超过8个也只能取八个
/** * 为八个按钮载入最近的活动,如果最近打过的应用任务栈中超过8个也只能取八个, */ private void reloadButtons() { final Context context = getContext(); final PackageManager pm = context.getPackageManager(); final ActivityManager am = (ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE); //这里是获取近期打开过的应用的RecentTaskInfo 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()); // 性能注意:android性能指南推荐迭代器来遍历集合,因为知道getRecentTasks()总是返回一个ArrayList // < >,我们将使用一个简单的指数相反。 int index = 0; int numTasks = recentTasks.size(); for (int i = 0; i < numTasks && (index < NUM_BUTTONS); ++i) { final ActivityManager.RecentTaskInfo info = recentTasks.get(i); // 目的用于调试,但不允许第一个结果创建一个空的列表 if (DBG_FORCE_EMPTY_LIST && (i == 0)) continue; Intent intent = new Intent(info.baseIntent); if (info.origActivity != null) { intent.setComponent(info.origActivity); } // 跳过当前Launcher数据,就是不把当前launcher作为近期打开过的任务。 if (homeInfo != null) { if (homeInfo.packageName.equals(intent.getComponent() .getPackageName()) && homeInfo.name.equals(intent.getComponent() .getClassName())) { continue; } } //下面做的其实就是封装一些数据给每一个对应的TextView 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; //关于setTag这个是view中集成的一个神奇的东西,后面我会专门表述一下我的见解 tv.setTag(tag); tv.setVisibility(View.VISIBLE); tv.setPressed(false); tv.clearFocus(); ++index; } } } // 处理没有图标的 mNoAppsText.setVisibility((index == 0) ? View.VISIBLE : View.GONE); // 隐藏其他的图标 for (; index < NUM_BUTTONS; ++index) { mIcons[index].setVisibility(View.GONE); } }
4,设置和显示最近的活动对话框。
/** * 设置和显示最近的活动对话框。 */ @Override public void onStart() { super.onStart(); reloadButtons(); //禁止状态栏 if (sStatusBar != null) { sStatusBar.disable(StatusBarManager.DISABLE_EXPAND); } // 注册接收广播 getContext().registerReceiver(mBroadcastReceiver, mBroadcastIntentFilter); mHandler.removeCallbacks(mCleanup); }
5,处理手指按下的动作
/** * 处理手指按下时的程序 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event)
6,处理手指离开的动作
/** * 处理手指抬起时的程序 */ @Override public boolean onKeyUp(int keyCode, KeyEvent event)
7,处理用户单击事件
/** * 用户单击处理程序。如果一个按钮被单击时,启动相应的活动。 */ @Override public void onClick(View v) { for (TextView b : mIcons) { if (b == v) { RecentTag tag = (RecentTag) b.getTag(); switchTo(tag); break; } } dismiss(); }
8,切换到所点击的应用
/** * 切换到所点击的应用 * * @param tag */ private void switchTo(RecentTag tag) { if (tag.info.id >= 0) { // 这是一个活跃的任务,所以把它移动到最近任务的前面 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); } } }
9,处理接收关闭对话框的广播
/** * 这是监听关闭系统对话框意图的广播。收到广播后立即关闭自己,为了允许高优先级的UI来接管(如电话收到了)。 */ 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(); } } } };
如果有需要从别的应用中关闭这个对话框,那么向系统发送一个广播即可:
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); this.sendBroadcast(i);