Android性能优化之APP启动白屏

在APP启动的时候如果初始化时间比较长的话会先显示系统的加载窗口(StartingWindow),等APP内部的初始化完成后才会显示真正的界面。
在启动Activity的时候会在ActivityStack中调用ActivityRecord的showStartWindow方法,该方法会调用WindowManagerService的setAppStartingWindow
方法,最终由PhoneWindowManager创建和显示该窗口:
PhoneWindowManager.java

@Override
public View addStartingWindow(IBinder appToken, String packageName, int theme,
        CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
        int icon, int logo, int windowFlags, Configuration overrideConfig) {
    if (!SHOW_STARTING_ANIMATIONS) {
        return null;
    }
    if (packageName == null) {
        return null;
    }

    WindowManager wm = null;
    View view = null;

    try {
        Context context = mContext;
        if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow " + packageName
                + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
                + Integer.toHexString(theme));
        if (theme != context.getThemeResId() || labelRes != 0) {
            try {
                context = context.createPackageContext(packageName, 0);
                context.setTheme(theme);
            } catch (PackageManager.NameNotFoundException e) {
                // Ignore
            }
        }

        if (overrideConfig != null && overrideConfig != EMPTY) {
            if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow: creating context based"
                    + " on overrideConfig" + overrideConfig + " for starting window");
            final Context overrideContext = context.createConfigurationContext(overrideConfig);
            overrideContext.setTheme(theme);
            final TypedArray typedArray = overrideContext.obtainStyledAttributes(
                    com.android.internal.R.styleable.Window);
            final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
            if (resId != 0 && overrideContext.getDrawable(resId) != null) {
                // We want to use the windowBackground for the override context if it is
                // available, otherwise we use the default one to make sure a themed starting
                // window is displayed for the app.
                if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow: apply overrideConfig"
                        + overrideConfig + " to starting window resId=" + resId);
                context = overrideContext;
            }
        }

        final PhoneWindow win = new PhoneWindow(context);
        win.setIsStartingWindow(true);

        CharSequence label = context.getResources().getText(labelRes, null);
        // Only change the accessibility title if the label is localized
        if (label != null) {
            win.setTitle(label, true);
        } else {
            win.setTitle(nonLocalizedLabel, false);
        }

        win.setType(
            WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);

        synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
            // Assumes it's safe to show starting windows of launched apps while
            // the keyguard is being hidden. This is okay because starting windows never show
            // secret information.
            if (mKeyguardHidden) {
                windowFlags |= FLAG_SHOW_WHEN_LOCKED;
            }
        }

        // Force the window flags: this is a fake window, so it is not really
        // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
        // flag because we do know that the next window will take input
        // focus, so we want to get the IME window up on top of us right away.
        win.setFlags(
            windowFlags|
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
            windowFlags|
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

        win.setDefaultIcon(icon);
        win.setDefaultLogo(logo);

        win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.MATCH_PARENT);

        final WindowManager.LayoutParams params = win.getAttributes();
        params.token = appToken;
        params.packageName = packageName;
        params.windowAnimations = win.getWindowStyle().getResourceId(
                com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
        params.privateFlags |=
                WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
        params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;

        if (!compatInfo.supportsScreen()) {
            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
        }

        params.setTitle("Starting " + packageName);

        wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        view = win.getDecorView();

        if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Adding starting window for "
            + packageName + " / " + appToken + ": " + (view.getParent() != null ? view : null));

        wm.addView(view, params);

        // Only return the view if it was successfully added to the
        // window manager... which we can tell by it having a parent.
        return view.getParent() != null ? view : null;
    } catch (WindowManager.BadTokenException e) {
        // ignore
        Log.w(TAG, appToken + " already running, starting window not displayed. " +
                e.getMessage());
    } catch (RuntimeException e) {
        // don't crash if something else bad happens, for example a
        // failure loading resources because we are loading from an app
        // on external storage that has been unmounted.
        Log.w(TAG, appToken + " failed creating starting window", e);
    } finally {
        if (view != null && view.getParent() == null) {
            Log.w(TAG, "view not successfully added to wm, removing view");
            wm.removeViewImmediate(view);
        }
    }

    return null;
}

从代码上我们可以看到该Window的theme和需要启动的Activity的theme是一样的,注意不是Application的theme,那么如果我们要进行优化的话就要从theme
来入手。

优化方法

一、配置主题

1、首先定义一个Theme给StartingWindow使用,该theme越简单越好,在该Theme中我们主要使用的是windowBackground属性,通过该属性来为

这个窗口设置背景,这样就不会出现闪白屏的问题:


<style name="StartingWindowTheme" parent="AppTheme">
    <item name="android:windowBackground">@drawable/xxx

style>

将该Theme在AndroidManifest.xml中设置给目标Activity:


                android:theme="@style/StartingWindowTheme"
.....
>
.....


2、在该Activity的onCreate方法中调用setTheme设置真正的主题


@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        setTheme(R.style.Theme_MainPage);
}

通过以上的设置之后就可以在启动的时候先显示我们设计好的图片,从视觉上让启动白屏消失

二、配置layout

该方法主要在手机或者ROM厂商可以使用,第三方APP开发无法实现

1、首先定义一个属性来对StartingWindow配置布局,该布局必须非常简单,不能有任何影响性能的因素

frameworks/base/core/res/res/values/attrs.xml


<declare-styleable name="Window">
......
    
    <attr name="windowLightStatusBar" format="boolean" />
    
    <attr name="startingWindowLayout" format="reference" />
declare-styleable>

然后给该属性指定ID :
frameworks/base/core/res/res/values/public.xml





<resources>


  <eat-comment />

      ......
    <public type="attr" name="startingWindowLayout" id="0x01010531" />

resources>


注意,在添加ID的时候一定要保证ID不要乱

2、修改PhonewindowManager中构建StartingWindow的地方,加载该指定的布局

PhonewindowManager.java



         /*
             * 获取主题中配置的StartingWindow的布局并设置到StartingWindow中
             */
            final TypedArray ta = context.obtainStyledAttributes(
                    com.android.internal.R.styleable.Window);
            int startWindowLayout = ta.getResourceId(com.android.internal.R.styleable.Window_startWindowLayout,0);
            if(startWindowLayout != 0){
                win.setStartingView(startWindowLayout);
            }


PhoneWindow.java


    public void setStartingView(int layoutResID){
        addStaringWindowContentView(layoutResID);
    }

这个位置还有优化的空间哦

3、最后在APP中定义主题的时候给startingWindowLayout设置上启动窗口的布局即可

你可能感兴趣的:(Android性能优化)