Catalog Introduction

  • 01.Android hosts flutter container
  • 02. Outdated NA jump flutter scheme
  • 03. Upgraded version NA jump Flutter processing
  • 04. How to deal with NA jump flutter transfer parameters
  • 05. Thinking about the analysis of several problems encountered
  • 06. Crash when the Flutter page is closed
  • 07.Android introduces the essence of flutter
  • 08.Flutter startup loading process and optimization

00.Recommended

01.Android hosts flutter container

  • How to host flutter pages in Android

    • The first case: Get a container from Android, open a new page, and load a new flutter page.
    • The second case: Get a container from Android, and load a flutter page in the NA page. [One page, part of it is NA, and part of it is Flutter]
  • How to embed pages written by Flutter into Activity

    • There are two official methods: through FlutterView and FlutterFragment.

02. Outdated NA jump flutter scheme

2.1 Use FlutterView

  • NA add FlutterView

    • Create an Activity in NA, create FlutterView in onCreate and add it to the layout.
    • The Flutter.createView() method returns a FlutterView, which inherits from View, and we can treat it as an ordinary View.
    • The third parameter of the Flutter.createView() method is passed in the "yc_route" string, which represents the route name, which determines the Widget to be displayed in Flutter.

      private void addFlutterView() {
      // 通过FlutterView引入Flutter编写的页面
      // Flutter.createView()方法返回的是一个FlutterView,它继承自View,我们可以把它当做一个普通的View
      // Flutter.createView()方法的第三个参数传入了"yc_route"字符串,表示路由名称,它确定了Flutter中要显示的Widget
      flutterView = Flutter.createView(this, getLifecycle(), INIT_ROUTE);
      FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
              FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
      //添加到布局中
      frameLayout.addView(flutterView, layoutParams);
      //addContentView(flutterView, layout);
      }
  • Flutter add page

    • In the runApp() method, the route name passed in in the Flutter.createView() method can be obtained through window.defaultRouteName, which is "yc_route",
    • After that, a _widgetForRoute() method was written to display the corresponding Widget according to the incoming route string.

      import 'dart:ui';
      import 'package:flutter/material.dart';
      
      void main() => runApp(_widgetForRoute(window.defaultRouteName));
      
      Widget _widgetForRoute(String route) {
      switch (route) {
      case 'yc_route':
        return  MyHomePage(title: '匹配到了,这个是flutter页面');
      }
      }
  • Jump to the black screen of the activity where flutter is located

    • The debug package is more obvious, but the release loads quickly. You can provide a loading when you enter the Flutter page.

2.2 Use FlutterFragment

  • NA add FlutterView

    • Create an Activity in NA, create FlutterFragment in onCreate and add it to the layout.
    • The parameters passed in by the Flutter.createFragment() method also represent the route name, which is used to determine the Widget to be displayed by Flutter, and return a FlutterFragment. This class inherits from Fragment. Just add the Fragment to the Activity.

      private void addFlutterFragment(){
      // 通过FlutterFragment引入Flutter编写的页面
      FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
      // Flutter.createFragment()方法传入的参数同样表示路由名称,用于确定Flutter要显示的Widget
      // 返回一个FlutterFragment,该类继承自Fragment,将该Fragment添加到Activity中就可以了。
      FlutterFragment flutterFragment = Flutter.createFragment(INIT_ROUTE);
      tx.replace(R.id.rl_flutter, flutterFragment);
      tx.commit();
      }
  • Flutter add page, this is the same as above

2.3 Issues that need attention

  • Flutter version upgrade compatibility issue

    • Due to the update of the Flutter version, some APIs in the content introduced above have been abandoned. I simply checked and learned that this error was caused by the io.flutter.facade package being abandoned in Flutter version 1.12. The two apis Flutter.createView and Flutter.createFragment could not be found. They are not used anymore...
  • How to add parameters to NA jump flutter

    • NA, this transfer parameter only needs to splice the parameters after the route.
    • Flutter, this receiving parameter only needs to parse the parameter.
    • As we will talk about in the use case of the upgraded version of FlutterView below, you can continue to look down...

