主頁 > 移動端開發 > Android 12 新APP啟影片面(SplashScreen API)簡介&&原始碼分析

Android 12 新APP啟影片面(SplashScreen API)簡介&&原始碼分析

2021-10-13 08:36:28 移動端開發

以往的啟影片面

  • 默認情況下剛啟動APP時會顯示一會白色背景
  • 如果把這個啟動背景設定為null,則一閃而過的白色會變成黑色
  • 如果把啟動Activity設定為背景透明【< item name=“android:windowIsTranslucent”>true</ item>】或者禁用了啟影片面【< item name=“android:windowDisablePreview”>true</ item>】;雖然一閃而過的黑色或者白色沒有了,但是因為背景透明了就會看到桌面,導致的結果就是感覺APP啟動慢了
  • 通常我們會在主題里給它設定一張公司Logo圖片【< item name=“android:windowSplashscreenContent”>@drawable/splash</ item>】,這樣就感覺APP啟動快了

全新的APP啟影片面

  • 統一的設計標準,不同APP展現出來的整體樣式是一樣的
  • 支持通過配置主題的方式更換中間的Logo/影片、背景色、圖片的背景色、底部公司品牌Logo等
  • 支持延長顯示的時間
  • 支持自定義關閉啟影片面的影片

注意事項

  • 【< item name=“android:windowSplashscreenContent”>@drawable/splash< /item>】和【< item name=“android:windowDisablePreview”>true</ item>】在Android 12設備上都失效(已廢棄),即使targetSdkVersion沒有升級到31也是這樣
  • Android 12新啟影片面,targetSdkVersion不需要升級到31,但是compileSdkVersion一定要升級到31才可以,否則編譯時無法找到主題里這些新增的屬性
  • 啟影片面的圖示/影片應該遵循Adaptive Icon(自適應圖示)的規范,不然圖片/影片可能會顯示例外

使用方法

APP在Android12上默認啟動效果

請添加圖片描述

在主題中通過配置自定義啟影片面

設定啟影片面背景色

<!--設定啟影片面背景色-->
<item name="android:windowSplashScreenBackground">#ff9900</item>

效果圖:
請添加圖片描述

設定啟影片面居中顯示的圖示或者影片

<!--設定啟影片面居中顯示的圖示或者影片-->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
<!--設定啟影片面在關閉之前顯示的時長,最長1000毫秒-->
<item name="android:windowSplashScreenAnimationDuration">1000</item>
  • windowSplashScreenAnimationDuration指的是啟影片面顯示的時間,跟影片的時長無關,也就是如果影片時間超過這個時間,它不會等待影片結束,而是直接關閉;如果希望影片顯示時間超過1秒,則需要參考后面【延遲關閉啟影片面】部分

效果圖:
請添加圖片描述

設定中間顯示圖示區域的背景色

用于解決圖示和背景顏色接近顯示不清問題

<!--設定中間顯示圖示區域的背景色,用于解決圖示和背景顏色接近顯示不清問題-->
<item name="android:windowSplashScreenIconBackgroundColor">#ff0000</item>

效果圖:
請添加圖片描述

設定啟影片面底部公司品牌圖片

官方不推薦使用,可能是因為底部再加個圖片不好看吧

<!--設定啟影片面底部公司品牌圖片,官方不推薦使用-->
<item name="android:windowSplashScreenBrandingImage">@drawable/ic_launcher_foreground</item>

效果圖:
請添加圖片描述

延遲關閉啟影片面

有時候希望啟影片面能在資料準備好之后才關閉,或者影片時間超過1秒

class MainActivity() : AppCompatActivity() {
	var isDataReady = false
	override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val contentView = findViewById<View>(android.R.id.content)
        contentView.viewTreeObserver.addOnPreDrawListener(object :
            ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                if (isDataReady) {//判斷是否可以關閉啟動影片,可以則回傳true
                    contentView.viewTreeObserver.removeOnPreDrawListener(this)
                }
                return isDataReady
            }
        })
        Thread.sleep(5000)//模擬耗時
        isDataReady = true
    }
}

