【Android】Android Framework系列--Launcher3各启动场景源码分析

Android Framework系列--Launcher3各启动场景源码分析

Launcher3启动场景

Launcher3是Android系统提供的默认桌面应用(Launcher),它的源码路径在"packages/apps/Launcher3/"。

Launcher3的启动场景主要包括:

  • 开机后启动:开机时,android ams服务拉起Launcher
  • 按键启动:比如短压home键,android wms中的PhoneWindowManager拉起Launcher
  • 异常崩溃后启动:Launcher异常崩溃后,android ams再次拉起Launcher

针对这三种情况,分析一下Aosp源码如何实现。

源码基于Android10版本,时序图基于Android12(基本上与Android10的流程差不多,可以参考)。

下述说明中Launcher3和Launcher意思相同。

开机启动Launcher

Launcher的开机启动由Android的AMS服务完成。

AMS在SystemReady阶段会调用startHomeOnAllDisplays函数。Android支持多Display(虚拟Display或者由硬件上报的实际Display),多Display情况下一般Launcher会针对不同Display做不同的效果。

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
	// 启动Home,也就是Launcher
	mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
}

调用ActivityTaskManagerInternal类型的接口startHomeOnAllDisplays,这个接口在ActivityTaskManagerService.java文件中实现。

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
final class LocalService extends ActivityTaskManagerInternal {
	@Override
	public boolean startHomeOnAllDisplays(int userId, String reason) {
		synchronized (mGlobalLock) {
			// 这个对象是RootActivityContainer类型
			return mRootActivityContainer.startHomeOnAllDisplays(userId, reason);
		}
	}
}

接下来调用RootActivityContainer的接口startHomeOnAllDisplays,第二个参数reson的值为"systemReady"。

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/RootActivityContainer.java
boolean startHomeOnAllDisplays(int userId, String reason) {
	boolean homeStarted = false;
	for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
		final int displayId = mActivityDisplays.get(i).mDisplayId;
		homeStarted |= startHomeOnDisplay(userId, reason, displayId);
	}
	return homeStarted;
}

遍历mActivityDisplays对于所有处于Active状态的Display调用startHomeOnDisplay,在每个Display上都启动Home(Launcher)。

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/RootActivityContainer.java
boolean startHomeOnDisplay(int userId, String reason, int displayId) {
	// allowInstrumenting :false
	// fromHomeKey : false
	return startHomeOnDisplay(userId, reason, displayId, false /* allowInstrumenting */,
			false /* fromHomeKey */);
}

boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
		boolean fromHomeKey) {
	// 如果DisplayID是非法的,使用当前处于顶层焦点的 Display
	// Fallback to top focused display if the displayId is invalid.
	if (displayId == INVALID_DISPLAY) {
		displayId = getTopDisplayFocusedStack().mDisplayId;
	}

	// 构建一个Intent
	Intent homeIntent = null;
	ActivityInfo aInfo = null;
	if (displayId == DEFAULT_DISPLAY) {
		// 如果DisplayID是默认的Display(一般是主屏)
		// 调用ActivityTaskManagerService的getHomeIntent,拿到用来启动Home的Intent
		homeIntent = mService.getHomeIntent();
		// 查找当前系统里包含 android.intent.category.HOME的Activity。
		// 择优选择使用哪个Activity
		aInfo = resolveHomeActivity(userId, homeIntent);
	} else if (shouldPlaceSecondaryHomeOnDisplay(displayId)) {
		// 如果不是默认Display屏幕
		Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, displayId);
		aInfo = info.first;
		homeIntent = info.second;
	}
	if (aInfo == null || homeIntent == null) {
		return false;
	}
	
	// 判断是否运行启动Home
	if (!canStartHomeOnDisplay(aInfo, displayId, allowInstrumenting)) {
		return false;
	}

	// 更新Home Intent
	// Updates the home component of the intent.
	homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
	homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
	// 如果是从HomeKey启动的,添加额外参数
	// Updates the extra information of the intent.
	if (fromHomeKey) {
		homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
	}
	// Update the reason for ANR debugging to verify if the user activity is the one that
	// actually launched.
	final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
			aInfo.applicationInfo.uid) + ":" + displayId;
	// 启动Home
	mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
			displayId);
	return true;
}

startHomeOnDisplay函数中查询当前系统中包含 android.intent.category.HOME 信息的Activity(resolveHomeActivity ),如果找到了多个Activity则选择高优先级的。根据查找的Activity信息构建Intent,使用ActivityTaskManagerService的ActivityStartController启动Home对应的Activity。

getHomeIntent函数在ActivityTaskManagerService中实现。

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
Intent getHomeIntent() {
	// String mTopAction = Intent.ACTION_MAIN;
    Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
    intent.setComponent(mTopComponent);
    intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
    if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
    	// 非FactoryTest模式下走这里。
    	// android.intent.category.HOME
        intent.addCategory(Intent.CATEGORY_HOME);
    }
    return intent;
}

