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启动黑屏白屏解决方案

Android项目启动时短暂的黑屏白屏处理

用于 android html5 视频的 chrome 上的黑屏

android从view切换surfaceview的时候,出现短暂的黑屏,怎么处理

YouTube API - 另一项活动中的黑屏

Qt for Android 启动短暂的黑屏或白屏问题如何解决?