Android11 SplashScreen 的显示和退出流程

应用的启动到显示到屏幕是需要一定的时间的,为了提升用户的体验,google加入了启动窗口,也就是SplashScreen

SplashScreen显示流程
在应用的启动过程中,会调用到ActivityStarter的startActivityInner方法,具体可参考:Android11 应用启动流程

ActivityStarter.startActivityInner

//frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
int startActivityInner(/*省略*/){
	//省略
	 mTargetStack.startActivityLocked(mStartActivity,
                topStack != null ? topStack.getTopNonFinishingActivity() : null, newTask,
                mKeepCurTransition, mOptions);
	//省略
}

ActivityStack.startActivityLocked

//frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java
void startActivityLocked(/*省略*/) {
	//省略
		if (r.mLaunchTaskBehind) {
               //省略
		} else if (SHOW_APP_STARTING_PREVIEW && doShow) {
               	//省略
		r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
	}

	//省略
}

ActivityRecord.showStartingWindow

//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
        //省略
        final CompatibilityInfo compatInfo =
                mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
        final boolean shown = addStartingWindow(packageName, theme,
                compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                allowTaskSnapshot(),
                mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal());
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN;
        }
    }

addStartingWindow

//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean addStartingWindow(/*省略*/) {
	if (theme != 0) {
            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
                    com.android.internal.R.styleable.Window,
                    mWmService.mCurrentUserId);
          	//开始获取配置的属性
            final boolean windowIsTranslucent = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
            final boolean windowIsFloating = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowIsFloating, false);
            final boolean windowShowWallpaper = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
            final boolean windowDisableStarting = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowDisablePreview, false);
           
            if (windowIsTranslucent) {//配置了windowIsTranslucent,直接返回
                return false;
            }
            if (windowIsFloating || windowDisableStarting) {//配置了windowDisablePreview或者windowIsFloating也返回
                return false;
            }
           //省略
		mStartingData = new SplashScreenStartingData(mWmService, pkg,
                theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                getMergedOverrideConfiguration());
        scheduleAddStartingWindow();
        return true;

}

如果不想要这个启动窗口,就可以参考配置对应的属性。
创建SplashScreenStartingData,然后调用scheduleAddStartingWindow继续处理

scheduleAddStartingWindow

//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void scheduleAddStartingWindow() {
        // Note: we really want to do sendMessageAtFrontOfQueue() because we
        // want to process the message ASAP, before any other queued
        // messages.
        if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");
            mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
        }
    }

private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();

执行AddStartingWindow的run方法

private class AddStartingWindow implements Runnable {
	@Override
    public void run() {
		synchronized (mWmService.mGlobalLock) {
              //省略
        	startingData = mStartingData;//对startingData 进行了赋值
        }
         WindowManagerPolicy.StartingSurface surface = null;
            try {
                surface = startingData.createStartingSurface(ActivityRecord.this);
            } catch (Exception e) {
                Slog.w(TAG, "Exception when adding starting window", e);
            }
		}
		if (surface != null) {
			startingSurface = surface;//对startingSurface 赋值
		}
	//省略
}

SplashScreenStartingData.createStartingSurface

	@Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mPolicy.addSplashScreen(activity.token, activity.mUserId, mPkg, mTheme,
                mCompatInfo, mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
                mMergedOverrideConfiguration, activity.getDisplayContent().getDisplayId());
    }

mService.mPolicy是PhoneWindowManager对象

PhoneWindowManager.addSplashScreen

//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
 @Override
    public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
            int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
            int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
		if (!SHOW_SPLASH_SCREENS) {//不要启动窗口的话,也可以修改这个值
            return null;
        }
		if (theme != context.getThemeResId() || labelRes != 0) {
                try {
                    context = context.createPackageContextAsUser(packageName, CONTEXT_RESTRICTED,
                            UserHandle.of(userId));//获取要启动应用的context
                    context.setTheme(theme);//设置主题
                } catch (PackageManager.NameNotFoundException e) {
                   
                }
       }
		//省略
		final PhoneWindow win = new PhoneWindow(context);//创建PhoneWindow
		 win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);//设置type为TYPE_APPLICATION_STARTING
		final WindowManager.LayoutParams params = win.getAttributes();
       	params.token = appToken;//设置token
       	params.packageName = packageName;//设置包名
       	addSplashscreenContent(win, context);//可以配置启动窗口要显示的内容
       	wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
       	view = win.getDecorView();
		wm.addView(view, params);//添加view
 
		return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
	}

可以看出,SplashScreen的添加和系统窗口的添加是一样,都是调用addView去添加一个窗口。需要注意

  • 窗口类型为TYPE_APPLICATION_STARTING
  • token为ActivityRecord的token,在WMS端决定该窗口是挂在ActivityRecord下
  • 返回的是一个SplashScreenSurface,也就是说前面startingSurface 是一个SplashScreenSurface对象

