Android FallbackHome导致的黑屏问题
Posted 福尔摩聪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android FallbackHome导致的黑屏问题相关的知识,希望对你有一定的参考价值。
问题描述:在android9.0的机器上,重启设备之后,开机动画结束之后,在launcher 拉起来之前,总是会黑个几秒钟,很影响用户体验.
研究问题:
1.刚开始认为是系统或者launcher发生了crash,然后黑屏,然后launcher重启
但是抓了一份完整的log之后发现并没有相关crash的相关信息.同时我又下载了个第三方的桌面,重启之后仍然有黑屏的现象,所以说这个和launcher本身是没有关系的.
2.那接下来就继续分析log了.
12-31 11:00:01.385 2868 2868 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.android.tv.settings/.system.FallbackHome} from uid 0
.....
12-31 11:00:03.831 2868 2882 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.oversea.aslauncher/.ui.main.MainActivity} from uid 0
发现系统在启动launcher之前,会先启动一下Settings里面的FallBackHome,看源码可知这是一个透明无界面的activity.然后两秒之后才会启动我们的launcher. 那按照这个线索分析,为什么会先启动这个fallbackhome呢?
先来缕一下系统启动完成之后启动launcher的流程:
1.ActivityManagerService.systemReady()
当systemserver将各项系统服务启动完成之后,这些system server会走对应的systemReady函数.而和启动launcher相关的是AMS的systemReady:
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
traceLog.traceBegin("PhaseActivityManagerReady");
/*************省略无数行代码******************/
startHomeActivityLocked(currentUserId, "systemReady");
/*************省略无数行代码******************/
traceLog.traceEnd(); // PhaseActivityManagerReady
}
boolean startHomeActivityLocked(int userId, String reason) {
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
// We are running in factory test mode, but unable to find
// the factory test app, so just sit around displaying the
// error message and don't try to start anything.
return false;
}
Intent intent = getHomeIntent(); //获取当前的homeIntent
ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {
intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being
// instrumented.
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid, true);
if (app == null || app.instr == null) {
intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
// For ANR debugging to verify if the user activity is the one that actually
// launched.
final String myReason = reason + ":" + userId + ":" + resolvedUserId;
mActivityStartController.startHomeActivity(intent, aInfo, myReason);
}
} else {
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
}
return true;
}
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);
}
Log.i("cong", "getHomeIntent: the intent is " + intent);
return intent;
}
那么开机时这个Intent具体是什么呢?
打log发现这个intent如下:
getHomeIntent: the intent is Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x100 }
这个intent是一个很正常的查找HOMEINTENT,那么ActivityInfo具体又是怎么来的呢?
private ActivityInfo resolveActivityInfo(Intent intent, int flags, int userId) {
ActivityInfo ai = null;
ComponentName comp = intent.getComponent();
try {
if (comp != null) {
// Factory test.
ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
} else {
ResolveInfo info = AppGlobals.getPackageManager().resolveIntent(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags, userId);
if (info != null) {
ai = info.activityInfo;
}
}
} catch (RemoteException e) {
// ignore
}
return ai;
}
这个也就是一个正常通过PMS查询Intent对应的Activity,那么查询的结果是什么呢?
可以看出来此时查询的结果是FallBackHome.到这里我们可能有些奇怪,为什么是fallbackhome呢?咱们的launcher应该也有这个属性啊,为什么没有查询到呢?
这个和android7.0开始使用的directboot mode有关.关于directoot mode,这个后续会写博客再讲.这里只是简单说一下. settings应用的
android:directBootAware="true"
这个属性置为true,所以一开机就能够启动,能够查询的到.而我们的launcher这个属性是false,导致开机时查询的时候,系统还没有对launcher的xml解析,所以是查不到的.所以目前只会查询到settings里面的fallbackhome,然后启动fallbackhome.
那么后续什么时候启动咱们的launcher呢?fallbackhome具体又是一个怎么样的界面呢?
我们来看源码:
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.settings.system;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.UserManager;
import android.util.Log;
import java.util.Objects;
public class FallbackHome extends Activity {
private static final String TAG = "FallbackHome";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//解锁成功的广播
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
maybeFinish();
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
maybeFinish();
}
};
//判断是否已经解锁,如果已经解锁,并且找到真正的launcher,那么会将跳转到launcher并且将自己finsh掉
private void maybeFinish() {
//判断是否已经解锁,如果没有解锁,那么直接return掉,继续等待
if (getSystemService(UserManager.class).isUserUnlocked()) {
final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME);
final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
//如果没有找到新的launcher,那么会每隔500ms继续检查
if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
mHandler.sendEmptyMessageDelayed(0, 500);
} else {
//如果找到了新launcher,那么会将自己finish掉
Log.d(TAG, "User unlocked and real home found; let's go!");
finish();
}
}
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
maybeFinish();
}
};
}
可以看出来这是一个没有界面的activity,那么就和我们的黑屏对应上了.开机完成之后,启动launcher时先启动了FallBackHome,而这个activity是没有界面的,所以导致了黑屏.
FallbackHome开启之后,会等待user解锁完成(DirectBoot mode的机制),解锁完成之后会查询新的launcher并且打开.如果没有找到新的launcher,那么他会每隔500ms查询一次.如果一直查询不到,那么他会一直黑屏.
最后我们来总结一下:从android7.0开始,使用了directboot mode这个机制,所以开机时不是直接启动我们的launcher,而是先启动Settings里面的FallbackHome,这是一个没有界面的activity. 在这个activity启动完成后,他会检测系统有没有解锁并且接收系统解锁的广播.如果系统还未解锁完成,那么他会一直等待这个广播,所以界面还是会黑. 如果系统解锁完成,那么他会查找系统内的launcher,如果找到,那么会直接跳转到新launcher并且将自己finish掉. 如果没有找到,那么他会每隔500ms继续检测一次,直到找到新launcher,在这期间也是黑屏的.
咱们系统内肯定是有心launcher的,所以黑屏的问题肯定是前者. 那么我们该如何解决呢?
首先想到的是修改launcher启动机制,不启动fallbackhome,直接启动我们的launcher. 这个尝试了一下,不太可行,要改的东西有点多,不是说只在AMS里面强行打开我们的laucher就可以了,这个时候系统还未解锁,是找不到这个intent的. 要想在这个方向上修改,肯定需要修改directboot机制,这个工作量就有点大了,暂不考虑.
后面我主要尝试了两种修改方法,并且都是有效的:
1.为FallBackHome增加一个界面,这个是最简单的,并且用户也能接受的.界面可以是一个简单的图片,提示用户 系统正在加载中.
2.延长开机动画.这个不是太推荐.
开机动画是显示在最上层的,延长开机动画,fallbackhome会显示在下层,上层继续播放着开机动画,看起来是没什么问题的.但是这个时间不太好把握.时间短了,开机动画播完,新launcher还没有启动完成,还是会出现黑屏或者闪屏. 时间设置长了,给用户的感觉不好,总感觉开机时间变长了.可以看需求自己设置.:
在WindowManagerService里面的performEnableScreen做出如下修改:
//add by lcc for delay bootanimation dissmis on
/*
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
// stop boot animation
// formerly we would just kill the process, but we now ask it to exit so it
// can choose where to stop the animation.
SystemProperties.set("service.bootanim.exit", "1");
mBootAnimationStopped = true;
}
if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {
if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: Waiting for anim complete");
return;
}
try {
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
data, null, 0);
data.recycle();
}
} catch (RemoteException ex) {
Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");
}*/
mH.postDelayed(new Runnable() {
@Override
public void run() {
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
// stop boot animation
// formerly we would just kill the process, but we now ask it to exit so it
// can choose where to stop the animation.
SystemProperties.set("service.bootanim.exit", "1");
mBootAnimationStopped = true;
}
if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {
if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: Waiting for anim complete");
return;
}
try {
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
data, null, 0);
data.recycle();
}
} catch (RemoteException ex) {
Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");
}
}
}, 2000);
//add by lcc for delay bootanimation dissmis off
改动不大,其实就加了一个2s的时延.
以上是关于Android FallbackHome导致的黑屏问题的主要内容,如果未能解决你的问题,请参考以下文章
用于 android html5 视频的 chrome 上的黑屏