Android System Server (Activity Finish)

Task Manager에서 Activity Finish가 일어나면

Activity Manager는 즉각적으로 Task리스트에서 해당 Activity는 사라지게 된다.

비록 onDestory가 일어나지 않는다고 하더라도

이런 변화로 인해서 순간적인 Top Activity의 변화를 정확히 잡아내기 어려운 현상이 생겼다.

해결 방법은 아직 못찾았다.

Activity Manager는 System Server내에 위치 하는 서비스이다.

관련해서 자세히 나온 슬라이드는 다음 링크를 확인 하면 된다.

www.slideshare.net/opersys/understanding-the-android-system-server

추가적으로 내용을 남기자면

안드로이드 소스내에

  • Platform_frameworks_Base > Services > core > java > android > com > android > Server

이하를 보면 관련 소스를 모두 볼수 있다.

상기 finish 시점의 Task Delete는 다음 코드에 포함되어있다.

com.android.server.wm.ActivityTaskManagerService;

    /**
     * This is the internal entry point for handling Activity.finish().
     *
     * @param token The Binder token referencing the Activity we want to finish.
     * @param resultCode Result code, if any, from this Activity.
     * @param resultData Result data (Intent), if any, from this Activity.
     * @param finishTask Whether to finish the task associated with this Activity.
     *
     * @return Returns true if the activity successfully finished, or false if it is still running.
     */
    @Override
    public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,
            int finishTask) {
        // Refuse possible leaked file descriptors
        if (resultData != null && resultData.hasFileDescriptors()) {
            throw new IllegalArgumentException("File descriptors passed in Intent");
        }

        final ActivityRecord r;
        synchronized (mGlobalLock) {
            r = ActivityRecord.isInStackLocked(token);
            if (r == null) {
                return true;
            }
        }

        // Carefully collect grants without holding lock
        final NeededUriGrants resultGrants = collectGrants(resultData, r.resultTo);

        synchronized (mGlobalLock) {
            // Sanity check in case activity was removed before entering global lock.
            if (!r.isInHistory()) {
                return true;
            }

            // Keep track of the root activity of the task before we finish it
            final Task tr = r.getTask();
            final ActivityRecord rootR = tr.getRootActivity();
            if (rootR == null) {
                Slog.w(TAG, "Finishing task with all activities already finished");
            }
            // Do not allow task to finish if last task in lockTask mode. Launchable priv-apps can
            // finish.
            if (getLockTaskController().activityBlockedFromFinish(r)) {
                return false;
            }

            // TODO: There is a dup. of this block of code in ActivityStack.navigateUpToLocked
            // We should consolidate.
            if (mController != null) {
                // Find the first activity that is not finishing.
                final ActivityRecord next =
                        r.getRootTask().topRunningActivity(token, INVALID_TASK_ID);
                if (next != null) {
                    // ask watcher if this is allowed
                    boolean resumeOK = true;
                    try {
                        resumeOK = mController.activityResuming(next.packageName);
                    } catch (RemoteException e) {
                        mController = null;
                        Watchdog.getInstance().setActivityController(null);
                    }

                    if (!resumeOK) {
                        Slog.i(TAG, "Not finishing activity because controller resumed");
                        return false;
                    }
                }
            }

            // note down that the process has finished an activity and is in background activity
            // starts grace period
            if (r.app != null) {
                r.app.setLastActivityFinishTimeIfNeeded(SystemClock.uptimeMillis());
            }

            final long origId = Binder.clearCallingIdentity();
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity");
            try {
                boolean res;
                final boolean finishWithRootActivity =
                        finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
                if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
                        || (finishWithRootActivity && 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. Also, to
                    // keep backwards compatibility we remove the task from recents when finishing
                    // task with root activity.
                    mStackSupervisor.removeTask(tr, false /*killProcess*/,
                            finishWithRootActivity, "finish-activity");
                    res = true;
                    // Explicitly dismissing the activity so reset its relaunch flag.
                    r.mRelaunchReason = RELAUNCH_REASON_NONE;
                } else {
                    r.finishIfPossible(resultCode, resultData, resultGrants,
                            "app-request", true /* oomAdj */);
                    res = r.finishing;
                    if (!res) {
                        Slog.i(TAG, "Failed to finish by app-request");
                    }
                }
                return res;
            } finally {
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                Binder.restoreCallingIdentity(origId);
            }
        }
    }

해당 Class 에서 Finish 대상이 되는 Activity의 Token을 제외하고 다음에 올 Activity를 resume 시키게 된다.