resolveHomeActivity函数用于查找包含android.intent.category.HOME信息的Activity。实现上是通过PMS完成的。

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/RootActivityContainer.java
/**
 * This resolves the home activity info.
 * @return the home activity info if any.
 */
@VisibleForTesting
ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) {
	final ComponentName comp = homeIntent.getComponent();
	try {
		if (comp != null) {
			// Factory test.
		} else {
			final String resolvedType =
					homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver());
			// 调用PMS查找信息
			final ResolveInfo info = AppGlobals.getPackageManager()
					.resolveIntent(homeIntent, resolvedType, flags, userId);
			if (info != null) {
				aInfo = info.activityInfo;
			}
		}
	} catch (RemoteException e) {
		// ignore
	}

	aInfo = new ActivityInfo(aInfo);
	aInfo.applicationInfo = mService.getAppInfoForUser(aInfo.applicationInfo, userId);
	return aInfo;
}

当找到所需信息后,调用ActivityStartController的startHomeActivity启动Home。该接口与AMS的startActivity和startActivityAsUser实现上基本原理一样,都是通过Intent启动Activity(fork一个进程出来)

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java
void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) {
	final ActivityOptions options = ActivityOptions.makeBasic();
	options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
	if (!ActivityRecord.isResolverActivity(aInfo.name)) {
		// The resolver activity shouldn't be put in home stack because when the foreground is
		// standard type activity, the resolver activity should be put on the top of current
		// foreground instead of bring home stack to front.
		options.setLaunchActivityType(ACTIVITY_TYPE_HOME);
	}
	options.setLaunchDisplayId(displayId);
	// 启动Activity
	mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
			.setOutActivity(tmpOutRecord)
			.setCallingUid(0)
			.setActivityInfo(aInfo)
			.setActivityOptions(options.toBundle())
			.execute();
	mLastHomeActivityStartRecord = tmpOutRecord[0];
	final ActivityDisplay display =
			mService.mRootActivityContainer.getActivityDisplay(displayId);
	final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
	if (homeStack != null && homeStack.mInResumeTopActivity) {
		// If we are in resume section already, home activity will be initialized, but not
		// resumed (to avoid recursive resume) and will stay that way until something pokes it
		// again. We need to schedule another resume.
		mSupervisor.scheduleResumeTopActivities();
	}
}

短压Home键启动Launcher

短压与长按区分,点一下Home键就属于短压。短压Home键后,Android会启动Home。

java 复制代码
// frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

// Native callback.
private long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
	// native层的 inputservice,通过这个接口上报回调
    return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java
/**
* Provides an opportunity for the window manager policy to process a key before
* ordinary dispatch.
*/
@Override
public long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
	   WindowState windowState = mService.windowForClientLocked(null, focus, false);
	   return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
}

mService是WindowManagerService类型,WindowManagerService中的mPolicy是PhoneWindowManager。PhoneWindowManager作为WMS的Policy配置文件,专门用来处理与UI行为有关的事件。PhoneWindowManager会拦截HomeKey事件进行相应处理后选择不再派发Home(PhoneWindowManager处理完就不需要其他人处理了),或者继续派发HomeKey给当前焦点View。

java 复制代码
// frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private long interceptKeyBeforeDispatchingInner(WindowState win, KeyEvent event,
		int policyFlags) {
	final boolean keyguardOn = keyguardOn();
	final int keyCode = event.getKeyCode();
	final int repeatCount = event.getRepeatCount();
	final int metaState = event.getMetaState();
	final int flags = event.getFlags();
	final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
	final boolean canceled = event.isCanceled();
	final int displayId = event.getDisplayId();

	// First we always handle the home key here, so applications
	// can never break it, although if keyguard is on, we do let
	// it handle it, because that gives us the correct 5 second
	// timeout.
	if (keyCode == KeyEvent.KEYCODE_HOME) {
		// 处理HomeKey
		DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId);
		if (handler == null) {
			handler = new DisplayHomeButtonHandler(displayId);
			mDisplayHomeButtonHandlers.put(displayId, handler);
		}
		return handler.handleHomeButton(win, event);
	} else if (keyCode == KeyEvent.KEYCODE_MENU) {
	// 省略
	}
}

/** A handler to handle home keys per display */
private class DisplayHomeButtonHandler {

	int handleHomeButton(WindowState win, KeyEvent event) {
		final boolean keyguardOn = keyguardOn();
		final int repeatCount = event.getRepeatCount();
		final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
		final boolean canceled = event.isCanceled();
		
		// If we have released the home key, and didn't do anything else
		// while it was pressed, then it is time to go home!
		if (!down) {
			// 省略
			// Post to main thread to avoid blocking input pipeline.
			// 处理短压Home
			mHandler.post(() -> handleShortPressOnHome(mDisplayId));
			return -1;
		}
		
		// 省略
		return -1;
	}
}