03. Upgraded version NA jump Flutter processing

3.1 Use the new version of FlutterView

  • A brief description of the new version

    • Introduce the Flutter page through FlutterView. Previously, we created a FlutterView through the createView() method of the Flutter class in the io.flutter.facade package, and then added it to the layout of the Activity. However, due to the obsolescence of the io.flutter.facade package, this method It is no longer available.
    • The official document states that there is currently no convenient API to introduce Flutter at the View level, so if possible, we should avoid using FlutterView, but it is also feasible to introduce Flutter pages through FlutterView.
    • It should be noted that the FlutterView here is located in the io.flutter.embedding.android package, which is different from the FlutterView (located in the io.flutter.view package) we created earlier.
  • NA add FlutterView

    • Create an Activity in NA, create FlutterView in onCreate and add it to the layout.
    • Call the attachToFlutterEngine() method of FlutterView. The function of this method is to display the UI page written by Flutter in FlutterView. Notice that a flutterEngine parameter is passed in here. What is it? The type of flutterEngine is FlutterEngine, which literally means the Flutter engine. It is responsible for executing Dart code on the Android side and displaying the UI written by Flutter to the FlutterView container.

      private void addFlutterView() {
      flutterEngine = new FlutterEngine(this);
      binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger();
      flutterEngine.getNavigationChannel().setInitialRoute("yc");
      flutterEngine.getDartExecutor().executeDartEntrypoint(
              DartExecutor.DartEntrypoint.createDefault()
      );
      // 通过FlutterView引入Flutter编写的页面
      // 这里的FlutterView位于io.flutter.embedding.android包中
      // 和此前我们所创建的FlutterView(位于io.flutter.view包中)是不一样的。
      // 通过查看FlutterView的源码可以发现它继承自FrameLayout,因此像一个普通的View那样添加就可以了。
      flutterView = new FlutterView(this);
      FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
              ViewGroup.LayoutParams.MATCH_PARENT,
              ViewGroup.LayoutParams.MATCH_PARENT);
      rlFlutter.addView(flutterView, lp);
      
      //flutterEngine.getNavigationChannel().setInitialRoute("yc");
      
      // 关键代码,将Flutter页面显示到FlutterView中
      // 这个方法的作用就是将Flutter编写的UI页面显示到FlutterView中
      // flutterEngine的类型为FlutterEngine,字面意思就是Flutter引擎
      // 它负责在Android端执行Dart代码,将Flutter编写的UI显示到FlutterView/FlutterActivity/FlutterFragment中。
      flutterView.attachToFlutterEngine(flutterEngine);
      
      // FlutterEngine加载的路由名称为"/",我们可以通过下面的代码指定初始路由名称
      // 传参的情况没有变化,直接在路由名称后面拼接参数就可以
      // todo 放在这里不生效,思考为什么
      // flutterEngine.getNavigationChannel().setInitialRoute("yc");
      }
  • Flutter add page

    • In the runApp() method, the route name passed in in the Flutter.createView() method can be obtained through window.defaultRouteName, which is "yc_route",
    • After that, a _widgetForRoute() method was written to display the corresponding Widget according to the incoming route string.

      import 'dart:ui';
      import 'package:flutter/material.dart';
      
      void main() => runApp(_widgetForRoute(window.defaultRouteName));
      
      Widget _widgetForRoute(String route) {
      switch (route) {
      case 'yc_route':
        return  MyHomePage(title: '匹配到了,这个是flutter页面');
      }
      }

