Android5.1.1源码 - 分析在最近的APP列表中清理APP的过程
@(Android研究)[Android|源码|清理APP]
[TOC]
前言
长按HOME键即可打开最近的APP列表,用手指在这个APP列表中的某个APP上向屏幕一边滑动时会关闭这个APP,本文分析的就是关闭APP的过程。
本文是流水文,没有讲清楚前因后果。 其他人看本文可以直接跳转到
当前结论
部分。
分析
长按HOME键会打开最近的APP列表,打开最近APP列表会执行下面的代码:
IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService("statusbar"));
try {
statusBarService.toggleRecentApps();
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Error toggling recent apps.");
}
这个方法先获得了IStatusBarService对象,然后调用IStatusBarService对象的toggleRecentApps()方法打开了最近APP列表。
IStatusBarService是一个接口,实际上是StatusBarManagerService对象,StatusBarManagerService类在文件"frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java"中,**statusBarService.toggleRecentApps();**语句调用的toggleRecentApps方法在StatusBarManagerService类中,下面是它的源码:
@Override
public void toggleRecentApps() {
if (mBar != null) {
try {
mBar.toggleRecentApps();
} catch (RemoteException ex) {}
}
}
mBar是StatusBarManagerService类的成员变量,它在成员方法registerStatusBar中被赋值,下面是registerStatusBar的源码:
// ================================================================================
// Callbacks from the status bar service.
// ================================================================================
@Override
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
int switches[], List<IBinder> binders) {
......
mBar = bar;
......
}
在BaseStatusBar类的start方法中调用了StatusBarManagerService.registerStatusBar方法,下面是调用这个方法的语句:
mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders);
mBarService是StatusBarManagerService类的对象,在当前我们关心mCommandQueue,因为mCommandQueue被赋值给了StatusBarManagerService.mBar。
mCommandQueue是BaseStatusBar类的成员变量,下面是对这个变量赋值的语句:
mCommandQueue = new CommandQueue(this, iconList);
这是CommandQueue类的对象,这个类的代码在文件"frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java"中。
回到StatusBarManagerService.toggleRecentApps方法中,在这个方法中执行了**mBar.toggleRecentApps();**语句,在这里调用的toggleRecentApps方法是CommandQueue.toggleRecentApps方法,下面来看一下CommandQueue.toggleRecentApps方法的的源码:
public void toggleRecentApps() {
synchronized (mList) {
......
mHandler.obtainMessage(MSG_TOGGLE_RECENT_APPS, 0, 0, null).sendToTarget();
}
}
下面是CommandQueue.mHandler成员变量的赋值的语句源码:
private Handler mHandler = new H();
H类是CommandQueue类的内部私有类,它继承了Handler类。
在CommandQueue.toggleRecentApps方法中调用了H.obtainMessage方法,在H类中的handleMessage方法会接收并处理这个消息,下面来看一下在CommandQueue.H.handleMessage方法中如何处理MSG_TOGGLE_RECENT_APPS消息:
public void handleMessage(Message msg) {
if (mPaused) {
this.sendMessageAtFrontOfQueue(Message.obtain(msg));
return;
}
final int what = msg.what & MSG_MASK;
switch (what) {
......
case MSG_TOGGLE_RECENT_APPS:
mCallbacks.toggleRecentApps();
break;
......
}
}
mCallbacks在CommandQueue类的构造方法中被赋值,CommandQueue的第一个参数赋值给了mCallbacks,在前文中在BaseStatusBar类中创建了CommandQueue对象,给CommandQueue构造方法传递的第一个参数是this,BaseStatusBar类是一个抽象类,PhoneStatusBar类继承了它,也就是说传递给CommandQueue的this是PhoneStatusBar对象,**mCallbacks.toggleRecentApps();**语句调用的是PhoneStatusBar对象对象的toggleRecentApps方法,但是在PhoneStatusBar类中没有toggleRecentApps方法,在PhoneStatusBar继承的BaseStatusBar类中找到了这个方法。
PhoneStatusBar类的代码在"frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java"文件中。
下面是BaseStatusBar.toggleRecentApps方法的源码:
@Override
public void toggleRecentApps() {
int msg = MSG_TOGGLE_RECENTS_APPS;
......
mHandler.sendEmptyMessage(msg);
}
mHandler是BaseStatusBar类的成员函数,它是BaseStatusBar的内部类H的对象,这个H对象继承了Handler类,在toggleRecentApps方法中mHandler调用了sendEmptyMessage方法发送了一条MSG_TOGGLE_RECENTS_APPS消息,BaseStatusBar.H.handleMessage方法中会处理这条消息。(分析到这里我忍不住吐槽一句,这弯弯绕又绕了回来,累不累!)
下面来看BaseStatusBar.H.handleMessage方法中如何处理MSG_TOGGLE_RECENTS_APPS消息:
public void handleMessage(Message m) {
switch (m.what) {
......
case MSG_TOGGLE_RECENTS_APPS:
if (mDeviceProvisioned) {
toggleRecents();
}
break;
......
}
}
mDeviceProvisioned不用管,toggleRecents方法在BaseStatusBar和PhoneStatusBar类中都有实现代码,先看PhoneStatusBar类中的实现,因为上文提过BaseStatusBar是抽象类PhoneStatusBar继承了BaseStatusBar类,下面是PhoneStatusBar.toggleRecents方法的源码:
@Override
protected void toggleRecents() {
// Toggle the recents visibility flag
mSystemUiVisibility ^= View.RECENT_APPS_VISIBLE;
notifyUiVisibilityChanged(mSystemUiVisibility);
super.toggleRecents();
}
前两行语句设置了一下状态,然后就调用父类的toggleRecents方法,PhoneStatusBar的父类是BaseStatusBar。
下面是BaseStatusBar.toggleRecents方法的源码:
protected void toggleRecents() {
if (mRecents != null) {
sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
}
}
mRecents的值在BaseStatusBar.start方法中由**mRecents = getComponent(RecentsComponent.class);**语句赋值,getComponent返回的是RecentsComponent类型,RecentsComponent是一个接口,实际上返回的是Recents类的对象,Recents类代码在文件"frameworks/base/packages/SystemUI/src/com/android/systemui/recent/Recents.java"中。
下面是Recents.toggleRecents方法的代码:
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
......
if (DEBUG) Log.d(TAG, "toggle recents panel");
try {
TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();
Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT);
intent.setClassName("com.android.systemui",
"com.android.systemui.recent.RecentsActivity");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
......
startActivitySafely(intent, opts.toBundle());
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentAppsIntent", e);
}
}
Recents.toggleRecents方法启动了RecentsActivity。
RecentsActivity类的代码在文件"frameworks/base/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java"中,RecentsActivity的布局文件设置代码:
setContentView(R.layout.status_bar_recent_panel);
status_bar_recent_panel.xml布局文件中包含一个"com.android.systemui.recent.RecentsHorizontalScrollView"组件,进去APP列表就是显示在这个组件中,而这个组件对应RecentsHorizontalScrollView类。
RecentsHorizontalScrollView类在文件"frameworks/base/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java"中。
经过了一系列操作执行到了RecentsPanelView.handleSwipe方法中,在这个方法中删除了相应的APP任务,下面是这个方法的源码:
public void handleSwipe(View view) {
TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
if (ad == null) {
Log.v(TAG, "Not able to find activity description for swiped task; view=" + view +
" tag=" + view.getTag());
return;
}
if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
mRecentTaskDescriptions.remove(ad);
mRecentTasksLoader.remove(ad);
// Handled by widget containers to enable LayoutTransitions properly
// mListAdapter.notifyDataSetChanged();
if (mRecentTaskDescriptions.size() == 0) {
dismissAndGoBack();
}
// Currently, either direction means the same thing, so ignore direction and remove
// the task.
final ActivityManager am = (ActivityManager)
getContext().getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
am.removeTask(ad.persistentTaskId);
// Accessibility feedback
setContentDescription(
getContext().getString(R.string.accessibility_recents_item_dismissed, ad.getLabel()));
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
setContentDescription(null);
}
}
**am.removeTask(ad.persistentTaskId);**语句删除了APP任务,这个ActivityManager.removeTask方法的调用最终执行到ActivityManagerService.removeTask方法中。
关于TaskDescription.persistentTaskId的值的获得可以参考RecentTasksLoader.loadFirstTask方法中的代码。
当前结论
最终结束掉APP的操作在ActivityManagerService.cleanUpRemovedTaskLocked方法中,ActivityManagerService类在文件"frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java"中,下面是ActivityManagerService.cleanUpRemovedTaskLocked方法的源码:
private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess) {
mRecentTasks.remove(tr);
tr.removedFromRecents();
ComponentName component = tr.getBaseIntent().getComponent();
if (component == null) {
Slog.w(TAG, "No component for base intent of task: " + tr);
return;
}
if (!killProcess) {
return;
}
// Determine if the process(es) for this task should be killed.
final String pkg = component.getPackageName();
ArrayList<ProcessRecord> procsToKill = new ArrayList<ProcessRecord>();
ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap();
for (int i = 0; i < pmap.size(); i++) {
SparseArray<ProcessRecord> uids = pmap.valueAt(i);
for (int j = 0; j < uids.size(); j++) {
// 在这里做了一些判断,判断哪些任务不被结束。
ProcessRecord proc = uids.valueAt(j);
if (proc.userId != tr.userId) {
// Don't kill process for a different user.
continue;
}
if (proc == mHomeProcess) {
// Don't kill the home process along with tasks from the same package.
continue;
}
if (!proc.pkgList.containsKey(pkg)) {
// Don't kill process that is not associated with this task.
continue;
}
for (int k = 0; k < proc.activities.size(); k++) {
TaskRecord otherTask = proc.activities.get(k).task;
if (tr.taskId != otherTask.taskId && otherTask.inRecents) {
// Don't kill process(es) that has an activity in a different task that is
// also in recents.
return;
}
}
// 除了不被删除的任务,剩下的任务都可以被结束。
// Add process to kill list.
procsToKill.add(proc);
}
}
// Find any running services associated with this app and stop if needed.
mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));
// 结束进程
// Kill the running processes.
for (int i = 0; i < procsToKill.size(); i++) {
ProcessRecord pr = procsToKill.get(i);
if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
pr.kill("remove task", true);
} else {
pr.waitingToKill = "remove task";
}
}
}
在上面的代码中获得了组件的包名,局部变量pkg,保存这个包名。经过实际测试,如果要保护某个APP不被这个方法kill掉,只要当"pkg.equals(<要保护APP的包名>)"返回true时直接使用"return;"语句返回,那么这个APP就不会被这个方法kill掉。测试的方法是直接修改了Android5.1.1的源码。