问题引入
在新平台开发过程中,测试同学一直抱怨在进入Launcher前,屏幕会黑屏闪现一下,大概2-3s,随后才进入,影响用户体验。刚开始没有特别关注,以为是启动Activity 过程中加载东西太多,导致窗体渲染有点慢。经过后续分析,原来另有原因,而且其中涉及好几个知识点。这里mark 一下。(在另一个平台上,出现了一个“平板正在启动中”的显示,产品同学很想拿掉或者替换,那么我们可以实现了?看完这篇就有答案了)
发现问题
抓取开机打印,查看一下在进入Launcher前后,系统做了什么:
12-17 15:22:57.358 507 507 I ActivityManager: System now ready
12-17 15:22:58.702 507 561 I ActivityManager: Start proc 938:com.android.tv.settings/1000 for top-activity {com.android.tv.settings/com.android.tv.settings.system.FallbackHome}
12-17 15:23:01.076 507 554 E ActivityManager: ams finishBooting.
12-17 15:23:01.078 507 554 I ActivityManager: User 0 state changed from BOOTING to RUNNING_LOCKED
12-17 15:23:01.118 507 554 D ActivityManager: Started unlocking user 0
12-17 15:23:01.118 507 554 D ActivityManager: Unlocking user 0 progress 0
12-17 15:23:01.118 507 554 D ActivityManager: Unlocking user 0 progress 5
12-17 15:23:01.869 507 551 I ActivityManager: User 0 state changed from RUNNING_LOCKED to RUNNING_UNLOCKING
12-17 15:23:01.870 507 551 D ActivityManager: Unlocking user 0 progress 20
12-17 15:23:06.422 507 560 I ActivityManager: User 0 state changed from RUNNING_UNLOCKING to RUNNING_UNLOCKED
12-17 15:23:06.456 507 560 I ActivityManager: Posting BOOT_COMPLETED user #0
12-17 15:23:06.602 507 561 I ActivityManager: Start proc 1274:com.funshion.ottedu/u0a26 for top-activity {com.funshion.ottedu/com.bestv.ott.home.HomeActivity}
发现在启动我们Launcher应用(com.funshion.ottedu)前,还启动了一个{com.android.tv.settings/com.android.tv.settings.system.FallbackHome}组件,而且是在解锁屏幕之前就启动了。
FallbackHome
经过查找发现原生设置中设有directBootAware 属性,且FallbackHome 带有android.intent.category.HOME特性,但是优先级很低(-1000)
packages/apps/Settings/AndroidManifest.xml
android:directBootAware="true"
<activity android:name=".FallbackHome"
android:excludeFromRecents="true"
android:label=""
android:screenOrientation="nosensor"
android:taskAffinity="com.android.settings.FallbackHome"
android:theme="@style/FallbackHome">
<intent-filter android:priority="-1000">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
public class FallbackHome extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
maybeFinish();
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
maybeFinish();
}
};
private void maybeFinish() {
if (getSystemService(UserManager.class).isUserUnlocked()) {
final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME);
final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
if (UserManager.isSplitSystemUser()
&& UserHandle.myUserId() == UserHandle.USER_SYSTEM) {
// This avoids the situation where the system user has no home activity after
// SUW and this activity continues to throw out warnings. See b/28870689.
return;
}
Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
mHandler.sendEmptyMessageDelayed(0, 500);
} else {
Log.d(TAG, "User unlocked and real home found; let's go!");
getSystemService(PowerManager.class).userActivity(
SystemClock.uptimeMillis(), false);
finish();
}
}
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
maybeFinish();
}
};
}
FallbackHome 的代码很简单,简单界面显示“平台正在启动中”,随后一直等待用户解锁的广播,接收到之后判断是否有新的Launcher可用,并结束自己使命。准确来说它只是一个过渡界面。
新平台出现黑屏的原因是里面延时2s 才显示Fallbakhome的界面,当我们正要显示FallbackHome界面时,此时接收到系统解锁(系统没有设置ping 密码,不需解锁直接进系统)便出现了黑屏一下的现象
那么有两种方案解决我们的问题,后面会详细补充。
directBootAware
Direct Boot Mode—是AndroidN 引入的新特性, 设备启动后进入,直到用户解锁(unlock)设备此阶段结束的一种模式。应用程序可以在用户锁屏阶段启动,并处理应用程序的部分逻辑。Direct Boot Mode和正常模式下最大的不同是使用一种新的存储空间:Device protected storage。
Android亮屏流程
这是一个比较复杂且繁琐的过程,这里给出几个关键文件和函数,感兴趣的朋友可以自己查看源码(后续可能会单独写一篇博文分享一下):
frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
//AMS systemReady
public systemReady() {
Slog.i(TAG, "System now ready");
//在这里会拉起FallbackHome
startHomeActivityLocked(currentUserId, "systemReady");
}
frameworks/base/core/java/android/app/ActivityThread.java
//resume阶段handleResumeActivity方法里面在加载完window之后将自己实现的Idlehandler添加到自己的消息队列里面。
handleLaunchActivity{
Looper.myQueue().addIdleHandler(new Idler());
}
//这里调用到AMS的activityIdle,开启亮屏流程
MessageQueue.IdleHandler {
am.activityIdle(a.token, a.createdConfig, stopProfiling);
}
//activityIdle 最后调用到的是ActivityStackSuperVisor里面的activityIdleInternalLocked。
//会报告启动时间结束,还有就是检查是否是还在开机阶段然后结束开机流程。意思就是桌面都已经拿到焦点显示好了,开机动画可以退役了。
ActivityRecord activityIdleInternalLocked() {
if (isFocusedStack(r.getStack()) || fromTimeout) {
booting = checkFinishBootingLocked();
}
}
private boolean checkFinishBootingLocked() {
mService.postFinishBooting(booting, enableScreen);//这里调用到AMS里面去了
}
//回到AMS
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
void postFinishBooting(boolean finishBooting, boolean enableScreen) {
mHandler.sendMessage(mHandler.obtainMessage(FINISH_BOOTING_MSG,
finishBooting ? 1 : 0, enableScreen ? 1 : 0));
}
case FINISH_BOOTING_MSG: {
if (msg.arg1 != 0) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "FinishBooting");
finishBooting();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
if (msg.arg2 != 0) {
enableScreenAfterBoot();
}
break;
}
//发送亮屏请求
void enableScreenAfterBoot() {
mWindowManager.enableScreenAfterBoot();
}
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
//亮屏处理
public void enableScreenIfNeeded() {
synchronized (mWindowMap) {
enableScreenIfNeededLocked();
mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000);//超时时间30s
}
}
void enableScreenIfNeededLocked() {
mH.sendEmptyMessage(H.ENABLE_SCREEN);
}
private void performEnableScreen() {
if (!mBootAnimationStopped) {
//这里设置属性可以关闭开机动画
SystemProperties.set("service.bootanim.exit", "1");
mBootAnimationStopped = true;
}
mActivityManager.bootAnimationComplete();//这里有回到AMS
mPolicy.enableScreenAfterBoot();
}
//再次回到AMS
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
//亮屏之后通知AMS后续的操作
public void bootAnimationComplete() {
finishBooting();
}
//系统的第一个应用启动起来,窗口准备完毕,介绍开机动画,并发送开机广播都在这里
final void finishBooting() {
mUserController.sendBootCompleted()//在这里会发生解锁广播之类
}
frameworks/base/services/core/java/com/android/server/am/UserController.java
void sendBootCompleted(IIntentReceiver resultTo) {
finishUserBoot(uss, resultTo);
}
private void finishUserBoot(UserState uss, IIntentReceiver resultTo) {
}
void finishUserUnlocked(final UserState uss) {
final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
mInjector.sendPreBootBroadcast(userId, quiet,
() -> finishUserUnlockedCompleted(uss));
}
比较凌乱,没有画图,后续再慢慢详细分析,不关注的朋友可以忽略。
解决方案
1.部分平台没有显示黑屏,而是显示“平板正在启动中”之类的,咱们可以直接修改FallbackHome的界面,修改成你想要的效果。当然你可以拿掉FallbackHome这个界面。但是前提是你需要自己实现一个带
directBootAware属性应用,且具有android.intent.category.HOME 特性的Activity。否则系统将起不来。因为系统无法亮屏
2.延时开机动画,等自己的Launcher起来之后,再杀掉开机动画。
这里直接给出patch.
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 9c8b7ef..e1c4e14 100755
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1161,19 +1161,25 @@ bool BootAnimation::android()
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
-
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
+ char value1[PROPERTY_VALUE_MAX];
+ char value2[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0");
+ property_get("service.launcher.visible", value1, "0");
+ property_get("service.launcher.create", value2, "0");
ALOGI("service.bootanim.exit : %s", value);
+ ALOGI("service.launcher.visible : %s", value1);
+ ALOGI("service.launcher.create : %s", value2);
int exitnow = atoi(value);
- if (exitnow) {
+ if (exitnow && (atoi(value1)||atoi(value2))) {
+ ALOGI("checkExit really exit");
+ property_set("service.boot.complete", "1");
requestExit();
mCallbacks->shutdown();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4b98d02..b83f733 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3485,12 +3485,12 @@ public class WindowManagerService extends IWindowManager.Stub
}
private boolean checkBootAnimationCompleteLocked() {
- if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
+ if (!mDisplayEnabled) {
mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);
mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,
BOOT_ANIMATION_POLL_INTERVAL);
if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Waiting for anim complete");
- return false;
+ return mBootAnimationStopped;
}
if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Animation complete!");
return true;
--
为什么需要修改WindowManagerService 里面的checkBootAnimationCompleteLocked。这里会一直等开机动画结束,才会想AMS 发送bootAnimationComplete。没有接收到解锁广播,FallbakHome 会一直运行。(WMS 也有个超时机制,30s)。
同样都带有HOME,为什么首先找到的是FallbackHome
想必你可能有这个疑问,这里也是有猫腻的,这是因为:
解锁之前queryIntentActivitiesInternal(),查找HomeIntent,只能匹配到 FallbackHome(与priority无关)。
android/content/pm/PackageUserState.java
public boolean isMatch(ComponentInfo componentInfo, int flags) {
final boolean isSystemApp = componentInfo.applicationInfo.isSystemApp();
final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
if (!isAvailable(flags)
&& !(isSystemApp && matchUninstalled)) return false;
if (!isEnabled(componentInfo, flags)) return false;
if ((flags & MATCH_SYSTEM_ONLY) != 0) {
if (!isSystemApp) {
return false;
}
}
final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
&& !componentInfo.directBootAware;
final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
&& componentInfo.directBootAware;
return matchesUnaware || matchesAware;
}
这里就不再继续展开了,关键代码已贴上,自己去RFSC吧。