3.2 Use the new version of FlutterFragment

  • There are several ways to add NA

    • FlutterFragment.createDefault()

      • FlutterFragment is created through FlutterFragment.createDefault(). The route name displayed by the created Fragment is "/". If we need to specify other route names, we cannot use this method.
    • FlutterFragment.withNewEngine()

      • Get the NewEngineFragmentBuilder object through FlutterFragment.withNewEngine(), use the builder mode to construct the FlutterFragment object, and specify the initial route name through the initialRoute() method.
      • The withNewEngine() method used can also be seen from the name that each time a new FlutterEngine object is created to display the Flutter UI, but from the official documentation, it can be understood that each FlutterEngine object needs a warm before displaying the Flutter UI. -up (simply understood as warm-up) period, which will cause the screen to appear blank for a short time. The solution is to create and start the FlutterEngine in advance, complete the warm-up process, and then cache the FlutterEngine, and then use the FlutterEngine to display Flutter UI.
    • FlutterFragment.withCachedEngine

      • The executed FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine) is to cache the FlutterEngine, and the "my_engine_id" passed in here is equivalent to the cache name.
      • Then call FlutterFragment.withCachedEngine("my_engine_id").build(); to get the cached FlutterFragment object
  • NA add FlutterFragment

    • Create an Activity in NA, create FlutterFragment in onCreate and add it to the layout.
    • The parameters passed in by the Flutter.createFragment() method also represent the route name, which is used to determine the Widget to be displayed by Flutter, and return a FlutterFragment. This class inherits from Fragment. Just add the Fragment to the Activity.

      private void addFlutterView() {
      // 通过FlutterFragment引入Flutter编写的页面
      // 通过FlutterFragment.createDefault()创建出FlutterFragment
      // 需要注意这里的FlutterFragment位于io.flutter.embedding.android包中
      //FlutterFragment flutterFragment = FlutterFragment.createDefault();
      
      // 通过FlutterFragment.withNewEngine()获取到NewEngineFragmentBuilder对象
      FlutterFragment.NewEngineFragmentBuilder fragmentBuilder = FlutterFragment.withNewEngine();
      // 使用建造者模式构造出FlutterFragment对象,可以通过initialRoute()方法指定初始路由名称。
      // 传递参数只需要在路由名称后面进行拼接。
      FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute("yc");
      FlutterFragment flutterFragment = initialRoute.build();
      
      getSupportFragmentManager()
              .beginTransaction()
              .add(R.id.rl_flutter, flutterFragment)
              .commit();
      
      
      // 存在的问题
      // 使用的withNewEngine()方法从名称上也能看出每次都是创建一个新的FlutterEngine对象来显示Flutter UI,
      // 但是从官方文档中我们可以了解到每个FlutterEngine对象在显示出Flutter UI之前
      // 是需要一个warm-up(不知道能不能翻译为预热)期的,这会导致屏幕呈现短暂的空白,
      // 解决方式就是预先创建并启动FlutterEngine,完成warm-up过程,然后将这个FlutterEngine缓存起来,
      // 之后使用这个FlutterEngine来显示出Flutter UI。
      // 解决方案看:FlutterFragmentCachedActivity
      
      
      // 如何获取到FlutterEngine对象呢?FlutterFragment中定义了一个getFlutterEngine()方法,
      // 从方法名来看大概就是获取FlutterEngine对象。
      // 尝试过创建MethodChannel时传入flutterFragment.getFlutterEngine().getDartExecutor(),
      // 运行后会直接抛出空指针异常,异常产生的位置在FlutterFragment的getFlutterEngine()方法中
      // 错误原因是这里的delegate为null,全局搜索一下,发现在FlutterFragment的onAttach()方法中会对delegate赋值,也就是说明此时没有执行onAttach()方法。
      // 猜测这就是由于上面提到过的FlutterEngine的warm-up机制,这是一个耗时过程,
      // 因此FlutterFragment并不会立刻执行onAttach()方法,导致我们在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法会抛出异常。
      // todo 调用下面这句话会空指针崩溃
      // FlutterEngine flutterEngine = flutterFragment.getFlutterEngine();
      }
  • Flutter add page

    • Same as above

