Android 10第一次开机进Launcher闪白屏


一、背景说明

  • 问题描述:软件第一次开机或恢复出厂设置后开机,进入原生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启动整体流程如下:
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三者之间的启动优先级,并锁定白屏所在应用,找到合适的规避方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值