效果圖:
請添加圖片描述

定制退出影片

啟影片面默認結束后是直接消失的,可能會顯得有些突兀,全新的SplashScreen支持定制退出影片

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    splashScreen.setOnExitAnimationListener { splashScreenView ->
        val slideUp = ObjectAnimator.ofFloat(
            splashScreenView,
            View.TRANSLATION_Y,
            0f,
            -splashScreenView.height.toFloat()
        )
        slideUp.interpolator = AnticipateInterpolator()
        slideUp.duration = 2000L
        slideUp.doOnEnd { splashScreenView.remove() }
        slideUp.start()
    }
}

效果圖:
請添加圖片描述

  • splashScreen是Activity中的getSplashScreen()方法回傳的

  • 官方說SplashScreenView在影片結束后要remove掉,實際測驗發現不remove也是可以的,因為影片結束后啟影片面已經被移動到看不到的地方了,不影響后續操作;但是通過查看SplashScreenView的remove方法原始碼,除了將SplashScreenView設為不可見外,還有圖片等資源的回收操作,所以建議還是要呼叫它的remove方法以回收資源

    class SplashScreenView extends FrameLayout {
    	public void remove() {
    		if (mHasRemoved) {
    			return;
    		}
    		setVisibility(GONE);
    		if (mParceledIconBitmap != null) {
    			if (mIconView instanceof ImageView) {
    				((ImageView) mIconView).setImageDrawable(null);
    			} else if (mIconView != null) {
    				mIconView.setBackground(null);
    			}
    			mParceledIconBitmap.recycle();
    			mParceledIconBitmap = null;
    		}
    		if (mParceledBrandingBitmap != null) {
    			mBrandingImageView.setBackground(null);
    			mParceledBrandingBitmap.recycle();
    			mParceledBrandingBitmap = null;
    		}
    		if (mParceledIconBackgroundBitmap != null) {
    			if (mIconView != null) {
    				mIconView.setBackground(null);
    			}
    			mParceledIconBackgroundBitmap.recycle();
    			mParceledIconBackgroundBitmap = null;
    		}
    		if (mWindow != null) {
    			final DecorView decorView = (DecorView) mWindow.peekDecorView();
    			if (DEBUG) {
    				Log.d(TAG, "remove starting view");
    			}
    			if (decorView != null) {
    				decorView.removeView(this);
    			}
    			restoreSystemUIColors();
    			mWindow = null;
    		}
    		if (mHostActivity != null) {
    			mHostActivity.setSplashScreenView(null);
    			mHostActivity = null;
    		}
    		mHasRemoved = true;
    	}
    }
    

計算啟影片面中間的影片剩余時長

上面我們說到可以自定義退出影片,也就是設定splashScreen.setOnExitAnimationListener,這個介面會在將要顯示APP主界面時回呼;

  • 如果設備性能比較差,可能會出現中間那個圖示影片已經結束,但是APP主界面卻還沒顯示的情況,這個時候如果啟影片面退出時還做一次影片,會導致APP進入主界面的時間更長,遇到這種情況應該取消退出影片,讓用戶及時看到主界面會更好一些;

  • 如果設備性能比較好,假如本來設定的啟影片面中間圖示影片時長1000毫秒,但是只執行了500毫秒的影片就可以開始顯示APP主界面影片了,卻因為固定的退出影片時長,導致需要等待更久的時間才能看到主界面