3.3 Use the new version of FlutterActivity

  • Native introduction of Flutter pages

    • Use FlutterActivity, where FlutterActivity is also located in the io.flutter.embedding.android package.
  • First add code to the manifest file

    <activity
        android:name="io.flutter.embedding.android.FlutterActivity"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
        android:hardwareAccelerated="true"
        android:theme="@style/AppTheme"
        android:windowSoftInputMode="adjustResize" />
  • Start this Activity directly, the code is as follows

    /**
     * 和介绍的创建FlutterFragment的三种方式是对应的
     *
     * FlutterActivity显示的Flutter路由是在创建Intent对象时指定的,
     * 优点就是使用起来更简单,缺点就是不够灵活,
     * 无法像FlutterView/FlutterFragment那样只是作为原生页面中的一部分展示,
     * 因此这种方式更适合整个页面都是由Flutter编写的场景。
     */
    private void test(){
        // 方式一、FlutterActivity显示的路由名称为"/",不可设置
        /*startActivity(
                FlutterActivity.createDefaultIntent(this)
        );*/
    
        // 方式二、FlutterActivity显示的路由名称可设置,每次都创建一个新的FlutterEngine对象
        startActivity(
                FlutterActivity
                        .withNewEngine()
                        .initialRoute("yc")
                        .build(this)
        );
    
        // 方式三、FlutterActivity显示的路由名称可设置,使用缓存好的FlutterEngine对象
        /*startActivity(
                FlutterActivity
                        .withCachedEngine("my_engine_id")
                        .build(this)
        );*/
    }
  • Features in this way

    • This method does not require us to create an Activity ourselves. The Flutter route displayed by FlutterActivity is specified when the Intent object is created. The advantage is that it is easier to use, but the disadvantage is that it is not flexible enough and cannot be used as a part of the native page like FlutterView/FlutterFragment. Display, so this method is more suitable for scenarios where the entire page is written by Flutter.

3.4 Supplementary explanation

  • I updated the Flutter version to 1.17, and found that the FlutterView cannot be displayed after the above code runs. Why is this?

    • After comparing with the official example flutter_view, it was found that the following code was missing:

      @Override
      protected void onResume() {
      super.onResume();
      // flutterEngine.getLifecycleChannel()获取到的是一个LifecycleChannel对象,类比于MethodChannel,
      // 作用大概就是将Flutter和原生端的生命周期相互联系起来。
      flutterEngine.getLifecycleChannel().appIsResumed();
      }
      
      @Override
      protected void onPause() {
      super.onPause();
      flutterEngine.getLifecycleChannel().appIsInactive();
      }
      
      @Override
      protected void onStop() {
      super.onStop();
      flutterEngine.getLifecycleChannel().appIsPaused();
      }
  • May be related to life cycle

    • What flutterEngine.getLifecycleChannel() gets is a LifecycleChannel object, which is analogous to MethodChannel, whose role is probably to connect the life cycle of Flutter and the native end.
    • Here, the appIsResumed(), appIsInactive() and appIsPaused() methods of LifecycleChannel are called in the onResume(), onPause(), and onStop() methods, respectively, to synchronize the life cycle of the Flutter end and the native end. After adding the above code, FlutterView can be displayed normally.
  • Why to add in later versions

    • It may be that there have been some changes in the rendering mechanism of FlutterVIew, which will only be displayed when the notification sent in the corresponding life cycle method of the native terminal is received. The specific principle is to compare the current and previous source code.

04. How to deal with NA jump flutter transfer parameters

4.1 How does NA pass parameters to Flutter?

  • If you need to pass parameters when the page jumps, how do you get the parameters in the native code in the Flutter code? In fact, it is very simple, just need to splice the parameters after the route.
  • Take the way of creating FlutterView as an example.

    NavigationChannel navigationChannel = flutterEngine.getNavigationChannel();
    String route = "yc?{\"name\":\"杨充\"}";
    navigationChannel.setInitialRoute(route);
  • Take the way of creating FlutterFragment as an example

    FlutterFragment.NewEngineFragmentBuilder fragmentBuilder = FlutterFragment.withNewEngine();
    // 使用建造者模式构造出FlutterFragment对象,可以通过initialRoute()方法指定初始路由名称。
    // 传递参数只需要在路由名称后面进行拼接。
    String route = "yc?{\"author\":\"杨充\"}";
    FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute(route);
    FlutterFragment flutterFragment = initialRoute.build();

