热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

关于android:Flutter-启动页的前世今生适配历程

APP启动页在国内是最常见也是必备的场景,其中启动页在iOS上算是强制性的要求,其实配置启动页挺简略,因为在Flutter里当初只须要:

APP 启动页在国内是最常见也是必备的场景,其中启动页在 iOS 上算是强制性的要求,其实配置启动页挺简略,因为在 Flutter 里当初只须要:

  • iOS 配置 LaunchScreen.storyboard
  • Android 配置 windowBackground;

个别只有配置无误并且图片尺寸匹配,基本上就不会有什么问题,那既然这样,还有什么须要适配的呢?

事实上大部分时候 iOS 是不会有什么问题,因为 LaunchScreen.storyboard 的流程本就是 iOS 官网用来做利用启动的过渡;而对于 Andorid 而言,直到 12 之前 windowBackground 这种其实只能算“民间”野路子,所以对于 Andorid 来说,这其中就波及到一个点:

android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />

所以上面次要介绍 Flutter 在 Android 上为了这个启动图做了哪些骚操作~

一、远古期间

在曾经遗记版本的“远古期间”FlutterActivity 还在 io.flutter.app.FlutterActivity 门路下的时候,那时启动页的逻辑绝对简略,次要是通过 App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 来进行判断。

 

FlutterActivity 外部 FlutterView 被创立的时候,会通过读取 meta-data 来判断是否须要应用 createLaunchView 逻辑

  • 1、获取以后主题的 android.R.attr.windowBackground 这个 Drawable
  • 2、创立一个 LaunchView 并加载这个 Drawable
  • 3、将这个 LaunchView 增加到 ActivityContentView
  • 4、在Flutter onFirstFrame 时将这个 LaunchView 移除;

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

是不是很简略,那就会有人疑难为什么要这样做?我间接配置 Activityandroid:windowBackground 不就实现了吗?

这就是下面提到的时间差问题,因为启动页到 Flutter 渲染完第一帧画面两头,会呈现概率呈现黑屏的状况,所以才须要这个行为来实现过渡

2.5 之前

经验了“远古时代”之后,FlutterActivity 来到了 io.flutter.embedding.android.FlutterActivity, 在到 2.5 版本公布之前,Flutter 又针对这个启动过程做了不少调整和优化,其中次要就是 SplashScreen

自从开始进入embedding 阶段后,FlutterActivity 次要用于实现了一个叫 Hostinterface,其中和咱们有关系的就是 provideSplashScreen

默认状况下它会从 AndroidManifest 文件里是否配置了 SplashScreenDrawable 来进行判断

 

默认状况下当 AndroidManifest 文件里配置了 SplashScreenDrawable,那么这个 Drawable 就会在 FlutterActivity 创立 FlutterView 时被构建成 DrawableSplashScreen

DrawableSplashScreen 其实就是一个实现了 io.flutter.embedding.android.SplashScreen 接口的类,它的作用就是:

在 Activity 创立 FlutterView 的时候,将 AndroidManifest 里配置的 SplashScreenDrawable 加载成 splashScreenView(ImageView);,并提供 transitionToFlutter 办法用于执行。

之后 FlutterActivity 内会创立出 FlutterSplashView,它是个 FrameLayout。

FlutterSplashViewFlutterViewImageView 增加到一起, 而后通过 transitionToFlutter 的办法来执行动画,最初动画完结时通过 onTransitionComplete 移除 splashScreenView

所以整体逻辑就是:

  • 依据 meta 创立 DrawableSplashScreen
  • FlutterSplashView 先增加了 FlutterView
  • FlutterSplashView 先增加了 splashScreenView 这个 ImageView;
  • 最初在 addOnFirstFrameRenderedListener 回调里执行 transitionToFlutter 去触发 animate ,并且移除 splashScreenView

当然这里也是分状态:

  • 等引擎加载实现之后再执行 transitionToFlutter
  • 引擎曾经加载实现了马上执行 transitionToFlutter
  • 以后的 FlutterView 还没有被增加到引擎,期待增加到引擎之后再 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();
   }

当然这个阶段的 FlutterActivity 也能够通过 override provideSplashScreen 办法来自定义 SplashScreen

留神这里的 SplashScreen 不等于 Android 12 的 SplashScreen。

看到没有,做了这么多其实也就是为了补救启动页和 Flutter 渲染之间,另外还有一个优化,叫 NormalTheme

当咱们设置了一个 ActivitywindowBackground 之后,其实对性能还是多多少少会有影响,所以官网就减少了一个 NormalTheme 的配置,在启动实现之后将主题设置为开发者本人配置的 NormalTheme

通过该配置 NormalTheme ,在 Activity 启动时,就会首先执行 switchLaunchThemeForNormalTheme(); 办法将主题从 LaunchTheme 切换到 NormalTheme

    

大略配置完就是如下样子,后面剖析那么多其实就是为了通知你,如果呈现问题了,你能够从哪个中央去找到对应的点


    
    
        
        
    
2.5 之后

讲了那么多,Flutter 2.5 之后 provideSplashScreenio.flutter.embedding.android.SplashScreenDrawable 就被弃用了,惊不喜惊喜,意不意外,开不开心

Flutter 官网说: Flutter 当初会主动维持着 Android 启动页面的效显示,直到 Flutter 绘制完第一帧后才隐没。

通过源码你会发现,当你设置了 splashScreen 的时候,会看到一个 log 正告:

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

为什么会弃用? 其实这个提议是在 github.com/flutter/flu… 这个 issue 上,而后通过 github.com/flutter/eng… 这个 pr 实现调整。

大略意思就是:本来的设计搞简单了,用 OnPreDrawListener 更精准,而且不须要为了前面 Andorid12 的启动反对做其余兼容,只须要给 FlutterActivity 等类减少接口开关即可

也就是2.5之后 Flutter 应用 ViewTreeObserver.OnPreDrawListener 来实现提早直到加载出 Flutter 的第一帧。

为什么说默认状况?因为这个行为在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才会被调用,而 RenderMode 又和 BackgroundMode 有关怀

默认状况下 BackgroundMode 就是 BackgroundMode.opaque ,所以就是 RenderMode.surface

所以在 2.5 版本后, FlutterActivity 外部创立完 FlutterView 后就会执行一个 delayFirstAndroidViewDraw 的操作。


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

这里次要留神一个参数:isFlutterUiDisplayed

当 Flutter 被实现展现的时候,isFlutterUiDisplayed 就会被设置为 true。

所以当 Flutter 没有执行实现之前,FlutterViewonPreDraw 就会始终返回 false,这也是 Flutter 2.5 开始之后适配启动页的新调整。

最初

看了这么多,大略能够看到其实开源我的项目的推动并不是一帆风顺的,没有什么是一开始就是最优解,而是通过多方尝试和交换,才有了当初的版本,事实上开源我的项目里,相似这样的经验不可胜数:

#### 相干视频举荐:

【2021最新版】Android studio装置教程+Android(安卓)零基础教程视频(适宜Android 0根底,Android初学入门)含音视频_哔哩哔哩_bilibili

【 Android进阶教程】——Framework面试必问的Handler源码解析_哔哩哔哩_bilibili

Android进阶零碎学习——Gradle入门与我的项目实战_哔哩哔哩_bilibili

Android架构设计原理与实战——Jetpack联合MVP组合利用开发一个优良的APP!_哔哩哔哩_bilibili

本文转自 https://juejin.cn/post/7038516159318065165,如有侵权,请分割删除。


推荐阅读
author-avatar
手机用户2602908893
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有