所以應該根據啟影片面中間圖示影片時長執行剩余時間來決定退出影片的時長,這樣才能盡快讓用戶看到APP主界面,并保證好的體驗效果

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    splashScreen.setOnExitAnimationListener { splashScreenView ->
        val slideUp = ObjectAnimator.ofFloat(
            splashScreenView,
            View.TRANSLATION_Y,
            0f,
            -splashScreenView.height.toFloat()
        )
        slideUp.interpolator = AnticipateInterpolator()

		//計算合適的退出影片時長
        var targetDuration = 0L
        val animationDuration = splashScreenView.iconAnimationDuration//圖示影片時長
        val animationStart = splashScreenView.iconAnimationStart//圖示影片開始時間
        if (animationDuration != null && animationStart != null) {
            val remainingDuration = (
                    animationDuration.toMillis() - (System.currentTimeMillis() - animationStart.toEpochMilli())
                    ).coerceAtLeast(0L)//計算剩余時間,如果小于0則賦值0
            targetDuration = remainingDuration
        }
        slideUp.duration = targetDuration
        slideUp.doOnEnd { splashScreenView.remove() }
        slideUp.start()
    }
}
  • 需要注意官網示例代碼中的splashScreenView.getIconAnimationDurationMillis()splashScreenView.getIconAnimationStartMillis()在實際測驗中,SplashScreenView中并沒有發現這兩個方法,取而代之的是splashScreenView.getIconAnimationDuration()splashScreenView.getIconAnimationStart();而且這兩個方法回傳的物件并不是long,而是DurationInstant,需要分別再次呼叫它們的toMillis()toEpochMilli()方法轉換成毫秒(long)
  • 官網示例代碼中的SystemClock.uptimeMillis()在實際測驗中發現也是不對的,SystemClock.uptimeMillis()回傳的是從手機開機時到現在的時間(毫秒),但是getIconAnimationStart()回傳的是卻是當時手機系統顯示的時間
  • 需要注意的是animationDurationiconAnimationStart只有當<item name="android:windowSplashScreenAnimatedIcon">配置的是影片時才不為null,如果配置的只是普通圖片,則會回傳null,所以計算剩余時長時需要判斷非空

原始碼分析

涉及到的主要類

  • SplashScreenView:啟影片面所顯示的View,繼承自FrameLayout;對應系統布局檔案是:splash_screen_view.xml

    //splash_screen_view.xml
    <android.window.SplashScreenView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:orientation="vertical">
    
        <View android:id="@+id/splashscreen_icon_view"
              android:layout_height="wrap_content"
              android:layout_width="wrap_content"
              android:layout_gravity="center"
              android:contentDescription="@string/splash_screen_view_icon_description"/>
    
        <View android:id="@+id/splashscreen_branding_view"
              android:layout_height="wrap_content"
              android:layout_width="wrap_content"
              android:layout_gravity="center_horizontal|bottom"
              android:layout_marginBottom="60dp"
              android:contentDescription="@string/splash_screen_view_branding_description"/>
    
    </android.window.SplashScreenView>
    
    public final class SplashScreenView extends FrameLayout {
    	private int mInitBackgroundColor;//界面背景色
    	private View mIconView;//界面中間顯示的圖示
        private View mBrandingImageView;//底部品牌圖示
        private Duration mIconAnimationDuration;//啟影片面顯示時長
        private Instant mIconAnimationStart;//中間影片開始執行的時間
    	public static class Builder {
    		private Drawable mIconDrawable;//界面中間顯示的圖示
            private Drawable mIconBackground;//界面中間顯示的圖示的背景色
            private Drawable mBrandingDrawable;//底部品牌圖示
            private Instant mIconAnimationStart;//中間影片開始執行的時間
            private Duration mIconAnimationDuration;//啟影片面顯示時長
    		
    		public SplashScreenView build() {
    			...
    			final SplashScreenView view = (SplashScreenView)
                        layoutInflater.inflate(R.layout.splash_screen_view, null, false);
                view.mInitBackgroundColor = mBackgroundColor;
    			view.setBackgroundColor(mBackgroundColor);//設定背景色
    			
    			ImageView imageView = view.findViewById(R.id.splashscreen_icon_view);
    			imageView.setImageDrawable(mIconDrawable);設定界面中間圖示/影片
    			imageView.setBackground(mIconBackground);//設定中間顯示的圖示的背景色
    			
    			view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
    			view.mBrandingImageView.setBackground(mBrandingDrawable);//設定底部品牌圖示
    			...
    			return view;
    		}
    	}
    }
    
  • SplashScreen:用于客戶端與SplashScreenView互動的介面,比如:自定義啟影片面退出時的影片

  • StartingSurfaceController:Android12新增,用于管理創建/釋放starting window surface;這個類里面通過系統屬性persist.debug.shell_starting_surface的值來決定是使用全新的SplashScreenView還是舊版的啟影片面

    • persist.debug.shell_starting_surface在Android12上默認為空,根據原始碼來看,如果為空,則默認值為true;也就是說Android12上默認是啟用新版啟影片面的,通過adb命令:adb shell setprop persist.debug.shell_starting_surface false并且重啟系統后,可以禁用全新啟影片面,所有APP啟影片面將變回舊版
    public class StartingSurfaceController {
    	static final boolean DEBUG_ENABLE_SHELL_DRAWER =
                SystemProperties.getBoolean("persist.debug.shell_starting_surface", true);
    			
    	StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, String packageName,
                int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
                int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
            if (!DEBUG_ENABLE_SHELL_DRAWER) {//使用舊版的啟影片面
                return mService.mPolicy.addSplashScreen(activity.token, activity.mUserId, packageName,
                        theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                        overrideConfig, displayId);
            }
    		//使用全新SplashScreenView
            synchronized (mService.mGlobalLock) {
                final Task task = activity.getTask();
                if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
                        task, activity, theme, null /* taskSnapshot */)) {
                    return new ShellStartingSurface(task);
                }
            }
            return null;
        }
    }
    
  • StartingSurfaceDrawer:創建SplashScreenView和啟動視窗的主要流程

    public class StartingSurfaceDrawer {
    	void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
                @StartingWindowType int suggestType) {
            ... ...
    		//創建啟動視窗引數
            final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
            params.setFitInsetsSides(0);
            params.setFitInsetsTypes(0);
            params.format = PixelFormat.TRANSLUCENT;
    		... ... 
            final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
    		//創建根布局
            final FrameLayout rootLayout = new FrameLayout(context);
            rootLayout.setPadding(0, 0, 0, 0);
            rootLayout.setFitsSystemWindows(false);
            final Runnable setViewSynchronized = () -> {
                SplashScreenView contentView = viewSupplier.get();
    			//將創建好的SplashScreenView添加到根布局
                rootLayout.addView(contentView);
    
            };
    		... ... 
    		//創建SplashscreenView
            mSplashscreenContentDrawer.createContentView(context, suggestType, activityInfo, taskId,
                    viewSupplier::setView);
    		... ... 
    		final WindowManager wm = context.getSystemService(WindowManager.class);
    		//添加視窗
    		if (addWindow(taskId, appToken, rootLayout, wm, params, suggestType)) {
    			... ...
    		}
        }
    	protected boolean addWindow(int taskId, IBinder appToken, View view, WindowManager wm,
                WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
    		... ... 
            wm.addView(view, params);
            ... ... 
        }
    }
    
  • SplashscreenContentDrawer:創建SplashscreenView的實作類

    public class SplashscreenContentDrawer {
    	void createContentView(Context context, @StartingWindowType int suggestType, ActivityInfo info,
                int taskId, Consumer<SplashScreenView> splashScreenViewConsumer) {
    			...
    			//創建SplashScreenView
                SplashScreenView contentView;
                    contentView = makeSplashScreenContentView(context, info, suggestType);
    			...
    			//通知SplashScreenView創建完畢
                splashScreenViewConsumer.accept(contentView);
            });
        }
    	private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai,
                @StartingWindowType int suggestType) {
    		... 
    		//讀取配置的視窗屬性
            getWindowAttrs(context, mTmpAttrs);
    		...
    		//開始創建SplashScreenView
            return new StartingWindowViewBuilder(context, ai)
                    .setWindowBGColor(themeBGColor)
                    .overlayDrawable(legacyDrawable)
                    .chooseStyle(suggestType)
                    .build();
        }
    	private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
    		//讀取在themes.xml中配置的屬性
            final TypedArray typedArray = context.obtainStyledAttributes(
                    com.android.internal.R.styleable.Window);
            attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
            attrs.mWindowBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
                    R.styleable.Window_windowSplashScreenBackground, def),
                    Color.TRANSPARENT);
            attrs.mSplashScreenIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable(
                    R.styleable.Window_windowSplashScreenAnimatedIcon), null);
            attrs.mAnimationDuration = safeReturnAttrDefault((def) -> typedArray.getInt(
                    R.styleable.Window_windowSplashScreenAnimationDuration, def), 0);
            attrs.mBrandingImage = safeReturnAttrDefault((def) -> typedArray.getDrawable(
                    R.styleable.Window_windowSplashScreenBrandingImage), null);
            attrs.mIconBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
                    R.styleable.Window_windowSplashScreenIconBackgroundColor, def),
                    Color.TRANSPARENT);
            typedArray.recycle();
        }
    	private class StartingWindowViewBuilder {
    		SplashScreenView build() {
                Drawable iconDrawable;
                final int animationDuration;
    			...
    			//設定中間的圖示/影片
                if (mTmpAttrs.mSplashScreenIcon != null) {
                    // Using the windowSplashScreenAnimatedIcon attribute
                    iconDrawable = mTmpAttrs.mSplashScreenIcon;
                    animationDuration = mTmpAttrs.mAnimationDuration;
    
                    // There is no background below the icon, so scale the icon up
                    if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
                            || mTmpAttrs.mIconBgColor == mThemeColor) {
                        mFinalIconSize *= NO_BACKGROUND_SCALE;
                    }
                    createIconDrawable(iconDrawable, false);
                } 
    			...
                return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration);
            }
    		private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
                    int animationDuration) {
                final SplashScreenView.Builder builder = new SplashScreenView.Builder(mContext)
                        .setBackgroundColor(mThemeColor)
                        .setOverlayDrawable(mOverlayDrawable)
                        .setIconSize(iconSize)
                        .setIconBackground(background)
                        .setCenterViewDrawable(foreground)
                        .setAnimationDurationMillis(animationDuration);
    			//設定底部的品牌圖示
                if (mSuggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
                        && mTmpAttrs.mBrandingImage != null) {
                    builder.setBrandingDrawable(mTmpAttrs.mBrandingImage, mBrandingImageWidth,
                            mBrandingImageHeight);
                }
                return splashScreenView;
            }
    	}
    }
    