4.2 Precautions for Passing Parameters

  • Separate the route name and the parameters with "?", just like the url in the browser, the parameters are passed in Json format, the reason is to facilitate the parsing of the Flutter side, and for some complex data, such as custom objects, use Json Serialization is also very easy to implement.

4.3 Flutter receives and passes parameters

  • At this time, what the Flutter side obtains through window.defaultRouteName is the route name + parameters. We need to separate the route name and the parameters, which is just pure string processing.

      Widget _widgetForRoute() {
        //var route = window.defaultRouteName;
        Map<String, dynamic> router = parseRouter();
        var route = router["route"];
        switch (route) {
          case 'yc':
            return AboutMePage(title: '匹配到了,这个是flutter页面',params : router);
        }
      }
    
      Map<String, dynamic> parseRouter(){
        String url = window.defaultRouteName;
        // route名称,路由path路径名称
        String route = url.indexOf('?') == -1 ? url : url.substring(0, url.indexOf('?'));
        // 参数Json字符串
        String paramsJson = url.indexOf('?') == -1 ? '{}' : url.substring(url.indexOf('?') + 1);
        // 解析参数
        Map<String, dynamic> params = json.decode(paramsJson);
        params["route"] = route;
        return params;
      }
  • Separate the route name from the parameter by "?", and parse the Json string corresponding to the parameter into a Map object. You need to import the dart:convert package.

05. Thinking about the analysis of several problems encountered

5.1 SetInitialRoute take effect

  • flutterEngine.getNavigationChannel().setInitialRoute("yc") take effect

    //第一种是生效的
    private void addFlutterView() {
        flutterEngine = new FlutterEngine(this);
        binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger();
        flutterEngine.getNavigationChannel().setInitialRoute("yc");
        flutterEngine.getDartExecutor().executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
        );
        flutterView = new FlutterView(this);
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        rlFlutter.addView(flutterView, lp);
        flutterView.attachToFlutterEngine(flutterEngine);
    }
    
    //第二种是不生效的
    private void addFlutterView() {
        flutterEngine = new FlutterEngine(this);
        binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger();
        flutterEngine.getDartExecutor().executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
        );
        flutterView = new FlutterView(this);
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        rlFlutter.addView(flutterView, lp);
    
        // todo 放在这里不生效,思考为什么
        flutterEngine.getNavigationChannel().setInitialRoute("yc");
        flutterView.attachToFlutterEngine(flutterEngine);
    
        // todo 放在这里不生效,思考为什么
        // flutterEngine.getNavigationChannel().setInitialRoute("yc");
    }

5.2 flutterFragment.getFlutterEngine() null pointer

  • Use scenario analysis

    private void createChannel() {
        // todo 调用下面这句话会空指针崩溃
        FlutterEngine flutterEngine = flutterFragment.getFlutterEngine();
        BinaryMessenger binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger();
        nativeChannel = new MethodChannel(binaryMessenger, METHOD_CHANNEL, StandardMethodCodec.INSTANCE);
    }
    
    //源码
    @Nullable
    public FlutterEngine getFlutterEngine() {
        return delegate.getFlutterEngine();
    }
  • The reason for the error is that the delegate here is null

    • Looking at the source code, I found that the delegate is assigned in the onAttach() method of FlutterFragment, which means that the onAttach() method is not executed at this time.
  • problem analysis

    • The warm-up mechanism of FlutterEngine is a time-consuming process, so FlutterFragment does not immediately execute the onAttach() method, which causes us to directly use the getFlutterEngine() method of FlutterFragment in the onCreate() method of the Activity to throw an exception.
  • How to solve the problem

    • To solve the problem, you have to wait until FlutterFragment finishes executing the onAttach() method before calling getFlutterEngine. So how to monitor the execution of this method?

