Android 锁屏时重启动手机如何快速启动Launcher

Launcher概述

Android系统启动的最后一步是启动一个Home应用程序,这个应用程序用来显示系统中已经安装的应用程序,这个Home应用程序就叫做Launcher。应用程序Launcher在启动过程中会PackageManagerService返回系统中已经安装的应用程序的信息,并将这些信息封装成一个快捷图标列表显示在系统屏幕上,这样用户可以通过点击这些快捷图标来启动相应的应用程序。

Launcher的启动流程

我们可以简单的概括为,Launcher是一个应用程序,这个应用程序在AndroidManifest.xml文件主Activity里面配置了Action为Intent.ACTION_MAIN,Category为Intent.CATEGORY_HOME,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.launcher3">
    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
   
    ....
    <application
        ...
        android:largeHeap="@bool/config_largeHeap"
        android:restoreAnyVersion="true"
        android:supportsRtl="true" >

        -->
        <activity
            android:name="com.android.launcher3.Launcher"
            android:launchMode="singleTask"
            android:clearTaskOnLaunch="true"
            android:stateNotNeeded="true"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="unspecified"
            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
            android:resizeableActivity="true"
            android:resumeWhilePausing="true"
            android:taskAffinity=""
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY"/>
                <category android:name="android.intent.category.LAUNCHER_APP" />
            </intent-filter>
        </activity>
....
    </application>
</manifest>

应用程序是由ActivityManagerService系统服务启动的,手机重启后AMS会启动Launcher应用这个过程和启动普通程序没有太大区别,只是需要AMS先找到Launcher应用,如下是启动Launcher的Intent方法,因为Launcher应用是配置了Category为Intent.CATEGORY_HOME所以AMS可以通过这个条件过滤出所有Launcher应用,如果有多个Launcher则会弹出框给用户选择。

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;
    }

问题分析:

按照正常流程在手机重启后Launcher应该是很快就会被启动的,但是当手机已经通电开机但是用户并有解锁锁屏的时候,Android N运行于一个安全的模式,也就是Dierect Boot模式,该模式下普通应用程序无法启动很多权限被禁掉了,所以AMS不会第一时间启动Launcher应用程序,而是由Setting应用启动,Setting程序是在监听到解锁广播判断Launcher应用是否启动如果启动了则关闭seting界面显示launcher界面,从进入Setting界面到启动Launcher进程再到Launcher界面显示需要3秒左右时间,这就导致解锁后无法第一时间看到Launcher界面启动。

下面是Setting应用启动Launcher的过程

在分析7.0过程中发现在启动Launcher之前会先启动一个FallbackHome,之后才会启动Launcher,通过调查发现FallbackHome属于Settings中的一个activity,Settings的android:directBootAware为true,并且FallbackHome在category中配置了Home属性,而Launcher的android:directBootAware为false,所有只有FallbackHome可以在direct boot模式下启动。
 

 <!-- Triggered when user-selected home app isn't encryption aware -->
        <activity
                android:name=".FallbackHome"
                android:excludeFromRecents="true"
                android:label=""
                android:screenOrientation="behind"
                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>

FallbackHome启动Launcher流程

FallbackHome代码非常简单就是一个透明布局提示Laucher正在启动,监听解锁广播Intent.ACTION_USER_UNLOCKED事件,收到解锁事件后再用Handler循环判断Launcher是否启动,如果没有启动则继续每个500毫秒判断一次,直到Launcher启动才关闭当前界面显示Launcher界面

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();
            }
        }
    }

完整代码如下 

public class FallbackHome extends Activity {
    private static final String TAG = "FallbackHome";
    private static final int PROGRESS_TIMEOUT = 2000;

    private boolean mProvisioned;

    private final Runnable mProgressTimeoutRunnable = () -> {
        View v = getLayoutInflater().inflate(
                R.layout.fallback_home_finishing_boot, null /* root */);
        setContentView(v);
        v.setAlpha(0f);
        v.animate()
                .alpha(1f)
                .setDuration(500)
                .setInterpolator(AnimationUtils.loadInterpolator(
                        this, android.R.interpolator.fast_out_slow_in))
                .start();
        getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set ourselves totally black before the device is provisioned so that
        // we don't flash the wallpaper before SUW
        mProvisioned = Settings.Global.getInt(getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
        if (!mProvisioned) {
            setTheme(R.style.FallbackHome_SetupWizard);
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
        } else {
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        }

        registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
        maybeFinish();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mProvisioned) {
            mHandler.postDelayed(mProgressTimeoutRunnable, PROGRESS_TIMEOUT);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        mHandler.removeCallbacks(mProgressTimeoutRunnable);
    }

    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
    }

    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();
        }
    };
}

快速启动Launcher方案:

根据上面分析我们知道Launcher非directBootAware应用,PMS中的限制导致此时无法启动Launcher,所以第一时间无法让Launcher启动,既然如此我们只要让Launcher也支持directBootAware就可以了,这个过程中遇到了问题:将Launcher标记为directBootAware应用会导致开机后Launcher crash。因为Launcher中的widget仍旧是非directBootAware的,此时仍旧无法启动,除非将widget相关的APP都标记为directBootAware,这个我们可以对widget做一个延迟加载处理即可。

下面是具体实施过程:

1.首先我们需要支持Launcher支持directBootAware,如下在配置即可

android:defaultToDeviceProtectedStorage="true"

android:directBootAware="true"

<application
        android:backupAgent=".LauncherBackupAgent"
        android:fullBackupContent="@xml/backupscheme"
        android:fullBackupOnly="true"
        android:defaultToDeviceProtectedStorage="true"
        android:directBootAware="true"
        android:hardwareAccelerated="true"
        android:icon="@drawable/ic_launcher_home"
        android:label="@string/derived_app_name"
        android:theme="@style/AppTheme"
        android:largeHeap="@bool/config_largeHeap"
        android:networkSecurityConfig="@xml/network_security_config"
        android:restoreAnyVersion="true"
        android:supportsRtl="true">

2.延迟加载widget,如下是加载widget方法,我们可以监听解锁广播事件,在解锁后延迟500毫秒再加载widget

mAppWidgetHost = new LauncherAppWidgetHost(this); mAppWidgetHost.startListening();

总结:

因为很多厂商对Launcher做了定制化所以按照上面的方案可能会遇到启动问题,不过这些问题都是可以逐个处理的,其实要想避免上面问题,我们可以j可以先加载一个简单的桌面界面等解锁后再加载一次。

发布了92 篇原创文章 · 获赞 27 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/zhuxingchong/article/details/100915030
今日推荐