1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-LA.md 15 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

Запуск страницы в Китае является наиболее распространенным и обязательным сценарием. В частности, запуск страницы считается обязательным требованием для iOS. На самом деле, конфигурация запуска страницы довольно проста, так как в Flutter требуется всего лишь:

  • Для iOS — конфигурирование LaunchScreen.storyboard;
  • Для Android — конфигурирование windowBackground.

Обычно, если конфигурация выполнена правильно и размеры изображений совпадают, то проблем возникнуть не должно. Но если это так, что ещё нужно адаптировать?

На самом деле большинство случаев для iOS проблем не вызывает, так как процесс LaunchScreen.storyboard предназначен для iOS официальной системы перехода приложения; а для Android до версии 12 windowBackground можно назвать "нелегальным" способом, поэтому для Android существует один важный момент:

[Первый кадр Flutter] + [время необходимое для перехода от растра к основному потоку и получения следующего Android vsync] = [Первый кадр Android].

Поэтому ниже мы рассмотрим некоторые хитрые приемы, используемые Flutter для этого запускающего изображения на Android.

1. Древние времена

В древних временах, когда забываешь версию, 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.

![image](http://img.cdn.guoshuyu.cn/20211223_Flutter-LA/image1)

```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);
    }
}

2.5 Версия и ранее

После "древней эпохи" 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.

image

Класс 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>

После версии 2.5

Рассказывая столько, Flutter 2.5 и выше废弃了provideSplashScreenio.flutter.embedding.android.SplashScreenDrawable方法,真是令人惊讶、欣喜若狂!

Перевод:

Flutter 2.5 и выше废弃了provideSplashScreenio.flutter.embedding.android.SplashScreenDrawable методы, это действительно удивляет и радует!

Таким образом, окончательный текст будет следующим:

Flutter 2.5 и выше废弃了provideSplashScreenio.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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master