06. Crash when the Flutter page is closed

  • The error log is as follows

         Caused by: java.lang.RuntimeException: Cannot execute operation because FlutterJNI is not attached to native.
            at io.flutter.embedding.engine.FlutterJNI.ensureAttachedToNative(FlutterJNI.java:259)
            at io.flutter.embedding.engine.FlutterJNI.onSurfaceDestroyed(FlutterJNI.java:369)
            at io.flutter.embedding.engine.renderer.FlutterRenderer.stopRenderingToSurface(FlutterRenderer.java:219)
            at io.flutter.embedding.android.FlutterTextureView.disconnectSurfaceFromRenderer(FlutterTextureView.java:223)
            at io.flutter.embedding.android.FlutterTextureView.access$400(FlutterTextureView.java:33)
            at io.flutter.embedding.android.FlutterTextureView$1.onSurfaceTextureDestroyed(FlutterTextureView.java:84)
            at android.view.TextureView.releaseSurfaceTexture(TextureView.java:261)
            at android.view.TextureView.onDetachedFromWindowInternal(TextureView.java:232)
            at android.view.View.dispatchDetachedFromWindow(View.java:22072)
            at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:4747)
            at android.view.ViewGroup.removeAllViewsInLayout(ViewGroup.java:6606)
            at android.view.ViewGroup.removeAllViews(ViewGroup.java:6552)
            at com.yc.fluttercontainer.FlutterEngineActivity.onDestroy(FlutterEngineActivity.java:292)
  • The error code is as follows

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (flutterEngine != null) {
            flutterEngine.destroy();
        }
        mFlutterContainer.removeAllViews();
        mFlutterView.removeAllViews();
        if (mRenderSurface != null) {
            // 打断内存泄漏
            ((FixFlutterTextureView) mRenderSurface).setSurfaceTextureListener(null);
        }
    }
  • https://blog.csdn.net/cxz200367/article/details/105998930

07.Android introduces the essence of flutter

  • How to understand the introduction of flutter pages in Android

    • The introduction of Flutter into the Android project essentially embeds the Widget written by Flutter into the Activity, similar to WebView. The container Activity is equivalent to WebView, and the route is equivalent to url. There are two ways: FlutterView and FlutterFragment. Jumping between pages and passing parameters can be achieved with the help of MethodChannel.

08. Flutter boot loading optimization

