Activity 啟動流程學習總結(附原始碼流程圖)

0xCAFEBOY發表於2019-06-27

前言

關於 Activity 啟動,Android 中場景大致有兩個:

  1. 從 launcher 中啟動應用,觸發該應用預設 Activity 的啟動。這種 Activity 都是在新程式和新的任務棧中啟動的,所以涉及到新程式和新任務棧的初始化
  2. 應用程式內部啟動非預設 Activity, 被啟動的 Activity 一般在原來的程式和任務棧中啟動

本文主要介紹第一種場景

背景知識

程式與執行緒

由於 Activity 的啟動流程中涉及了大量的程式間通訊,例如:ActivityManagerService 和 ActivityStack 位於同一程式,ApplicationThread 和 ActivityThread 位於同一程式。所以有必要梳理一下程式與執行緒的區別

從作業系統的角度看,程式和執行緒有什麼區別?

  1. 程式有獨立的地址空間,一個程式崩潰後,在保護模式下不會對其他程式造成影響。
  2. 執行緒沒有獨立的地址空間,執行緒只是程式所屬程式的不同執行路徑

從 JVM 的執行時資料分割槽的角度,程式和執行緒有什麼關係?

JVM 執行時資料分割槽如圖所示:

Activity 啟動流程學習總結(附原始碼流程圖)
由圖可以看出如下幾點:

  1. 一個程式中的多個執行緒共享堆區和方法區
  2. 每個執行緒擁有自己的程式計數器,虛擬機器棧,本地方法棧

Activity 啟動模式

啟動模式橫向對比

四中啟動模式的概念就不詳述了,這裡只是對關鍵點做出橫向對比,如圖所示

Activity 啟動模式比較

一些鮮為人知的 Intent Flag

下面是一些 Intent Flag 及介紹,如圖所示

鮮為人知但常用的啟動模式 flag

Activity 啟動流程原始碼分析

概念

首先介紹一下 Activity 啟動流程涉及到的核心類

ActivityThread

ActivityThread 的作用是管理應用程式主執行緒的相關流程,例如管理與處理 activity manager 傳送的請求,這些請求可以來自 Activity 或 BroadCast

Instrumentation

它用來監控應用程式與系統之間的互動

ActivityManagerService (AMS)

AMS(ActivityManagerService)是貫穿Android系統元件的核心服務,負責了系統中四大元件的啟動、切換、排程以及應用程式管理和排程工作

執行流程

Step 1. Launcher.startActivitySafely

/**
 * Launches the intent referred by the clicked shortcut.
 *
 * @param v The view representing the clicked shortcut.
 */
public void onClick(View v) {
    // Make sure that rogue clicks don't get through while allapps is launching, or after the
    // view has detached (it's possible for this to happen if the view is removed mid touch).
    if (v.getWindowToken() == null) {
        return;
    }
    if (!mWorkspace.isFinishedSwitchingState()) { // Launcher 程式在監聽點選事件時會判斷頁面是否正在滑動,如果在滑動則不響應點選應用程式 icon 的事件
        return;
    }
    Object tag = v.getTag();
    if (tag instanceof ShortcutInfo) { // 處理點選應用程式 icon 的場景
        // Open shortcut
        final Intent intent = ((ShortcutInfo) tag).intent;
        int[] pos = new int[2];
        v.getLocationOnScreen(pos);
        intent.setSourceBounds(new Rect(pos[0], pos[1],
                pos[0] + v.getWidth(), pos[1] + v.getHeight()));

        boolean success = startActivitySafely(v, intent, tag); // startActivitySafely 這裡主要會判斷待啟動的 Activity 是否存在,若不存在則會報 ActivityNotFound Exception 並捕獲

        if (success && v instanceof BubbleTextView) {
            mWaitingForResume = (BubbleTextView) v;
            mWaitingForResume.setStayPressed(true);
        }
    } else if (tag instanceof FolderInfo) {  // 處理點選資料夾的場景
        ···
    } else if (v == mAllAppsButton) {
        ···
    }
}
複製程式碼

Launcher 程式在監聽點選事件時會判斷頁面是否正在滑動,如果在滑動則不響應點選應用程式 icon 的事件

Step 2. Activity.startActivity()

由於 Launcher 繼承於 Activity, 因此程式碼執行流程到了 startActivity()


public class Activity extends ContextThemeWrapper
		implements LayoutInflater.Factory,
		Window.Callback, KeyEvent.Callback,
		OnCreateContextMenuListener, ComponentCallbacks {
 
	......
 
	@Override
	public void startActivity(Intent intent) {
		startActivityForResult(intent, -1);
	}
 
	......
複製程式碼

startActivity 最終是呼叫 startActivityForResult, 傳入的引數為 -1 代表這次啟動 Activity 不需要獲取結果

Step 3. Activity.startActivityForResult()


public class Activity extends ContextThemeWrapper
		implements LayoutInflater.Factory,
		Window.Callback, KeyEvent.Callback,
		OnCreateContextMenuListener, ComponentCallbacks {
 
	......
 
	public void startActivityForResult(Intent intent, int requestCode) {
		if (mParent == null) {
			Instrumentation.ActivityResult ar =
				mInstrumentation.execStartActivity(
				this, mMainThread.getApplicationThread(), mToken, this,
				intent, requestCode);
			......
		} else {
			......
		}
 
 
	......
複製程式碼

這裡有兩個有趣的場景:

  1. startActivityForResult() 不能用於啟動 singleTask 為啟動模式的 Activity, 否則會從 onActivityForResult() 中收到 cancel 事件
  2. 假設有這樣一個場景,Activity A 啟動 Activity B, 如果在 A 的 onCreate() / onResume() 中呼叫 startActivityForResult(), 並且傳入的 requestCode >= 1, 那麼 A Activity 所掛鉤的 Window 將暫時不可見,這種不可見狀態直到收到 B Activity 的回撥結果。目的是防止 Activity 跳轉時 UI 閃爍

Step 4. ActivityStack.startActivityUncheckedLocked

final int startActivityUncheckedLocked(ActivityRecord r,
		ActivityRecord sourceRecord, Uri[] grantedUriPermissions,
		int grantedMode, boolean onlyIfNeeded, boolean doResume) {
		final Intent intent = r.intent;
		final int callingUid = r.launchedFromUid;
 
		int launchFlags = intent.getFlags();
 
		// We'll invoke onUserLeaving before onPause only if the launching
		// activity did not explicitly state that this is an automated launch.
		mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
		
		......
 
		ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
			!= 0 ? r : null;
 
		// If the onlyIfNeeded flag is set, then we can do this if the activity
		// being launched is the same as the one making the call...  or, as
		// a special case, if we do not know the caller then we count the
		// current top activity as the caller.
		if (onlyIfNeeded) {
			......
		}
 
		if (sourceRecord == null) {
			......
		} else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
			......
		} else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
			|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
			......
		}
 
		if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
			......
		}
 
		boolean addingToTask = false;
		if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
			(launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
			|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
			|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
				// If bring to front is requested, and no result is requested, and
				// we can find a task that was started with this same
				// component, then instead of launching bring that one to the front.
				if (r.resultTo == null) {
					// See if there is a task to bring to the front.  If this is
					// a SINGLE_INSTANCE activity, there can be one and only one
					// instance of it in the history, and it is always in its own
					// unique task, so we do a special search.
					ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
						? findTaskLocked(intent, r.info)
						: findActivityLocked(intent, r.info);
					if (taskTop != null) {
						......
					}
				}
		}
 
		......
 
		if (r.packageName != null) {
			// If the activity being launched is the same as the one currently
			// at the top, then we need to check if it should only be launched
			// once.
			ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
			if (top != null && r.resultTo == null) {
				if (top.realActivity.equals(r.realActivity)) {
					......
				}
			}
 
		} else {
			......
		}
 
		boolean newTask = false;
 
		// Should this be considered a new task?
		if (r.resultTo == null && !addingToTask
			&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
				// todo: should do better management of integers.
				mService.mCurTask++;
				if (mService.mCurTask <= 0) {
					mService.mCurTask = 1;
				}
				r.task = new TaskRecord(mService.mCurTask, r.info, intent,
					(r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
				......
				newTask = true;
				if (mMainStack) {
					mService.addRecentTaskLocked(r.task);
				}
 
		} else if (sourceRecord != null) {
			......
		} else {
			......
		}
 
		......
 
		startActivityLocked(r, newTask, doResume);
		return START_SUCCESS;
	}
複製程式碼

由於我們是總 launcher 啟動 Activity, 因此 當前的前臺任務棧棧頂是 launcher Activity, 因此需要建立一個新的 ActivityStack, 和 ActivityRecord 物件,將 ActivityRecord 放入其中,最終這個新建立的 ActivityStack 被儲存到 AMS 中

Step 5. Activity.resumeTopActivityLocked

   // If the top activity is the resumed one, nothing to do.
    if (mResumedActivity == next && next.state == ActivityState.RESUMED) {
	......
    }
 
    // If we are sleeping, and there is no resumed activity, and the top
    // activity is paused, well that is the state we want.
    if ((mService.mSleeping || mService.mShuttingDown)
	&& mLastPausedActivity == next && next.state == ActivityState.PAUSED) {
	......
    }
複製程式碼

現在我們已經準備好了新的 ActivityStack 和新的 ActivityRecord, 然後我們就需要處理 launcher Activity 的生命週期,將其置為 pause 狀態。根據原始碼,這裡首先會檢查被啟動的 Activity 是否在棧頂,是否就是啟動者,如果是的話,就什麼都不做,如果都不滿足的話,就需要將目前棧頂的 Activity 置為 Pause 狀態,在這之前,首先要檢查是否有正在 Pausing 的 Activity, 如果有的話launcher Activity 需要被掛起。

總結

由於 Activity 啟動流程較複雜,剩餘流程將不再詳述,總結如下圖所示:

Activity 啟動流程圖

參考文獻


您的點贊是我寫作的最大動力!

相關文章