大體類方法呼叫程序

  • ActivityRecord.showStartingWindow -> addStartingWindow -> scheduleAddStartingWindow ->
  • AddStartingWindow.run
  • SplashScreenStartingData.createStartingSurface ->
  • StartingSurfaceController.createSplashScreenStartingSurface ->
  • TaskOrganizerController.addStartingWindow
  • TaskOrganizerController.TaskOrganizerState.addStartingWindow
  • TaskOrganizerController.TaskOrganizerCallbacks.addStartingWindow
  • TaskOrganizer.addStartingWindow
  • StartingWindowController.addStartingWindow
  • StartingSurfaceDrawer.addSplashScreenStartingWindow
  • SplashscreenContentDrawer.createContentView -> makeSplashScreenContentView ->
  • getWindowAttrs -> StartingWindowViewBuilder.build -> fillViewWithIcon
  • SplashScreenView.Builder
  • StartingSurfaceDrawer.addWindow

自定義退出影片原始碼分析

  • 通過Activity獲取用于與SplashscreenView互動的SplashScreen介面;可以看出SplashScreen介面的實作類是SplashScreen的內部類SplashScreenImpl
class Activity{
	public final @NonNull SplashScreen getSplashScreen() {
        return getOrCreateSplashScreen();
    }

    private SplashScreen getOrCreateSplashScreen() {
        synchronized (this) {
            if (mSplashScreen == null) {
                mSplashScreen = new SplashScreen.SplashScreenImpl(this);
            }
            return mSplashScreen;
        }
    }
}
  • 設定退出影片監聽;可以看到真正的實作類是SplashScreenManagerGlobal;
class SplashScreenImpl implements SplashScreen {
	private OnExitAnimationListener mExitAnimationListener;
	private final SplashScreenManagerGlobal mGlobal;

