在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
来入手。
这个窗口设置背景,这样就不会出现闪白屏的问题:
<style name="StartingWindowTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/xxx
style>
将该Theme在AndroidManifest.xml中设置给目标Activity:
android:theme="@style/StartingWindowTheme"
.....
>
.....
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.Theme_MainPage);
}
通过以上的设置之后就可以在启动的时候先显示我们设计好的图片,从视觉上让启动白屏消失
该方法主要在手机或者ROM厂商可以使用,第三方APP开发无法实现
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不要乱
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);
}
这个位置还有优化的空间哦