The APP startup page is the most common and necessary scenario in China. The startup page is a mandatory requirement on iOS. In fact, configuring the startup page is very simple, because in fluent, you only need:
- iOS configuration launchscreen storyboard;
- Configure windows background for Android;
Generally, as long as the configuration is correct and the picture size matches, there will be no problem. In that case, what else needs to be adapted?
In fact, iOS won't have any problems most of the time, because launchscreen The storyboard process was originally used by iOS officials to make the transition of application startup; As far as Andorid is concerned, until 12 years ago, the window background was only a "folk" wild way, so as far as Andorid is concerned, this involves one point:
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame" android:value="true" />
Therefore, the following mainly introduces what operations Flutter has done for this startup diagram on Android ~
1, Ancient times
In the forgotten version of "ancient times", the FlutterActivity is still in io flutter. App. When you are in the fluteractivity path, the logic of starting the page is relatively simple, which is mainly determined by whether SplashScreenUntilFirstFrame is configured in the App's AndroidManifest file.
<meta-data android:name="io.flutter.app.android.SplashScreenUntilFirstFrame" android:value="true" />
When the FlutterView inside the FlutterActivity is created, it will judge whether to use the createLaunchView logic by reading meta data:
- 1. Get the Android. Net of the current topic R.attr. The Drawable window background;
- 2. Create a LaunchView and load the Drawable;
- 3. Add the LaunchView to the ContentView of the Activity;
- 4. Remove the LaunchView when the shutter is on the firstframe;
private void addLaunchView() { if (this.launchView != null) { this.activity.addContentView(this.launchView, matchParent); this.flutterView.addFirstFrameListener(new FirstFrameListener() { public void onFirstFrame() { FlutterActivityDelegate.this.launchView.animate().alpha(0.0F).setListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { ((ViewGroup)FlutterActivityDelegate.this.launchView.getParent()).removeView(FlutterActivityDelegate.this.launchView); FlutterActivityDelegate.this.launchView = null; } }); FlutterActivityDelegate.this.flutterView.removeFirstFrameListener(this); } }); this.activity.setTheme(16973833); } }
Is it very simple, then some people will wonder why they do this? Don't I just configure the android:windowBackground of the Activity?
This is the problem of time difference mentioned above, because there is a probability that a black screen will appear between the start page and the first frame rendered by fluent, so this behavior is needed to realize the transition.
Before 2.5
After "ancient times", fluteractivity came to io Flutter. embedding. android. Before the release of version 2.5 of fluteractivity, Flutter made many adjustments and optimizations for this startup process, mainly SplashScreen.
Since entering the embedding stage, fluteractivity is mainly used to implement an interface called Host, of which providesplayscreen is related to us.
By default, it will judge whether SplashScreenDrawable is configured in the AndroidManifest file.
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" />
By default, when SplashScreenDrawable is configured in the AndroidManifest file, this Drawable will be built as DrawableSplashScreen when FlutterActivity creates a FlutterView.
DrawableSplashScreen is actually an implementation of Io flutter. embedding. android. The class of splashScreen interface, which is used to:
When the Activity creates the fluterview, load the SplashScreenDrawable configured in the AndroidManifest into splashScreenView(ImageView);, The transitionToFlutter method is provided for execution.
After that, the FlutterSplashView will be created in the FlutterActivity, which is a FrameLayout.
FlutterSplashView adds FlutterView and ImageView together, and then executes the animation through the method of transitionToFlutter. Finally, at the end of the animation, remove the splashScreenView through onTransitionComplete.
So the overall logic is:
- Create DrawableSplashScreen according to meta;
- FlutterSplashView adds FlutterView first;
- FlutterSplashView first adds the ImageView of splashScreenView;
- Finally, execute transitionToFlutter in the addOnFirstFrameRenderedListener callback to trigger the animate and remove the splashScreenView.
Of course, this is also a sub state:
- Execute transitionToFlutter after the engine is loaded;
- After the engine has been loaded, execute transition toflutter immediately;
- The current fluterview has not been added to the engine. Wait until it is added to the engine before transitionToFlutter;
public void displayFlutterViewWithSplash(@NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) { if (this.flutterView != null) { this.flutterView.removeOnFirstFrameRenderedListener(this.flutterUiDisplayListener); this.removeView(this.flutterView); } if (this.splashScreenView != null) { this.removeView(this.splashScreenView); } this.flutterView = flutterView; this.addView(flutterView); this.splashScreen = splashScreen; if (splashScreen != null) { if (this.isSplashScreenNeededNow()) { Log.v(TAG, "Showing splash screen UI."); this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState); this.addView(this.splashScreenView); flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener); } else if (this.isSplashScreenTransitionNeededNow()) { Log.v(TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition."); this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState); this.addView(this.splashScreenView); this.transitionToFlutter(); } else if (!flutterView.isAttachedToFlutterEngine()) { Log.v(TAG, "FlutterView is not yet attached to a FlutterEngine. Showing nothing until a FlutterEngine is attached."); flutterView.addFlutterEngineAttachmentListener(this.flutterEngineAttachmentListener); } } } private boolean isSplashScreenNeededNow() { return this.flutterView != null && this.flutterView.isAttachedToFlutterEngine() && !this.flutterView.hasRenderedFirstFrame() && !this.hasSplashCompleted(); } private boolean isSplashScreenTransitionNeededNow() { return this.flutterView != null && this.flutterView.isAttachedToFlutterEngine() && this.splashScreen != null && this.splashScreen.doesSplashViewRememberItsTransition() && this.wasPreviousSplashTransitionInterrupted(); }
Of course, the fluteractivity at this stage can also customize the SplashScreen through the override provideSplashScreen method.
Note that the SplashScreen here is not equal to the SplashScreen of Android 12.
See, we have done so much to make up for the gap between the startup page and fluent rendering. There is also an optimization called NormalTheme.
When we set an Activity's windowBackground, it will actually have an impact on the performance. Therefore, the official added a NormalTheme configuration. After startup, set the theme to the NormalTheme configured by the developer.
By configuring NormalTheme, switchLaunchThemeForNormalTheme() will be executed first when the Activity is started; Method to switch the theme from LaunchTheme to NormalTheme.
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
After the configuration, it looks like the following. The previous analysis is actually to tell you where you can find the corresponding point if there is a problem.
<activity android:name=".MyActivity" android:theme="@style/LaunchTheme" // ... > <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
After 2.5
After talking about so much, after fluent 2.5, providesplayscreen and Io flutter. embedding. android. Splashscreendrawable was abandoned. Are you surprised, surprised, surprised, and happy?
Flutter official said: flutter will now automatically maintain the effective display of Android startup page until flutter finishes drawing the first frame.
Through the source code, you will find that when you set splashScreen, you will see a log warning:
if (splashScreen != null) { Log.w( TAG, "A splash screen was provided to Flutter, but this is deprecated. See" + " flutter.dev/go/android-splash-migration for migration steps."); FlutterSplashView flutterSplashView = new FlutterSplashView(host.getContext()); flutterSplashView.setId(ViewUtils.generateViewId(FLUTTER_SPLASH_VIEW_FALLBACK_ID)); flutterSplashView.displayFlutterViewWithSplash(flutterView, splashScreen); return flutterSplashView; }
Why is it abandoned? In fact, this proposal is github.com/flutter/flu... On this issue, and then through github.com/flutter/eng... This pr completes the adjustment.
The original design is complicated. It is more accurate to use OnPreDrawListener, and there is no need to make other compatibility for the later Andorid12 startup support. It only needs to add interface switches to classes such as fluteractivity.
That is, after 2.5, the Flutter is used ViewTreeObserver.OnPreDrawListener To delay until the first frame of the Flutter is loaded.
Why default? Because this behavior is in the FlutterActivity, it is in getrendermode() = = RenderMode Surface will be called, and RenderMode is concerned with BackgroundMode.
By default, BackgroundMode is BackgroundMode Opaque, so it's rendermode surface
Therefore, after version 2.5, a delayFirstAndroidViewDraw operation will be executed after the FlutterView is created inside the FlutterActivity.
private void delayFirstAndroidViewDraw(final FlutterView flutterView) { if (this.host.getRenderMode() != RenderMode.surface) { throw new IllegalArgumentException("Cannot delay the first Android view draw when the render mode is not set to derMode.surface`."); } else { if (this.activePreDrawListener != null) { flutterView.getViewTreeObserver().removeOnPreDrawListener(this.activePreDrawListener); } this.activePreDrawListener = new OnPreDrawListener() { public boolean onPreDraw() { if (FlutterActivityAndFragmentDelegate.this.isFlutterUiDisplayed && terActivityAndFragmentDelegate.this.activePreDrawListener != null) { flutterView.getViewTreeObserver().removeOnPreDrawListener(this); FlutterActivityAndFragmentDelegate.this.activePreDrawListener = null; } return FlutterActivityAndFragmentDelegate.this.isFlutterUiDisplayed; } }; flutterView.getViewTreeObserver().addOnPreDrawListener(this.activePreDrawListener); } }
Here, we mainly pay attention to one parameter: isFlutterUiDisplayed.
isFlutterUiDisplayed will be set to true when the display of Flutter is completed.
Therefore, the onPreDraw of FlutterView will always return false before the execution of Flutter is completed, which is also a new adjustment of the adaptation startup page after the start of Flutter 2.5.
last
After reading so much, we can probably see that the promotion of open source projects is not plain sailing. There is nothing that is the optimal solution at the beginning, but the current version is obtained after many attempts and exchanges. In fact, there are countless experiences like this in open source projects:
####Relevant video recommendations:
[advanced Android tutorial] - Handler source code analysis for Framework interview
This article is transferred from https://juejin.cn/post/7038516159318065165 , in case of infringement, please contact to delete.