Android App啟動流程

拋出問題:
1. Android系統(tǒng)桌面是什么
2. 點(diǎn)擊應(yīng)用圖標(biāo)后Android系統(tǒng)執(zhí)行了什么操作

用文字總結(jié)App啟動流程可以分為以下步驟:

1. Launcher通過Binder建立Launcher所在進(jìn)程與system_server進(jìn)程(ActivityManagerService所在進(jìn)程)的通信,通知ActivityManagerService即將要啟動一個Activity
2. ActivityManagerService通過Binder讓Launcher進(jìn)入pause狀態(tài)
3. Launcher進(jìn)入pause狀態(tài)后,通過Binder告知ActivityManagerService挪蹭,隨后ActivityManagerService創(chuàng)建一個進(jìn)程(將要打開的應(yīng)用進(jìn)程)并啟動ActivityThread(應(yīng)用的UI線程)
4. ActivityThread通過Binder將ApplicationThread類型的Binder對象傳遞給ActivityManagerService障簿,方便ActivityManagerService后續(xù)與其的通信
5. 準(zhǔn)備工作完成后轮蜕,ActivityManagerService通知ActivityThread啟動Activity
6. ActivityThread調(diào)度執(zhí)行Activity的生命周期方法,完成啟動Activity的工作

Activity是視圖存在的根本,那么我們可以通過命令adb shell dumpsys activity activities判斷是哪個Activity為我們呈現(xiàn)桌面視圖的

點(diǎn)擊應(yīng)用圖標(biāo)后Android系統(tǒng)執(zhí)行了什么操作

呈現(xiàn)Android桌面視圖(View)->點(diǎn)擊View上某個應(yīng)用圖標(biāo)->產(chǎn)生點(diǎn)擊事件->點(diǎn)擊事件被響應(yīng)->通知Android系統(tǒng)的某個/某些進(jìn)程->Android系統(tǒng)執(zhí)行某些操作->啟動App

Launcher如何響應(yīng)由我們產(chǎn)生的點(diǎn)擊事件

產(chǎn)生點(diǎn)擊事件后,如果產(chǎn)生點(diǎn)擊事件的View的Tag是ShortcutInfo(即啟動應(yīng)用的快捷方式)喂很,就會取得ShortcutInfo中保存的Intent(這個Intent指向我們要啟動的App),然后執(zhí)行startActivitySafely(v, intent, tag)方法皆刺,而startActivitySafely方法只是對startActivity方法的簡單封裝少辣。

所以,Launcher響應(yīng)我們產(chǎn)生的點(diǎn)擊事件后羡蛾,實(shí)際上就是啟動一個新的Activity漓帅。請看代碼:

/**
* 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()) {
return;
}

Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
// 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);

if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
mWaitingForResume.setStayPressed(true);
}
} else if (tag instanceof FolderInfo) {
if (v instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) v;
handleFolderClick(fi);
}
} else if (v == mAllAppsButton) {
if (isAllAppsVisible()) {
showWorkspace(true);
} else {
onClickAllAppsButton(v);
}
}
}

boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
try {
success = startActivity(v, intent, tag);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
}
return success;
}

現(xiàn)在回想下App開發(fā)時,每個App都需要有一個“MainActivity”痴怨,這個Activity必須在AndroidManifest.xml文件中有以下配置:

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

在配置AndroidManifest.xml文件時煎殷,將Activity的Action指定為android.intent.action.MAIN,會使Activity在一個新的Task中啟動(Task是一個Activity棧)腿箩。將category指定為android.intent.category.LAUNCHER,表示通過Intent啟動此Activity時劣摇,只接受category為LAUNCHER的Intent珠移。

所以,Launcher將會通過App的快捷方式(ShortcutInfo)得到應(yīng)用的Intent,并通過這個Intent啟動應(yīng)用的“MainActivity”钧惧,從而啟動應(yīng)用暇韧。

所以我們研究的問題就從“App啟動流程”變?yōu)椤癆ctivity啟動流程”。

Launcher通過Binder通知ActivityManagerService啟動Activity

浓瞪,將Intent的Flag設(shè)為Intent.FLAG_ACTIVITY_NEW_TASK懈玻,使得Android系統(tǒng)將創(chuàng)建一個新的Task來放置即將被打開的新Activity(應(yīng)用的“MainActivity)。然后獲取一個布爾值以用于后續(xù)判斷是否顯示啟動App的動畫乾颁。

然后獲取Intent中是否傳輸了Parcelable格式的用戶句柄涂乌,并通過Context.LAUNCHER_APPS_SERVICE獲取用于在多用戶情境下啟動App的系統(tǒng)服務(wù)。

不管是否顯示啟動App的動畫英岭,最終都會執(zhí)行startActivity(intent)launcherApps.startMainActivity方法以啟動應(yīng)用的“MainActivity”湾盒。

代碼如下:boolean startActivity(View v, Intent intent, Object tag) {

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

try {
// Only launch using the new animation if the shortcut has not opted out (this is a
// private contract between launcher and may be ignored in the future).
boolean useLaunchAnimation = (v != null) &&
!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
UserHandle user = (UserHandle) intent.getParcelableExtra(ApplicationInfo.EXTRA_PROFILE);
LauncherApps launcherApps = (LauncherApps)
this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
if (useLaunchAnimation) {
ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
v.getMeasuredWidth(), v.getMeasuredHeight());
if (user == null || user.equals(android.os.Process.myUserHandle())) {
// Could be launching some bookkeeping activity
startActivity(intent, opts.toBundle());
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(),
opts.toBundle());
}
} else {
if (user == null || user.equals(android.os.Process.myUserHandle())) {
startActivity(intent);
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(), null);
}
}
return true;
} catch (SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Launcher does not have the permission to launch " + intent +
". Make sure to create a MAIN intent-filter for the corresponding activity " +
"or use the exported attribute for this activity. "
+ "tag="+ tag + " intent=" + intent, e);
}
return false;
}

launcherApps.startMainActivity只在用戶句柄不為空且用戶句柄不等于當(dāng)前進(jìn)程句柄(handle)時(其他用戶的句柄)調(diào)用,

為什么用戶句柄會影響Activity的啟動方式诅妹?

我們需要取得用戶A的句柄(和用戶A相關(guān)的數(shù)據(jù))罚勾,將我們想啟動的用戶B的App的Intent、用戶A的句柄交給內(nèi)核吭狡,讓擁有權(quán)限的

startActivity(intent)如何啟動Activity

進(jìn)入Activity類后層層深入就可以看到最終調(diào)用的是startActivityForResult方法:

從代碼上看尖殃,如果Launcher有mParent Activity,就會執(zhí)行mParent.startActivityFromChild划煮;如果沒有送丰,就會執(zhí)行mInstrumentation.execStartActivity。進(jìn)入mParent.startActivityFromChild方法會看到最終也是執(zhí)行了mInstrumentation.execStartActivity般此。執(zhí)行完成后蚪战,會取得一個ActivityResult對象,用于給調(diào)用者Activity傳遞一些數(shù)據(jù)铐懊,最后在Activity切換時顯示Transition動畫邀桑。

這里有一點(diǎn)需要指出的是:這里的ParentActivity指的是類似TabActivity、ActivityGroup關(guān)系的嵌套Activity科乎。之所以要強(qiáng)調(diào)parent和child壁畸,是要避免混亂的Activity嵌套關(guān)系。代碼如下:

public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received.  Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}

cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}

我們進(jìn)入Instrumentation(管家婆類)類看看execStartActivity方法吧:

我們通過參數(shù)IBinder contextThread取得一個IApplicationThread類型的對象whoThread茅茂,而contextThread是由mMainThread.getApplicationThread()取得的ApplicationThread對象捏萍,此時mMainThread指的就是Launcher應(yīng)用的主線程,所以whoThread指代的自然是Launcher的ApplicationThread空闲。

因?yàn)锳ctivity的onProvideReferrer()方法默認(rèn)返回null令杈,除非該方法被重寫,而我們使用的Launcher并沒有重寫該方法碴倾,所以不用管referrer逗噩。

然后判斷是否有ActivityMonitor掉丽,如果有,則即將要打開的Activity是否和ActivityMonitor中保存的IntentFilter匹配异雁,如果匹配則增加ActivityMonitor的計數(shù)捶障。大致是用于監(jiān)控符合匹配規(guī)則的Activity的數(shù)量的。

最后調(diào)用ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);啟動Activity纲刀,并檢查啟動是否成功项炼。換句話說,最終負(fù)責(zé)啟動Activity的是ActivityManager示绊,前面得到的ApplicationThread也是在這里使用的锭部。代碼:

public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) { final ActivityMonitor am = mActivityMonitors.get(i); if (am.match(who, null, intent)) { am.mHits++; if (am.isBlocking()) { return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市耻台,隨后出現(xiàn)的幾起案子空免,更是在濱河造成了極大的恐慌,老刑警劉巖盆耽,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋砚,死亡現(xiàn)場離奇詭異,居然都是意外死亡摄杂,警方通過查閱死者的電腦和手機(jī)坝咐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來析恢,“玉大人墨坚,你說我怎么就攤上這事∮彻遥” “怎么了泽篮?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柑船。 經(jīng)常有香客問我帽撑,道長,這世上最難降的妖魔是什么鞍时? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任亏拉,我火速辦了婚禮,結(jié)果婚禮上逆巍,老公的妹妹穿的比我還像新娘及塘。我一直安慰自己,他們只是感情好锐极,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布笙僚。 她就那樣靜靜地躺著,像睡著了一般灵再。 火紅的嫁衣襯著肌膚如雪味咳。 梳的紋絲不亂的頭發(fā)上庇勃,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機(jī)與錄音槽驶,去河邊找鬼。 笑死鸳兽,一個胖子當(dāng)著我的面吹牛掂铐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揍异,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼全陨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了衷掷?” 一聲冷哼從身側(cè)響起辱姨,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎戚嗅,沒想到半個月后雨涛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懦胞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年替久,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躏尉。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚯根,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胀糜,到底是詐尸還是另有隱情颅拦,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布教藻,位于F島的核電站距帅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏怖竭。R本人自食惡果不足惜锥债,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痊臭。 院中可真熱鬧哮肚,春花似錦、人聲如沸广匙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸦致。三九已至潮剪,卻和暖如春涣楷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抗碰。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工狮斗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弧蝇。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓碳褒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親看疗。 傳聞我的和親對象是個殘疾皇子沙峻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自 http://cheelok.com/aosp/54/ 啟動一個App的流程:Android系統(tǒng)桌面->點(diǎn)擊...
    香蕉樹878閱讀 3,102評論 0 15
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,133評論 25 707
  • 在剛開始接觸Android的時候就嘗試著去看ActivityThread,希望能明白App的啟動流程两芳。毋庸置疑摔寨,當(dāng)...
    javalong閱讀 1,581評論 3 24
  • 我們平時在手機(jī)桌面上點(diǎn)擊一個app 圖標(biāo), 就能啟動一個app應(yīng)用怖辆。從用戶角度來看是复,這個過程看起來很簡單,但是它的...
    皇馬船長閱讀 9,766評論 1 11
  • 我上周參加了同等學(xué)力申碩的英語考試疗隶,也就是在職研究生全國統(tǒng)考的外語考試佑笋,整個過程令我非常地驚訝和感慨。在這次...
    月光薇妮閱讀 2,400評論 0 3