Запуск страницы в Китае является наиболее распространенным и обязательным сценарием. В частности, запуск страницы считается обязательным требованием для iOS. На самом деле, конфигурация запуска страницы довольно проста, так как в Flutter требуется всего лишь:
LaunchScreen.storyboard
;windowBackground
.Обычно, если конфигурация выполнена правильно и размеры изображений совпадают, то проблем возникнуть не должно. Но если это так, что ещё нужно адаптировать?
На самом деле большинство случаев для iOS проблем не вызывает, так как процесс LaunchScreen.storyboard
предназначен для iOS официальной системы перехода приложения; а для Android до версии 12 windowBackground
можно назвать "нелегальным" способом, поэтому для Android существует один важный момент:
[Первый кадр Flutter] + [время необходимое для перехода от растра к основному потоку и получения следующего Android vsync] = [Первый кадр Android]
.
Поэтому ниже мы рассмотрим некоторые хитрые приемы, используемые Flutter для этого запускающего изображения на Android.
В древних временах, когда забываешь версию, FlutterActivity
находился по пути io.flutter.app.FlutterActivity
, тогда логика запуска была относительно простой, основывалась она на том, был ли указан SplashScreenUntilFirstFrame
в манифесте приложения AndroidManifest
.```xml
**Когда `FlutterView` создавался внутри `FlutterActivity`, он проверял метаданные для использования логики `createLaunchView`:**
- 1. Получение текущей темы `android.R.attr.windowBackground` этого `Drawable`.
- 2. Создание `LaunchView` и загрузка этого `Drawable`.
- 3. Добавление этого `LaunchView` в контент активности.
- 4. Удаление этого `LaunchView` после первого кадра Flutter.

```java
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);
}
}
После "древней эпохи" FlutterActivity
переместился в io.flutter.embedding.android.FlutterActivity
. До выпуска версии 2.5 Flutter провел значительную работу над этим процессом старта, включая оптимизацию и улучшение SplashScreen
.
С момента перехода в стадию embedding
, FlutterActivity
был создан с целью реализации интерфейса Host
, где ключевой метод для нас — это provideSplashScreen
.По умолчанию он проверяет наличие метаданных SplashScreenDrawable
в файле AndroidManifest.xml
.
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"/>
По умолчанию, если в файле AndroidManifest.xml
указано SplashScreenDrawable
, то этот drawable будет использоваться при создании FlutterView
в FlutterActivity
для создания объекта DrawableSplashScreen
.
Класс DrawableSplashScreen
реализует интерфейс io.flutter.embedding.android.SplashScreen
, его задача заключается в следующем:
При создании
FlutterView
вActivity
, загружаетSplashScreenDrawable
из файлаAndroidManifest.xml
какsplashScreenView
(ImageView) и предоставляет методtransitionToFlutter
для выполнения анимации.
Затем внутри FlutterActivity
создается FlutterSplashView
, который является FrameLayout
.
FlutterSplashView
объединяет FlutterView
и ImageView
вместе, после чего выполняется анимация через метод transitionToFlutter
, и в конце, когда анимация завершена, splashScreenView
удаляется через метод onTransitionComplete
.
Итак, общая логика такова:
DrawableSplashScreen
;FlutterSplashView
сначала добавляется FlutterView
;FlutterSplashView
добавляется splashScreenView
как ImageView
;addOnFirstFrameRenderedListener
вызывается метод transitionToFlutter
для запуска анимации и удаления splashScreenView
.Конечно, вот перевод:
Также здесь учитываются различные состояния:- После завершения загрузки движка выполните transitionToFlutter
;
Движок уже загружен, сразу выполняйте transitionToFlutter
;
Текущий FlutterView
ещё не был добавлен в движок, ждём его добавления перед выполнением transitionToFlutter
.```java
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, "Показываю экран Splash."); this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState); this.addView(this.splashScreenView); flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener); } else if (this.isSplashScreenTransitionNeededNow()) { Log.v(TAG, "Показываю немедленный переход Splash к Flutter из-за ранее прерванного перехода."); this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState); this.addView(this.splashScreenView); this.transitionToFlutter(); } else if (!flutterView.isAttachedToFlutterEngine()) { Log.v(TAG, "FlutterView еще не прикреплен к FlutterEngine. Ничего не показывать до тех пор, пока FlutterEngine не будет прикреплен."); 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(); }
---
**Конечно, на данном этапе можно также с помощью переопределения метода `provideSplashScreen` настроить собственный SplashScreen для FlutterActivity.**
> Обратите внимание, что этот SplashScreen не совпадает со SplashScreen в Android 12.
Как видите, все это было сделано ради того, чтобы заполнить промежуток между экраном запуска и отрисовкой Flutter, **а ещё один оптимизационный подход называется `NormalTheme`.**
> Когда вы задаёте атрибут `windowBackground` для Activity, это может незначительно влиять на производительность, поэтому официальные разработчики добавили конфигурацию `NormalTheme`, которая **позволяет после завершения запуска установить тему, которую разработчик самостоятельно настроил как `NormalTheme`.**
С этой конфигурацией `NormalTheme`, при запуске Activity, будет выполнена первая функция `switchLaunchThemeForNormalTheme();`, которая переключает тему с `LaunchTheme` на `NormalTheme`.
```xml
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
При правильной настройке конфигурация будет выглядеть примерно так, все эти объяснения были даны для того, чтобы помочь вам найти проблему, если она возникнет.
<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>
Рассказывая столько, Flutter 2.5 и выше废弃了provideSplashScreen
和io.flutter.embedding.android.SplashScreenDrawable
方法,真是令人惊讶、欣喜若狂!
Перевод:
Flutter 2.5 и выше废弃了
provideSplashScreen
和io.flutter.embedding.android.SplashScreenDrawable
методы, это действительно удивляет и радует!
Таким образом, окончательный текст будет следующим:
Flutter 2.5 и выше废弃了
provideSplashScreen
和io.flutter.embedding.android.SplashScreenDrawable
方法,这确实令人惊讶且欣喜若狂!> Официальные разработчики Flutter сообщают, что теперь Flutter автоматически поддерживает отображение Android Splash Screen до тех пор, пока Flutter не отрендерит первую фрейм.
Изучив исходный код, вы заметите, что когда вы задаете splashScreen
, вы получите логическое предупреждение:
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);
}
вернуть flutterSplashView;
}
Почему было решено отказаться от этого? На самом деле это предложение рассматривалось в issue https://github.com/flutter/flutter/issues/85292, а затем реализовано через pull request https://github.com/flutter/engine/pull/27645.
Основная причина заключается в том, что ранее использовалась более сложная реализация, которая теперь заменена на использование OnPreDrawListener
, что позволяет более точно контролировать момент отрисовки первого фрейма Flutter. Это также упрощает процесс адаптации к новым версиям Android, таким как Android 12, поскольку требуется лишь добавление соответствующего интерфейса в классах типа FlutterActivity
.
С версии Flutter 2.5 используется метод ViewTreeObserver.OnPreDrawListener
, чтобы обеспечить задержку до момента отображения первого фрейма Flutter.Здесь важно обратить внимание на параметр isFlutterUiDisplayed
. Когда отображение Flutter завершено, isFlutterUiDisplayed
устанавливается в true.
Поэтому до тех пор, пока Flutter не завершит выполнение, метод onPreDraw
класса FlutterView
будет продолжать возвращать false, что является новым изменением с Flutter 2.5 для адаптации стартового экрана.
Просмотрев всё это, можно заметить, что продвижение открытых проектов не всегда проходит гладко. Ничто не начинается сразу как идеальное решение; вместо этого это происходит после множества попыток и обсуждений. На самом деле, в открытых проектах таких случаев множество:
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )