一、背景说明
- 问题描述:软件第一次开机或恢复出厂设置后开机,进入原生Launcher前闪现白屏,由于显示时间较短(约500ms),白屏界面未显示完整(1/3~1/2屏幕大小)就消失了。
- Android版本:Android 10
- 关键词:Android 10、开机、白屏
二、问题分析
所涉及的代码及其路径汇总如下:
packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
packages\apps\Launcher3\AndroidManifest.xml
packages\apps\Settings\src\com\android\settings\FallbackHome.java
packages\apps\Provision\src\com\android\provision\DefaultActivity.java
frameworks\base\core\java\android\app\ApplicationPackageManager.java
frameworks\base\services\java\com\android\server\SystemServer.java
frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
frameworks\base\services\core\java\com\android\server\wm\ActivityTaskManagerService.java
frameworks\base\services\core\java\com\android\server\wm\RootActivityContainer.java
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
从问题现象来看,可以有两种可能性
- 1.Launcher应用本身的问题,oncreate()前先显示了某个界面。
- 2.系统原因或者其他应用问题,在进入Launcher前先打开了某个应用或界面。
显然2的可能性更大,原因在于白屏仅在第一次和恢复出厂设置后才出现,而普通的开关机,recovery不会出现,这很容易联系到开机向导应用,我们知道开机向导主要用于对系统的语言,网络等配置进行设置,而且仅在系统第一次启动时出现,优先于Launcher显示,这些行为很符合问题的特征,但需要’证据‘支持。
通常情况下,结合开机log分析开机启动顺序,比较容易定位到问题,但由于我的平台开机时跑完bootanimation会断开adb,并且没有UART串口,故没有办法抓取完整的开机log,和闪白屏log。
对于1情况,比较好容易验证,在Launcher的onCreate();中添加打印及延时,确认白屏与Launcher显示的顺序。
packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
@Override
protected void onCreate(Bundle savedInstanceState) {
...
super.onCreate(savedInstanceState);
try {
Log.d(TAG,"delay 5 s");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
...
setContentView(mLauncherView);
...
}
验证发现,任然会闪白屏。闪白屏后延迟了5s的黑屏进入了Launcher,因此可以断定闪白屏在前,进入Launcher在后。
将上述延时移至FallbackHome
packages\apps\Settings\src\com\android\settings\FallbackHome.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
try {
Log.d(TAG,"delay 5 s");
Thread.sleep(5000);//添加5s延时
} catch (InterruptedException e) {
e.printStackTrace();
}
...
mWallManager = getSystemService(WallpaperManager.class);
if (mWallManager == null) {
Log.w(TAG, "Wallpaper manager isn't ready, can't listen to color changes!");
} else {
loadWallpaperColors(flags);//加载壁纸
}
getWindow().getDecorView().setSystemUiVisibility(flags);
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
maybeFinish();//视情况Finish自身Activity
}
而现象为开机动画播完后黑屏5s后闪白屏才进入Launcher,所以是先启动FallbackHome界面。那么问题来了:
- 为什么是先进入FallbackHome,而不是Launcher?
- FallbackHome具体是怎么打开的,它起什么作用呢?
- 闪白屏由FallbackHome引起?
对分析FallbackHome发现他使用的是系统壁纸,而系统使用定制的纯黑色背景,因此不会出现白屏,可以排除。那么’bug’到底藏在哪呢?
前面说过还有一个可疑点——开机向导。
于是乎,屏蔽平台的开机向导apk后恢复出厂设置进行验证。
# mv system/product/priv-app/Provision/Provision.apk system/product/priv-app/Provision/Provision.apk.1
# sync
恢复出厂设置后开机发现不再闪白屏,反复对比验证可以确定就是Provosion导致的闪白屏,至于具体的原因,我们还得从Launcher的启动流程开始分析。
三、Launcher启动流程
Launcher启动整体流程如下:
系统开机时init进程启动systemserver,这里从systemserver进程开始分析,之前的启动过程不再赘述。
frameworks\base\services\java\com\android\server\SystemServer.java
private void run() {
try {
...
// Start services.
try {
traceBeginAndSlog("StartServices");
startBootstrapServices();//启动引导服务,如AMS、PMS、PKMS、DMS等
startCoreServices();//启动系统核心服务,如BatteryService、WebViewUpdateService等
startOtherServices();//启动其他服务,如IMS,WMS,GpuService等
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
traceEnd();
}
...
}
}
private void startBootstrapServices() {
...
// Activity manager runs the show.
traceBeginAndSlog("StartActivityManager");
// TODO: Might need to move after migration to WM.
ActivityTaskManagerService atm = mSystemServiceManager.startService(
ActivityTaskManagerService.Lifecycle.class).getService();//启动ATMS服务
mActivityManagerService = ActivityManagerService.Lifecycle.startService(
mSystemServiceManager, atm);//启动AMS服务
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
mActivityManagerService.setInstaller(installer);
mWindowManagerGlobalLock = atm.getGlobalLock();
traceEnd();
...
}
private void startOtherServices() {
...
mActivityManagerService.systemReady(() -> {
...
}
从Android 10(API29)开始,ActivityManagerService的工作被ActivityTaskManagerService接管,虽然AMS被接管,但为例保持不同版本系统的正常运行,AMS依然可用,原有接口依然有用。只是有些接口被注解为@Deprecated ,代码跳转至ATMS执行,例如startActiviy():
@Override
public int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return mActivityTaskManager.startActivity(caller, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions);
}
调用AMS的systemReady(),准备启动Launcher
frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
...
if (bootingSystemUser) {
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
}
...
}
mAtmInternal 是ActivityTaskManagerInternal在ATMS的构造方法中初始化,是由ATMS对外提供的一个抽象类,真正的实现是在ATMS中的 LocalService,所以执行到了 LocalService 的 startHomeOnAllDisplays方法。
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) {
return mRootActivityContainer.startHomeOnAllDisplays(userId, reason);
}
}
...
}
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;
}
...
boolean startHomeOnDisplay(int userId, String reason, int displayId) {
return startHomeOnDisplay(userId, reason, displayId, false /* allowInstrumenting */,
false /* fromHomeKey */);
}
boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
boolean fromHomeKey) {
...
if (displayId == DEFAULT_DISPLAY) {
homeIntent = mService.getHomeIntent();//构建一个category为CATEGORY_HOME的Intent,表明是Home Activity
aInfo = resolveHomeActivity(userId, homeIntent);//通过PKMS从系统所用已安装的应用中,找到一个符合HomeItent的Activity
}
...
mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
displayId);//启动Home Activity
return true;
}
frameworks\base\services\core\java\com\android\server\wm\ActivityTaskManagerService.java
Intent getHomeIntent() {
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) {
intent.addCategory(Intent.CATEGORY_HOME);
}
return intent;
}
从上面代码分析可知,通过构建category为CATEGORY_HOME的Intent,与PKMS已安装的应用中找到一个匹配的aInfo,并准备启动。讲道理aInfo应当为Launcher。
packages\apps\Launcher3\AndroidManifest.xml
<activity
android:name="com.android.launcher3.Launcher"
...
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
...
</activity>
但事实上通过打印aInfo,发现有限启动的是FallbackHome,原来在安装的apk中,除了Launcher,还有以下应用有配置android.intent.category.HOME
packages/apps/Provision/AndroidManifest.xml
<application>
<activity android:name="DefaultActivity"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
...
</application>
packages/apps/Settings/AndroidManifest.xml
<activity android:name=".CryptKeeper"
...
<intent-filter android:priority="10">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Triggered when user-selected home app isn't encryption aware -->
<activity android:name=".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>
奇怪的是,从android:priority属性来看,优先级顺序应该是CryptKeeper>DefaultActivity>Launcher>FallbackHome,但事实上启动的顺序是FallbackHome>DefaultActivity>Launcher。
CryptKeeper负责锁屏相关,系统第一次开机并未进行锁屏,故不会启动。至于为什么FallbackHome优先启动呢?
顺着resolveHomeActivity()往下看…
frameworks\base\services\core\java\com\android\server\wm\RootActivityContainer.java
@VisibleForTesting
ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) {
final int flags = ActivityManagerService.STOCK_PM_FLAGS;
final ComponentName comp = homeIntent.getComponent();
ActivityInfo aInfo = null;
try {
if (comp != null) {
// Factory test.
aInfo = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
} else {
final String resolvedType =
homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver());
final ResolveInfo info = AppGlobals.getPackageManager()
.resolveIntent(homeIntent, resolvedType, flags, userId);
if (info != null) {
aInfo = info.activityInfo;
}
}
}
...
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = mService.getAppInfoForUser(aInfo.applicationInfo, userId);
return aInfo;
}
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
@Override
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int userId) {
return resolveIntentInternal(intent, resolvedType, flags, userId, false,
Binder.getCallingUid());
}
private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
int flags, int userId, boolean resolveForStart, int filterCallingUid) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
...
//查询所有符合条件的activities
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
flags, filterCallingUid, userId, resolveForStart, true /*allowDynamicSplits*/);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
final ResolveInfo bestChoice =
chooseBestActivity(intent, resolvedType, flags, query, userId);//从query 中选择最适的启动Activity
return bestChoice;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
通过打印发现query 只有FallbackHome,因此FallbackHome最新启动。
packages\apps\Settings\src\com\android\settings\FallbackHome.java
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//注册用户解锁的广播
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
maybeFinish();
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
maybeFinish();//接收到广播后执行maybeFinish()
}
};
private void maybeFinish() {
if (getSystemService(UserManager.class).isUserUnlocked()) {
final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME);
//最终调用resolveIntent()再次获取合适的HomeActivity
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();//finish()自身
}
}
}
由于开机向导的优先级高于Launcher,故FallbackHome启动DefaultActivity
packages\apps\Provision\src\com\android\provision\DefaultActivity.java
public class DefaultActivity extends Activity {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Add a persistent setting to allow other apps to know the device has been provisioned.
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);
// remove this activity from the package manager.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, DefaultActivity.class);
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
// terminate the activity.
finish();
}
}
可以看到系统开机向导内容很简单,设置DEVICE_PROVISIONED和USER_SETUP_COMPLETE表示系统完成开机设置,通常用于拉起app。另外就是diable自身,后续开机不会启动,并finish()自身。后续则启动Launcher,走正常的应用启动流程了,后续过程不在赘述了。
自此,从现象,结合log及系统启动流程,理清了白屏产生的机制,那么如何解决该问题呢?
- 方法一、
修改如下:
packages/apps/Provision/AndroidManifest.xml
<application>
<activity android:name="DefaultActivity"
+ android:directBootAware="true"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
...
</application>
由于FallbackHome启动后需要接受到用户取消解锁广播后才finish自己,启动下一个homeActivity,这样就引起了白屏闪现。因此打开Provision 的directBootAware属性,使其可直接启动。
- 方案二、
移除Provision开机向导,并将DefaultActivity中的DEVICE_PROVISIONED和USER_SETUP_COMPLETE移到Launcher中或者其他开机启动的apk。
device/qcom/common/base.mk
PRODUCT_PACKAGES := \
AccountAndSyncSettings \
DeskClock \
...
- Provision \
...
总结
通过Launcher启动的代码流程分析,配合log验证,梳理了FallbackHome、Launcher、Provision三者之间的启动优先级,并锁定白屏所在应用,找到合适的规避方法。