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可以先加載一個簡單的桌面界面等解鎖後再加載一次。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章