实战-Android开机进入Launcher前黑屏问题

问题引入
在新平台开发过程中,测试同学一直抱怨在进入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吧。

你可能感兴趣的:(android,tv)