private void handleShortPressOnHome(int displayId) {
	// Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
	final HdmiControl hdmiControl = getHdmiControl();
	if (hdmiControl != null) {
		hdmiControl.turnOnTv();
	}

	// If there's a dream running then use home to escape the dream
	// but don't actually go home.
	if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
		mDreamManagerInternal.stopDream(false /*immediate*/);
		return;
	}

	// 启动Home
	// Go home!
	launchHomeFromHotKey(displayId);
}

PhoneWindowManager针对每个Display创建一个DisplayHomeButtonHandler ,通过它处理HomeKey。在启动Home期间如果开始了dream模式(类似于屏保),会先退出dream。最后调用launchHomeFromHotKey来启动Home,后续流程基本上与Home开机启动一致了。

Launcher异常崩溃后的自启动

Launcher意外退出(比如崩溃了)时,会触发AMS的forceStopPackage。AMS会再次将Home拉起。

java 复制代码
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public void forceStopPackage(final String packageName, int userId) {
	try {
		IPackageManager pm = AppGlobals.getPackageManager();
		synchronized(this) {
			int[] users = userId == UserHandle.USER_ALL
					? mUserController.getUsers() : new int[] { userId };
			for (int user : users) {
				if (mUserController.isUserRunning(user, 0)) {
					// 对每个运行的User,停掉 packageName对应的应用
					forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
					finishForceStopPackageLocked(packageName, pkgUid);
				}
			}
		}
	} finally {
		Binder.restoreCallingIdentity(callingId);
	}
}

forceStopPackageLocked函数中,会先Kill掉应用对应的进程。然后 resume focused app,在resume的过程中会拉起Home。

java 复制代码
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@GuardedBy("this")
final boolean forceStopPackageLocked(String packageName, int appId,
		boolean callerWillRestart, boolean purgeCache, boolean doit,
		boolean evenPersistent, boolean uninstalling, int userId, String reason) {
	
	// kill进程
	boolean didSomething = mProcessList.killPackageProcessesLocked(packageName, appId, userId,
			ProcessList.INVALID_ADJ, callerWillRestart, true /* allowRestart */, doit,
			evenPersistent, true /* setRemoved */,
			packageName == null ? ("stop user " + userId) : ("stop " + packageName));

	if (doit) {
		if (purgeCache && packageName != null) {
			AttributeCache ac = AttributeCache.instance();
			if (ac != null) {
				ac.removePackage(packageName);
			}
		}
		if (mBooted) {
			// resume focused app
			// 通过这个函数重新拉起Home
			mAtmInternal.resumeTopActivities(true /* scheduleIdle */);
		}
	}

	return didSomething;
}

调用ActivityTaskManagerServiced的resumeTopActivities函数。在 Home崩溃的情况下,调用这个函数,可以保证Home重新被拉起(这个函数最终会调用到RootActivityContainer 的resumeHomeActivity函数。感兴趣的可以继续顺着代码往下看)

java 复制代码
//frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public void resumeTopActivities(boolean scheduleIdle) {
    synchronized (mGlobalLock) {
        mRootActivityContainer.resumeFocusedStacksTopActivities();
        if (scheduleIdle) {
            mStackSupervisor.scheduleIdleLocked();
        }
    }
}

// frameworks/base/services/core/java/com/android/server/wm/RootActivityContainer.java
boolean resumeFocusedStacksTopActivities(
		ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {

	for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
		// 省略
		if (!resumedOnDisplay) {
			// In cases when there are no valid activities (e.g. device just booted or launcher
			// crashed) it's possible that nothing was resumed on a display. Requesting resume
			// of top activity in focused stack explicitly will make sure that at least home
			// activity is started and resumed, and no recursion occurs.
			final ActivityStack focusedStack = display.getFocusedStack();
			if (focusedStack != null) {
				focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
			}
		}
	}

	return result;
}
相关推荐
火柴就是我1 分钟前
Dart 原始字符串(Raw Strings)详解文档
android
玲小珑12 分钟前
Auto.js 入门指南(五)实战项目——自动脚本
android·前端
玲小珑12 分钟前
Auto.js 入门指南(四)Auto.js 基础概念
android·前端
没有了遇见1 小时前
DrawerLayout 滑动冲突
android
玲小珑2 小时前
Auto.js 入门指南(六)多线程与异步操作
android·前端
用户2018792831674 小时前
通俗易懂理解Java注解
android
用户2018792831674 小时前
通俗易懂理解泛型
android
linversion4 小时前
如何手动上传Android混淆映射文件Mapping.txt到Firebase
android
慕晨4 小时前
RecyclerView + SnapHelper 滚动差异问题
android
玲小珑4 小时前
Auto.js 入门指南(三)第一个 Auto.js 脚本
android·前端