ゴールデンウィークで暇だったので、AndroidでBackキーを押したときにActivityが終了してアプリが切り替わる部分を調べてみる。
仕事でこのあたりいじって独自処理を埋め込むことになりそうなので。
Androidのバージョンは5/2時点のAOSP master。まぁLollipopの間はがっつり変わる部分では無いでしょう。
スタート地点はActivity.javaのonkeyDown。
onKeyDownの中でonBackPressed呼んでますね。
1 2 3 4 5 6 7 8 9 10 |
public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) { event.startTracking(); } else { onBackPressed(); } return true; } |
onBackPressedもActivityで実装されています。
mFragmentsでBackStackが無ければfinishAfterTransitionですね。
1 2 3 4 5 6 7 8 9 |
public void onBackPressed() { if (mActionBar != null && mActionBar.collapseActionView()) { return; } if (!mFragments.popBackStackImmediate()) { finishAfterTransition(); } } |
ActivityTransitionStateは終了時のアニメーションですかね。
中は見ていないですが、アニメーションが無ければ即finishと言うところでしょうか。
1 2 3 4 5 |
public void finishAfterTransition() { if (!mActivityTransitionState.startExitBackTransition(this)) { finish(); } } |
finishは次のような感じ。
mParentは親となるActivityですね。ActivityGroupでActivityの中にActivityを埋め込んだ時のかな。
ActivityManagerServiceのfinishActivityを呼んでいますね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public void finish() { finish(false); } private void finish(boolean finishTask) { if (mParent == null) { int resultCode; Intent resultData; synchronized (this) { resultCode = mResultCode; resultData = mResultData; } if (false) Log.v(TAG, "Finishing self: token=" + mToken); try { if (resultData != null) { resultData.prepareToLeaveProcess(); } if (ActivityManagerNative.getDefault() .finishActivity(mToken, resultCode, resultData, finishTask)) { mFinished = true; } } catch (RemoteException e) { // Empty } } else { mParent.finishFromChild(this); } } |
ここからはSystemServiceのプロセスです。
ActivityManagerServiceのコード量は多いので、一部分だけ抜粋。
ActivityManagerService.finishActivityです。
finishTaskがtrueかつ終了するActivityがルートであれば、removeTaskByIdLockedでタスクを終了しています。
そうでなければ、ActivityStackのrequestFinishActivityLockedを呼んでいます。
同一タスク内でのActivity終了によるActivity切替は深く追いかけませんが、この後いろいろあってActivityStack.finishCurrentActivityLockedあたりで終了処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
boolean res; if (finishTask && r == rootR) { // If requested, remove the task that is associated to this activity only if it // was the root activity in the task. The result code and data is ignored // because we don't support returning them across task boundaries. res = removeTaskByIdLocked(tr.taskId, false); if (!res) { Slog.i(TAG, "Removing task failed to finish activity"); } } else { res = tr.stack.requestFinishActivityLocked(token, resultCode, resultData, "app-request", true); if (!res) { Slog.i(TAG, "Failed to finish by app-request"); } } |
removeTaskByIdLockedの中身です。
removeTaskActivitiesLockedはperformClearTaskAtIndexLocked(0)が呼ばれ、すべてのActivityがfinishされます。finishの処理はActivityStack.finishActivityLockedですね。この中身は後で見ています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private boolean removeTaskByIdLocked(int taskId, boolean killProcess) { TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId); if (tr != null) { tr.removeTaskActivitiesLocked(); cleanUpRemovedTaskLocked(tr, killProcess); if (tr.isPersistable) { notifyTaskPersisterLocked(null, true); } return true; } Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId); return false; } |
cleanUpRemovedTaskLockedの中身です。
killProcessは今回falseなので、プロセスをkillする前にreturnします。mRecentTasksは最近使ったアプリ一覧です。TaskRecord.removedFromRecentsはサムネイルの削除などの後始末をしています。
1 2 3 4 5 6 7 8 9 10 11 12 |
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; } |
ActivityStack.finishActivityLockedの中身を見てみます。
長いので一部省略しています。
終了処理をしたり、キーのディスパッチを停止したりしていますね。
Activityがなくなった場合はendTaskがtrueになり、mWindowManager.prepareAppTransitionしてmWindowManager.setAppVisibilityでVisibilityをfalseにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, String reason, boolean oomAdj) { r.makeFinishing(); r.pauseKeyDispatchingLocked(); adjustFocusedActivityLocked(r, "finishActivity"); finishActivityResultsLocked(r, resultCode, resultData); if (mResumedActivity == r) { boolean endTask = index <= 0; if (DEBUG_VISBILITY || DEBUG_TRANSITION) Slog.v(TAG, "Prepare close transition: finishing " + r); mWindowManager.prepareAppTransition(endTask ? AppTransition.TRANSIT_TASK_CLOSE : AppTransition.TRANSIT_ACTIVITY_CLOSE, false); // Tell window manager to prepare for this one to be removed. mWindowManager.setAppVisibility(r.appToken, false); if (mPausingActivity == null) { if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r); if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false"); startPausingLocked(false, false, false, false); } |
WindowManagerService.setAppVisibilityを見てみます。
長いので省略・・・。
wtokenはAppWindowTokenです。
wtoken.hiddenRequestedがtrueになり、WindowManagerServiceのmClosingAppsにwtokenがaddされています。
mClosingAppsに追加されたAppWindowTokenが実際に処理される場所はどこでしょう。
handleAppTransitionReadyLockedかな?
とりあえずアプリ切替はWindowManagerServiceのsetAppVisibilityをfalseとする事で実現されているようです。
このあたりをゴニョゴニョしたらホニャホニャできそうです。
なお、この調査はコードを追いかけただけで、実際にログを仕込んで確認したわけでは無いのであしからず・・・。
No Comments