最后来看一下addSplashscreenContent方法

//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void addSplashscreenContent(PhoneWindow win, Context ctx) {
        final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);
        final int resId = a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);
        a.recycle();
        if (resId == 0) {
            return;
        }
        final Drawable drawable = ctx.getDrawable(resId);
        if (drawable == null) {
            return;
        }

        // We wrap this into a view so the system insets get applied to the drawable.
        final View v = new View(ctx);
        v.setBackground(drawable);
        win.setContentView(v);
    }

通过配置windowSplashscreenContent来设置启动窗口需要显示的内容

SplashScreen退出流程
待启动的应用绘制完成之后,需要退出SplashScreen,其调用流程如下

WindowManager: 	at com.android.server.wm.ActivityRecord.removeStartingWindow(ActivityRecord.java:1970)
WindowManager: 	at com.android.server.wm.ActivityRecord.onFirstWindowDrawn(ActivityRecord.java:5346)
WindowManager: 	at com.android.server.wm.WindowState.performShowLocked(WindowState.java:4438)
WindowManager: 	at com.android.server.wm.WindowStateAnimator.commitFinishDrawingLocked(WindowStateAnimator.java:375)

从performShowLocked开始分析

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
boolean performShowLocked() {
	//省略

        final int drawState = mWinAnimator.mDrawState;
        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
            if (mAttrs.type != TYPE_APPLICATION_STARTING) {
                mActivityRecord.onFirstWindowDrawn(this, mWinAnimator);//现在要显示的不是启动窗口
            } else {
                mActivityRecord.onStartingWindowDrawn();
            }
        }
	//省略

ActivityRecord.onFirstWindowDrawn

//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
       //省略
        removeStartingWindow();
		//省略
    }

removeStartingWindow

//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void removeStartingWindow() {
	//省略
	final WindowManagerPolicy.StartingSurface surface;
	if (mStartingData != null) {
		surface = startingSurface;
		mStartingData = null;
		startingSurface = null;
		startingWindow = null;
		startingDisplayed = false;

	//省略
	 mWmService.mAnimationHandler.post(() -> {
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface);
            try {
                surface.remove();
            } catch (Exception e) {
                Slog.w(TAG_WM, "Exception when removing starting window", e);
            }
	});
}

首先对surface进行赋值并清空一些变量,startingSurface是前面通过createStartingSurface得到的SplashScreenSurface对象,然后调用SplashScreenSurface的remove方法

SplashScreenSurface.remove

 @Override
    public void remove() {
        final WindowManager wm = mView.getContext().getSystemService(WindowManager.class);
        wm.removeView(mView);
    }

调用removeView去移除之前显示的启动窗口。

总结
启动窗口的启动和退出也是通过addView/removeView来实现的(本文忽略了WMS端的处理)

启动
在这里插入图片描述
退出
在这里插入图片描述

相关推荐

  1. Android11 FallbackHome启动关闭流程分析

    2024-07-13 08:30:03       32 阅读
  2. android11 系统启动流程 面试题目

    2024-07-13 08:30:03       37 阅读
  3. Android 软键盘显示隐藏

    2024-07-13 08:30:03       54 阅读
  4. Android 11系统启动流程

    2024-07-13 08:30:03       39 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-13 08:30:03       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-13 08:30:03       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-13 08:30:03       57 阅读
  4. Python语言-面向对象

    2024-07-13 08:30:03       68 阅读

热门阅读

  1. Spuer().__init__的意义

    2024-07-13 08:30:03       28 阅读
  2. 匿名函数与函数

    2024-07-13 08:30:03       28 阅读
  3. ios CCRuntime.m

    2024-07-13 08:30:03       23 阅读
  4. js项目生产环境中移除 console

    2024-07-13 08:30:03       23 阅读
  5. uniapp微信小程序授权登录实现

    2024-07-13 08:30:03       23 阅读
  6. 版本发布 | IvorySQL 3.3 发版

    2024-07-13 08:30:03       26 阅读
  7. 【分布式系统】Ceph对象存储系统之RGW接口

    2024-07-13 08:30:03       27 阅读
  8. 浅谈PostCSS

    2024-07-13 08:30:03       26 阅读
  9. AI学习指南机器学习篇-层次聚类的优缺点

    2024-07-13 08:30:03       23 阅读
  10. 一文学会鉴别“套壳”ChatGPT模型

    2024-07-13 08:30:03       28 阅读
  11. MPPT概念

    2024-07-13 08:30:03       25 阅读
  12. Python MySQL 教程

    2024-07-13 08:30:03       29 阅读