특히 Task에서 삭제하는 코드는 다음에 있다.

                final boolean finishWithRootActivity =
                        finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
                if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
                        || (finishWithRootActivity && 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. Also, to
                    // keep backwards compatibility we remove the task from recents when finishing
                    // task with root activity.
                    mStackSupervisor.removeTask(tr, false /*killProcess*/,
                            finishWithRootActivity, "finish-activity");
                    res = true;
                    // Explicitly dismissing the activity so reset its relaunch flag.
                    r.mRelaunchReason = RELAUNCH_REASON_NONE;

 

com.android.server.wm.Task

    /**
     * This is a simplified version of topRunningActivity that provides a number of
     * optional skip-over modes.  It is intended for use with the ActivityController hook only.
     *
     * @param token If non-null, any history records matching this token will be skipped.
     * @param taskId If non-zero, we'll attempt to skip over records with the same task ID.
     *
     * @return Returns the HistoryRecord of the next activity on the stack.
     */
    ActivityRecord topRunningActivity(IBinder token, int taskId) {
        final PooledPredicate p = PooledLambda.obtainPredicate(Task::isTopRunning,
                PooledLambda.__(ActivityRecord.class), taskId, token);
        final ActivityRecord r = getActivity(p);
        p.recycle();
        return r;
    }

 

참고로 롤리팝 5.1의 경우 Task 종료는 다음과 같이 구현되어있다. 2018년 이후부터는 상기에 있는 코드로 변화 되었다.

core>java>com>android>server>am>ActivityManagerService

    /**
     * This is the internal entry point for handling Activity.finish().
     *
     * @param token The Binder token referencing the Activity we want to finish.
     * @param resultCode Result code, if any, from this Activity.
     * @param resultData Result data (Intent), if any, from this Activity.
     * @param finishTask Whether to finish the task associated with this Activity.  Only applies to
     *            the root Activity in the task.
     *
     * @return Returns true if the activity successfully finished, or false if it is still running.
     */
    @Override
    public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,
            boolean finishTask) {
        // Refuse possible leaked file descriptors
        if (resultData != null && resultData.hasFileDescriptors() == true) {
            throw new IllegalArgumentException("File descriptors passed in Intent");
        }

        synchronized(this) {
            ActivityRecord r = ActivityRecord.isInStackLocked(token);
            if (r == null) {
                return true;
            }
            // Keep track of the root activity of the task before we finish it
            TaskRecord tr = r.task;
            ActivityRecord rootR = tr.getRootActivity();
            // Do not allow task to finish in Lock Task mode.
            if (tr == mStackSupervisor.mLockTaskModeTask) {
                if (rootR == r) {
                    mStackSupervisor.showLockTaskToast();
                    return false;
                }
            }
            if (mController != null) {
                // Find the first activity that is not finishing.
                ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
                if (next != null) {
                    // ask watcher if this is allowed
                    boolean resumeOK = true;
                    try {
                        resumeOK = mController.activityResuming(next.packageName);
                    } catch (RemoteException e) {
                        mController = null;
                        Watchdog.getInstance().setActivityController(null);
                    }

                    if (!resumeOK) {
                        return false;
                    }
                }
            }
            final long origId = Binder.clearCallingIdentity();
            try {
                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, 0);
                } else {
                    res = tr.stack.requestFinishActivityLocked(token, resultCode,
                            resultData, "app-request", true);
                }
                return res;
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }
    
    
    /**
     * Removes the task with the specified task id.
     *
     * @param taskId Identifier of the task to be removed.
     * @param flags Additional operational flags.  May be 0 or
     * {@link ActivityManager#REMOVE_TASK_KILL_PROCESS}.
     * @return Returns true if the given task was found and removed.
     */
    private boolean removeTaskByIdLocked(int taskId, int flags) {
        TaskRecord tr = recentTaskForIdLocked(taskId);
        if (tr != null) {
            tr.removeTaskActivitiesLocked();
            cleanUpRemovedTaskLocked(tr, flags);
            if (tr.isPersistable) {
                notifyTaskPersisterLocked(null, true);
            }
            return true;
        }
        return false;
    }
    
    
    private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) {
        mRecentTasks.remove(tr);
        tr.removedFromRecents(mTaskPersister);
        final boolean killProcesses = (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0;
        Intent baseIntent = new Intent(
                tr.intent != null ? tr.intent : tr.affinityIntent);
        ComponentName component = baseIntent.getComponent();
        if (component == null) {
            Slog.w(TAG, "Now component for base intent of task: " + tr);
            return;
        }

        // Find any running services associated with this app.
        mServices.cleanUpRemovedTaskLocked(tr, component, baseIntent);

        if (killProcesses) {
            // Find any running processes associated with this app.
            final String pkg = component.getPackageName();
            ArrayList<ProcessRecord> procs = 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) {
                        continue;
                    }
                    if (!proc.pkgList.containsKey(pkg)) {
                        continue;
                    }
                    procs.add(proc);
                }
            }

            // Kill the running processes.
            for (int i=0; i<procs.size(); i++) {
                ProcessRecord pr = procs.get(i);
                if (pr == mHomeProcess) {
                    // Don't kill the home process along with tasks from the same package.
                    continue;
                }
                if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
                    pr.kill("remove task", true);
                } else {
                    pr.waitingToKill = "remove task";
                }
            }
        }
    }

cleanupRemovedTaskLocked 코드를 보면 

       // Find any running services associated with this app.
        mServices.cleanUpRemovedTaskLocked(tr, component, baseIntent);

해당 Activity와 연관이 있는 서비스가 있는지 확인 후 Activity를 종료하는 순서로 보인다.

이 차이로 인해서 Activity가 task 리스트에서 즉각적으로 사라지는지의 차이가 나는 것으로 보인다.

728x90
반응형