	public SplashScreenImpl(Context context) {
		mGlobal = SplashScreenManagerGlobal.getInstance();
	}

	@Override
	public void setOnExitAnimationListener(//設定監聽
			@NonNull SplashScreen.OnExitAnimationListener listener) {
		synchronized (mGlobal.mGlobalLock) {
			if (listener != null) {
				mExitAnimationListener = listener;
				mGlobal.addImpl(this);
			}
		}
	}

	@Override
	public void clearOnExitAnimationListener() {//取消監聽
		synchronized (mGlobal.mGlobalLock) {
			mExitAnimationListener = null;
			mGlobal.removeImpl(this);
		}
	}
	... ...
}
  • SplashScreenManagerGlobal:它也是SplashScreen的內部類,單例模式,初始化時會向ActivityThread注冊自己,當啟影片面將要退出時回呼它的handOverSplashScreenView方法

    • 注冊的監聽全部保存在SplashScreenManagerGlobal的ArrayList串列中
    class SplashScreenManagerGlobal {
    	private final Object mGlobalLock = new Object();
    	private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>();
    
    	private SplashScreenManagerGlobal() {
    		//向ActivityThread注冊自身,用于回呼handOverSplashScreenView方法
    		ActivityThread.currentActivityThread().registerSplashScreenManager(this);
    	}
    
    	public static SplashScreenManagerGlobal getInstance() {
    		return sInstance.get();
    	}
    
    	private static final Singleton<SplashScreenManagerGlobal> sInstance =
    			new Singleton<SplashScreenManagerGlobal>() {
    				@Override
    				protected SplashScreenManagerGlobal create() {
    					return new SplashScreenManagerGlobal();
    				}
    			};
    
    	private void addImpl(SplashScreenImpl impl) {
    		synchronized (mGlobalLock) {
    			mImpls.add(impl);
    		}
    	}
    
    	private void removeImpl(SplashScreenImpl impl) {
    		synchronized (mGlobalLock) {
    			mImpls.remove(impl);
    		}
    	}
    	
    	public void handOverSplashScreenView(@NonNull IBinder token,
    			@NonNull SplashScreenView splashScreenView) {
    		... ...
    		//處理跳過啟影片面邏輯,分發退出監聽
    		dispatchOnExitAnimation(token, splashScreenView);
    	}
    
    	private void dispatchOnExitAnimation(IBinder token, SplashScreenView view) {
    		synchronized (mGlobalLock) {
    			final SplashScreenImpl impl = findImpl(token);
    			impl.mExitAnimationListener.onSplashScreenExit(view);
    		}
    	}
    }
    
  • ActivityThread在哪里回呼SplashScreenManagerGlobal.handOverSplashScreenView方法?

    class ActivityThread{
    	private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal;
    	
    	public void registerSplashScreenManager(
                @NonNull SplashScreen.SplashScreenManagerGlobal manager) {
            synchronized (this) {
                mSplashScreenGlobal = manager;
            }
        }
    	
    	@Override
        public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
            final SplashScreenView v = r.activity.getSplashScreenView();
            if (v == null) {
                return;
            }
            synchronized (this) {
                if (mSplashScreenGlobal != null) {
                    mSplashScreenGlobal.handOverSplashScreenView(r.token, v);
                }
            }
        }
    }
    
  • ActivityThread.handOverSplashScreenView大體呼叫程序:

    • ActivityClientController.splashScreenAttached ->
    • ActivityRecord.splashScreenAttachedLocked -> onSplashScreenAttachComplete
    • ClientLifecycleManager.scheduleTransaction ->
    • TransferSplashScreenViewStateItem.execute(mRequest==HANDOVER_TO)
    • ActivityThread.handOverSplashScreenView ->
    • SplashScreenGlobal.handOverSplashScreenView -> dispatchOnExitAnimation ->
    • ExitAnimationListener.onSplashScreenExit

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/310607.html

標籤:其他

上一篇:從零開發一款Android RTMP播放器

下一篇:我如何創建一個依賴動態(.dll)tensorflow庫的靜態庫(.lib)?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more