8.1 Analyze the start page flow of flutter

  • Through the flutter engine, the related initialization work of the entire flutter engine starts in the onCreate method

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        this.switchLaunchThemeForNormalTheme();
        super.onCreate(savedInstanceState);
        this.lifecycle.handleLifecycleEvent(Event.ON_CREATE);
        this.delegate = new FlutterActivityAndFragmentDelegate(this);
        //创建绑定引擎等
        delegate.onAttach(this);
        //用于插件、框架恢复状态
        delegate.onActivityCreated(savedInstanceState);
        //设置窗口背景透明,隐藏 status bar
        configureWindowForTransparency();
        //从这里分析,这里是咱们的入口
        setContentView(createFlutterView());
        this.configureStatusBarForFullscreenFlutterExperience();
    }
  • Then look down, the onCreateView method of the FlutterActivityAndFragmentDelegate class will be called

    • FlutterActivityAndFragmentDelegate class, the initialization, startup and other operations of flutter are delegated to it.
    • Generally speaking, a FlutterSurfaceView is created, which inherits from surfaceView (our flutter page is also rendered on this surface). Then we use it to initialize a FlutterView,

      @NonNull
      View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
      Log.v("FlutterActivityAndFragmentDelegate", "Creating FlutterView.");
      this.ensureAlive();
      if (this.host.getRenderMode() == RenderMode.surface) {
          //flutter 应用在surface上显示,所以会进入到这里
          FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(this.host.getActivity(), this.host.getTransparencyMode() == TransparencyMode.transparent);
          this.host.onFlutterSurfaceViewCreated(flutterSurfaceView);
          //用flutterSurfaceView 初始化了一个 FlutterView
          this.flutterView = new FlutterView(this.host.getActivity(), flutterSurfaceView);
      } else {
          //否则,应用在TextureView上显示
          FlutterTextureView flutterTextureView = new FlutterTextureView(this.host.getActivity());
          this.host.onFlutterTextureViewCreated(flutterTextureView);
          //用flutterTextureView 初始化了一个 FlutterView
          this.flutterView = new FlutterView(this.host.getActivity(), flutterTextureView);
      }
      
      this.flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener);
      //创建一个闪屏view - FlutterSplashView
      this.flutterSplashView = new FlutterSplashView(this.host.getContext());
      if (VERSION.SDK_INT >= 17) {
          this.flutterSplashView.setId(View.generateViewId());
      } else {
          this.flutterSplashView.setId(486947586);
      }
      //显示闪屏页
      this.flutterSplashView.displayFlutterViewWithSplash(this.flutterView, this.host.provideSplashScreen());
      Log.v("FlutterActivityAndFragmentDelegate", "Attaching FlutterEngine to FlutterView.");
      //所创建surface 绑定到engine上
      this.flutterView.attachToFlutterEngine(this.flutterEngine);
      return this.flutterSplashView;
      }
  • Then we create a FlutterSplashView (inherited FrameLayout). It is important to call the displayFlutterViewWithSplash() method.

    • As you can see here, through splashScreen (an interface), look at the interface implementation class specifically, then create a splashScreenView, and finally add it to the layout of flutter

      public void displayFlutterViewWithSplash(@NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) {
      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);
            //添加 splashScreenView 
            this.addView(this.splashScreenView);
            flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener);
        } 
      }
      }
    • So when will this start Splash layout be removed? When creating FlutterSplashView, a listener for the completion event was added, and it was removed only after flutter was loaded successfully.

      public FlutterSplashView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
      this.onTransitionComplete = new Runnable() {
          public void run() {
              FlutterSplashView.this.removeView(FlutterSplashView.this.splashScreenView);
              FlutterSplashView.this.previousCompletedSplashIsolate = FlutterSplashView.this.transitioningIsolateId;
          }
      };
      this.setSaveEnabled(true);
      }
  • get conclusion

    • It can be found that there will be a long process from the display of the splash screen to the start of the engine and the display of the flutter page, and the splash screen will not be removed until the flutter page is displayed.

8.2 How to optimize the flutter startup screen

  • The first option

    • Flutter also provides a transition scheme (white screen by default) because it takes a certain amount of time to create and initialize the engine. As shown below, you can set the background

      AndroidManifest.xml下的
      <meta-data
            android:name="io.flutter.embedding.android.SplashScreenDrawable"
            android:resource="@drawable/launch_background"/>
  • The second option

    @Nullable
    @Override
    public SplashScreen provideSplashScreen() {
        //创建自定义flutter启动屏view
        return new FlutterSplashView();
    }
    
    public class FlutterSplashView implements SplashScreen {
    
        @Nullable
        @Override
        public View createSplashView(@NonNull Context context, @Nullable Bundle savedInstanceState) {
            View v = new View(context);
            v.setBackgroundColor(Color.WHITE);
            return v;
        }
    
        @Override
        public void transitionToFlutter(@NonNull Runnable onTransitionComplete) {
            onTransitionComplete.run();
        }
    }

fluter Utils tool library: https://github.com/yangchong211/YCFlutterUtils

Flutter mixed project code example: https://github.com/yangchong211/YCHybridFlutter


杨充
221 声望42 粉丝