This article was originally published on the wechat official account — Interesting Things in the world, handling, reprint please note the source, otherwise will be held liable for copyright.

After another two months without Posting, we have recently started to introduce the Flutter as a new form of development into our mature projects. As an early adopter, I also did a Flutter sharing for 30 or 40 students in the group. Because it involves some internal information, so after I desensitization sorted out, I will use a special article for related sharing, you can start to look forward to, haha. As for this article, I’m going to talk about something interesting – the rapid introduction of a mature project with Flutter and the mixed development of Flutter and Native. I hope you can give me a lot of likes and attention.

Reading Instructions:

  • 1. This article is based on Android platform
  • 2. The Flutter test project — Test, the Flutter container project — Container

This paper is divided into the following sections, which can be read as needed:

  • 1. Rapid introduction of Flutter in mature projects — The seamless introduction of Flutter in existing projects as a means of development
  • 2. Hybrid Development of Flutter and Native — Exploration of the development of both Flutter and Native technologies in one page
  • 3. The tail

The Flutter is being tested on Github

The Flutter container project on Github

First, the mature project Flutter was quickly introduced

Many tutorials now stop at creating a new Flutter project and then start showing how to use that project to develop the Flutter. But most of the scenarios we use Flutter for now are based on mature projects. We cannot push the original project back to the drawing board by using the Flutter. In this section I’m going to look at a way for mature projects to seamlessly access Flutter. This chapter needs to be combined with the Github project code mentioned above.

1. Practice of Idle fish and Meituan

  • 1. At present, many manufacturers have their own Flutter implementation practices for mature projects, among which Meituan and Xianyu should have been running for a long time. Their access methods are mainly divided into the following steps:
    • 1. Clarify the construction and operation mode of Flutter App.
    • 2. Modify the Gradle file of the Flutter project and package the Flutter project as an AAR file.
    • 3. Push the AAR file to the Maven server.
    • 4. The main project introduces the AAR file of Flutter and compiles it together with the main project to generate the main App.
  • 2. Meituan’s practice
  • 3. Practice of idle fish

My practice

From the above introduction, the practice of xianyu and Meituan seems to have some inconveniences. For example, the Flutter code cannot be dynamically updated, the AAR of the Flutter is too intrusive to compile with the main project, etc. (this is just my own shallow opinion, if you have any objection, please comment in the comments section). So what I want to do in this section is introduce a very minimally invasive way to access the Flutter, which is simply to say: dynamically load the Apk generated by the Flutter. Next, I will explain the code in the two github projects mentioned above. You must clone the two projects, of course, it is better to click star.

(1). Create the Flutter test project

  • 1. Create a Flutter Project. This one is very simple and there are many tutorials online that I won’t repeat.
  • 2. After the creation, as shown in Figure 1, we need to add some code in the build.gradle file in the app directory, as shown in code block 1.
- the code block1When evening, this article from the book, the Denver nuggets: -- -- -- -- -- project. AfterEvaluate {android. ApplicationVariants. All {the variant - >def variantName = variant.name.capitalize()
        def buildTask = project.tasks.findByName("assemble${variantName}")
        if (buildTask) {
            def outputApk = variant.outputs[0].outputFile.path
            def classEntry = "*.dex"
            def soEntry = "lib/*"
            def metaEntry = "META-INF/*"
            def licenseEntry = "assets/flutter_assets/LICENSE"

            buildTask.doLast {
                println variant.outputs[0].outputFile.length()
                exec {
                    commandine 'sh'.'-c'."zip -d ${outputApk} ${classEntry}"
                }
                exec {
                    commandLine 'sh'.'-c'."zip -d ${outputApk} ${soEntry}"
                }
                exec {
                    commandLine 'sh'.'-c'."zip -d ${outputApk} ${metaEntry}"
                }
                exec {
                    commandLine 'sh'.'-c'."zip -d ${outputApk} ${licenseEntry}"
                }
            }
        }
    }
}
Copy the code
  • 3. The main function of this code is to delete all unnecessary files such as classes.dex, libflutter.
  • 4. After the code is added, we run the Flutter build apk –debug from the command line. This will generate a debug version of APK. Its size is 7.3MB, compared to 33.5MB for the DEBUG version of apK before the code in block 1 was added. And you can see that this is very effective. With a release version of apK, the size is further reduced to 1.5MB.

(2). Create the Flutter container project

  • 1. With the simplified APK for Flutter, the next thing we need is a container to load the Flutter APK. The code is in the Flutter container project I mentioned earlier, so follow me to see how the container loads the Flutter APK.
  • 2. As shown in Figure 2, the project’s Flutter container is in the form of an Android Library, so that people can bring the lib into their own projects. We can see that the Flutter. Jar is directly introduced into lib. The jar is available in debug and release versions. The JAR contains the Java layer code for Flutter, along with the SO file. The debug version size is 7.3MB, and the release version size is 3.6MB. This is how much our APK is going to increase in the end, but it’s still acceptable. The APK, which contains the Dart code and resources, is available as a dynamic download.
- the code block2, this article from a brief book, gold: when xi -----public class MainActivity extends AppCompatActivity {
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    RxPermissions permissions = new RxPermissions(this);
    permissions.setLogging(true);
    permissions.request(Manifest.permission.READ_EXTERNAL_STORAGE)
        .subscribe(aBoolean -> FlutterContainer.init(getApplication(), "/storage/emulated/0/flutter1.apk"));
    findViewById(R.id.aaa).setOnClickListener(v -> startActivity(new Intent(MainActivity.this, Main2Activity.class))); }}Copy the code
- the code block3, this article from a brief book, gold: when xi -----public class FlutterContainer {
  private static final String TAG = "FlutterContainer";
  private static boolean sInitialized = false;
  private static Context sApplicationContext;
  
  private static String sFlutterInstallPath = "";
  
  public static void init(@NonNull Application applicationContext, @NonNull FlutterEngine.PrepareFlutterPackage prepareFlutterPackage) {
    init(applicationContext, null, prepareFlutterPackage, null);
  }
  
  public static void init(@NonNull Application applicationContext, @NonNull String flutterInstallPath) {
    init(applicationContext, flutterInstallPath, null.null);
  }
  
  public static void init(@NonNull Application applicationContext, @NonNull String flutterInstallPath, @Nullable FlutterEngine.Callback startCallBack) {
    init(applicationContext, flutterInstallPath, null, startCallBack);
  }
  
  public static void init(@NonNull Application applicationContext, @NonNull FlutterEngine.PrepareFlutterPackage prepareFlutterPackage, @Nullable FlutterEngine.Callback startCallBack) {
    init(applicationContext, null, prepareFlutterPackage, startCallBack);
  }
  
  /** * can only be initialized once when the app starts **@param applicationContext
   */
  private static void init(@NonNull Application applicationContext, @Nullable String flutterInstallPath, @Nullable FlutterEngine.PrepareFlutterPackage prepareFlutterPackage, @Nullable FlutterEngine.Callback startCallBack) {
    if (sInitialized) {
      return;
    }
    new FlutterManager(applicationContext);
    sInitialized = true;
    sApplicationContext = applicationContext;
    if(! TextUtils.isEmpty(flutterInstallPath)) { upgradeFlutterPackage(flutterInstallPath, startCallBack); }else if(prepareFlutterPackage ! =null) {
      upgradeFlutterPackage(prepareFlutterPackage, startCallBack);
    } else {
      Log.i(TAG, "FlutterContainer init no flutter package"); }}/ * * *@param flutterInstallPath
   */
  public static void upgradeFlutterPackage(@NonNull String flutterInstallPath, @Nullable FlutterEngine.Callback startCallBack) {
    if(! sInitialized) {return;
    }
    FlutterManager.getInstance().resetFlutterPackage();
    sFlutterInstallPath = flutterInstallPath;
    FlutterManager.getInstance().getFlutterEngine().startFast(startCallBack);
  }
Copy the code
  • 3. Next, let’s look at code block 2, which is an example. You can see that FlutterContainer is the API exposed by the container library to initialize the Flutter environment and upgrade the Flutter Apk.
  • 4.Code block 2Init, so let’s seeBlock 3API in FlutterContainer.
    • 1. Init: This method is called once the first time the Flutter APk needs to be initialized. There are several different apis.
    • 2. UpgradeFlutterPackage: used to reload the Flutter APK. For example, if we need to release a new version of the Flutter, we can use this API to reload a new Flutter APK.
- the code block4, this article from a brief book, gold: when xi -----public class FlutterManager {
  
  private static FlutterManager sInstance;
  
  private final FlutterEngine mFlutterEngine;
  private final FlutterContextWrapper mFlutterContextWrapper;
  private final Context mContext;
  
  FlutterManager(Application context) {
    sInstance = this; // Simple singleton, thread is not safe, logical guarantee
    mFlutterEngine = new FlutterEngine(context);
    mFlutterContextWrapper = new FlutterContextWrapper(context);
    mContext = context;
  }
  
  public static FlutterManager getInstance(a) {
    return sInstance;
  }
  
  public void registerChannel(BinaryMessenger messenger, String channel, BaseHandler handler) {
    new MethodChannel(messenger, channel + ".method").setMethodCallHandler(handler);
    if (handler.mEnableEventChannel) {
      new EventChannel(messenger, channel + ".event").setStreamHandler(handler); }}FlutterEngine getFlutterEngine(a) {
    return mFlutterEngine;
  }
  
  public FlutterContextWrapper getFlutterContextWrapper(a) {
    return mFlutterContextWrapper;
  }
  
  /** * Is there a Flutter pack available **@return* /
  public boolean isFlutterAvailable(a) {
    File activeApk = new File(FlutterContainer.getFlutterInstallPath());
    return activeApk.isFile();
  }
  
  /** * If you want to use the new Flutter package, you need to reset */
  void resetFlutterPackage(a) { mFlutterContextWrapper.reset(); }}Copy the code
  • 5.FlutterContainer is the entry point to initialize Flutter APk, so FlutterManager is the class that does this. We look atBlock 4You can see that FlutterManager is a singleton, and one of the steps in flutterContainer.init is to initialize this singleton. The API has the following features:
    • 1. RegisterChannel: Register the communication channel between Java and Dart. This will be explained in more detail later.
    • GetFlutterEngine: Get the FlutterEngine, which internally calls the API that Flutter really loads apK.
    • 3. GetFlutterContextWrapper: a Context wrapper classes, mainly in order to make the Flutter the extracted smoothly inside the apk code and resources.
- the code block5, this article from a brief book, gold: when xi -----public class FlutterContextWrapper extends ContextWrapper {

  private AssetManager sAssets;

  FlutterContextWrapper(Context base) {
    super(base);
  }

  public void reset(a) {
    sAssets = null; // After each flutter package is installed, a new asset needs to be created
  }

  @Override
  public Resources getResources(a) {
    return new Resources(getAssets(), super.getResources().getDisplayMetrics(),
        super.getResources().getConfiguration());
  }

  @Override
  public AssetManager getAssets(a) {
    if(sAssets ! =null) {
      return sAssets;
    }

    File activeApk = new File(FlutterContainer.getFlutterInstallPath());
    if(! activeApk.isFile()) {return super.getAssets();
    }

    sAssets = ReflectionUtil.newInstance(AssetManager.class);
    ReflectionUtil.callMethod(sAssets, "addAssetPath", activeApk.getPath());
    return sAssets;
  }

  @Override
  public PackageManager getPackageManager(a) {
    return new FlutterPackageManager(super.getPackageManager()); }}Copy the code
  • 6. Since Flutter will put the Dart code and resources in the asset when building the APK, we need to create a FlutterContextWrapper to replace the AssetManager, as in code block 5. Make the asset directory point to the Flutter APK we created when the Flutter is loaded.
- the code block6, this article from a brief book, gold: when xi -----class FlutterEngine {
  
  private static boolean sInitialized; // The global markup engine is started
  private final Context mContext;
  
  FlutterEngine(Context context) {
    mContext = context;
  }
  
  /** * Quick start mode, indicating that there is already a package */
  void startFast(@Nullable Callback callback) {
    if (sInitialized) {
      // It needs to start as soon as possible, so it needs to be deheavy
      callback(callback, null);
      return;
    }
    if (FlutterManager.getInstance().isFlutterAvailable()) { // There are currently available packages
      startFlutterInitialization();
      ensureInitializationComplete();
      callback(callback, null);
    } else {
      DebugUtil.logError(new RuntimeException("startFast but no available package")); }}/** * Indicates the slow start mode, indicating that no alarm is generated. Prepare */
  void startSlow(@Nullable Callback callback, @NonNull PrepareFlutterPackage prepareFlutterPackage) {
    Single.fromCallable(() -> {
      // This place does not deweight, and reinitialize whether or not sInitialized, to ensure that the latest flutter package is used.
      prepareFlutterPackage.prepareFlutterPackage();
      return new Object();
    }).subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(o -> {
          startFlutterInitialization();
          ensureInitializationComplete();
          callback(callback, null);
        }, throwable -> {
          throwable.printStackTrace();
          callback(callback, throwable);
        });
  }
  
  private static void callback(@Nullable Callback callback, Throwable t) {
    if(callback ! =null) { callback.onCompleted(t); }}private void startFlutterInitialization(a) { // Do not block UI
    // The start method of the Flutter SDK can be called multiple times. Its main function is to unpack the resources, so there is no need to redo it
    FlutterMain.startInitialization(FlutterManager.getInstance().getFlutterContextWrapper());
  }
  
  private void ensureInitializationComplete(a) {
    FlutterMain.ensureInitializationComplete(mContext, null);
    sInitialized = true; // It has been initialized
  }
  
  // Start the callback
  public interface Callback {
    
    /** * Initialization is complete@paramE The value is null for success and is not null for failure. */
    void onCompleted(Throwable e);
  }
 
  // Prepare the callback of the Flutter package
  public interface PrepareFlutterPackage {
    String prepareFlutterPackage(a); }}Copy the code
  • 7. Follow the FlutterContainer call further, we will come toCode block 6In FlutterEngine, there are two main apis:
    • 1. StartFast: As the method name says, this method means to quickly load the Flutter APk. It can only be called once and multiple calls will be cancelled. Generally, if we have our flutter APK ready, we can use this method to load our FLUTTER APK. Can see its internal will call to FlutterMain. StartInitialization, this is Flutter. The jar in the API, is mainly used to extract and mobile Asset in the Context. Since we created a FlutterContextWrapper earlier, this will actually unzip the Dart code and resources in the Flutter APK.
    • 2. StartSlow: This method can be called multiple times and is mainly used to upgrade apK. If we don’t have apK ready and need to download it from the network, we can use this method. Principle and startFast is same, but in the end of all is to use FlutterMain. StartInitialization to unpack and mobile Flutter in the apk resources.
  • 8. Here the seamless introduction of Flutter into a mature project is complete. You can compile Flutter container and then Flutter test projects generated apk adb push to phone/storage/emulated / 0 / flutter1 apk, can experience the dynamic loading Flutter apk pleasure.
  • 9. You can also use the Flutter attch to hot reload the debug version of the Flutter APK and enjoy the second level code update.

Ii. Hybrid development of Flutter and Native

Having finished with how to seamlessly introduce Flutter into a mature project, this chapter looks at the way that Both Flutter and Native are developed together. It might be easy to mix and develop, embed a Flutter Activity/Fragment directly and run the Flutter as a container. In fact, this idea is too idealized, what if I need both Flutter and Native in an Acitivity/Fragment? That’s what I’m going to do in this chapter, so let’s read along with me.

1. The practice of Flutter, Native mixed development scenarios and idle fish

  • 1. Let’s talk about under what circumstances should we use Flutter and Native together in an Activity/Fragment
    • 1. For example, if I need to embed map View on one of my interfaces, if I need to use Flutter on this interface, the components of Flutter are far less perfect than that of Native. For example, Amap and Baidu Map only have Native versions at present. This is where the Flutter and Native mix comes in.
    • 2. Take the current popular short video apps as examples, such as the video editing function of Douyin App. Most of the video editing functions are developed based on the video editing SDK of Native layer. If this interface were to upload the Flutter, the entire video editing SDK would need to provide a version of Dart, which will not be available for a short time.
    • 3. With the above two examples, we now have a good idea of the scenarios in which a mix of Flutter and Native is needed on a single interface: This is what happens when the **Flutter control is not yet available to replace the Native control, and a certain interface requires the Flutter. ** With the development of Flutter, there may be a Flutter map and a Flutter video editing SDK, but mixed development of Flutter and Native has been a very common scene in the last year or two.
  • 2. So let’s talk about some mixed development practices that are already in place. I’ve written a blog about my mixed development practices:Mixed development practices of Idle fish.
    • 1. Give the View on Android to Flutter using the API provided by Flutter.
    • 2. Since the Flutter is rendered in SurfaceView or TextureView, the View on Android generates a Texture(OpenGL Texture). Hand in the Flutter and let the Flutter be rendered together on the Surface/TextureVIew.
    • 3. The corresponding gestures are also transmitted from the Flutter layer to the Android layer.
  • 3. Of course, the practice of idle fish has its advantages, such as the practice recommended by the official, better versatility and so on.

My practice

In order to deal with the high cost of data transfer, I thought of another way to get around this problem. This summary should be consumed in conjunction with the Flutter container project.

  • 1. First of all, we need to understand several precursors to rendering of Flutter on Android:

    • 1. After Flutter is up and running, the screen is rendered to SurfaceView/TextureView on Android.
    • 2. For an in-depth look at SurfaceView and TextureView, check out this article: Android Drawing Mechanism and Full Source Code Analysis of the Surface family.
    • 3. The underlying Flutter defaults to black if rendered with SurfaceView.
    • If rendered with TextureView, the underlying Flutter is transparent by default.
    • 5. To summarize, if we render the Flutter using TextureView, we can treat the Flutter as a normal view in the Android view hierarchy, which can be above or below certain views. This is our solution: no longer think of the Flutter as an Activity, it is just a part of the View hierarchy, so we can do whatever we want with the View.
  • 2. The code is very simple after you understand the idea of mixed development.

    • 1. First we need to know in addition to the IO. Flutter. App. FlutterActivity, usually we use the outside Acitivty. Flutter also provides another IO. Flutter. Embedding. Android. FlutterActivity Acitvity, one of the ways in which this Activity rendering Flutter TexutreView is used.
    • 2. Of course the last IO. Flutter. Embedding. Android. FlutterAcitivity or through IO. Flutter. The embedding. Android. FlutterFragment to TextureView Add to the View hierarchy.
    - the code block7, this article from a brief book, gold: when xi -----public class FlutterTextureBaseFragment extends FlutterFragment {
      protected FlutterView mFlutterView;
      protected FlutterContextWrapper mFlutterContextWrapper;
      
      @Nullable
      @Override
      public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = super.onCreateView(inflater, container, savedInstanceState);
        mFlutterView = ViewUtil.getFlutterView(view);
        mFlutterContextWrapper = new FlutterContextWrapper(getContext());
        return mFlutterView;
      }
      
      @Nullable
      public FlutterView getFlutterView(a) {
        return mFlutterView;
      }
      
      public static class TextureBuilder extends FlutterFragment.Builder {
        @NonNull
        public <T extends FlutterFragment> T build(a) {
          try {
            T frag = (T) FlutterTextureBaseFragment.class.newInstance();
            if (frag == null) {
              throw new RuntimeException("The FlutterFragment subclass sent in the constructor (" + FlutterTextureBaseFragment.class.getCanonicalName() + ") does not match the expected return type.");
            } else {
              Bundle args = this.createArgs();
              frag.setArguments(args);
              returnfrag; }}catch (Exception var3) {
            throw new RuntimeException("Could not instantiate FlutterFragment subclass (" + FlutterTextureBaseFragment.class.getName() + ")", var3); }}}@Override
      public Context getContext(a) {
        if (mFlutterContextWrapper == null) {
          return super.getContext();
        } else {
          returnmFlutterContextWrapper; }}}Copy the code
    • 3. We look at the code block 7, FlutterFragment. The Builder is to construct IO. Flutter. Embedding. Android. FlutterFragment Buidler class, My FlutterTextureBaseFragment used primarily to provide FlutterView to the outside world.
    - the code block8, this article from a brief book, gold: when xi -----public class FlutterTextureBaseActivity extends FlutterActivity {
      protected FlutterView mFlutterView;
      protected FlutterTextureBaseFragment mFlutterTextureBaseFragment;
      
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      }
      
      @Nullable
      public ViewGroup getFlutterViewParent(a) {
        getFlutterView();
        if (mFlutterView == null) {
          return null;
        } else {
          return(ViewGroup) mFlutterView.getParent(); }}@Nullable
      public FlutterView getFlutterView(a) {
        if (mFlutterTextureBaseFragment == null) {
          return null;
        } else if(mFlutterTextureBaseFragment.getView() ! =null) {
          mFlutterView = mFlutterTextureBaseFragment.getFlutterView();
          return mFlutterView;
        } else {
          return null; }}@Nullable
      public FlutterTextureBaseFragment getFlutterTextureBaseFragment(a) {
        return mFlutterTextureBaseFragment;
      }
      
      @NonNull
      protected FlutterTextureBaseFragment createFlutterFragment(a) {
        mFlutterTextureBaseFragment = (new FlutterTextureBaseFragment.TextureBuilder())
            .dartEntrypoint(this.getDartEntrypoint())
            .initialRoute(this.getInitialRoute())
            .appBundlePath(this.getAppBundlePath())
            .flutterShellArgs(FlutterShellArgs.fromIntent(this.getIntent()))
            .renderMode(FlutterView.RenderMode.texture)
            .transparencyMode(FlutterView.TransparencyMode.opaque)
            .build();
        returnmFlutterTextureBaseFragment; }}Copy the code
    • 4. Look at the code block 8 FlutterTextureBaseActivity inherited IO. Flutter. Embedding. Android. FlutterActivity, Main job is to create a FlutterTextureBaseFragment TexutreVIew as rendering, and then provide FlutterView ParentView, for external use.
    • 5. Once you know the above code, it is very easy to do mixed development in an Activity. For example, if I need to copy the video editing page of the Tiktok App with my Flutter, I can do the following:
      • 1. Inheriting FlutterTextureBaseActivity, put the View video editing SDK FlutterView below, this time FlutterView will give video edit View.
      • 2. Develop business logic in Flutter
      • 3. Use the Channel to let the behavior in the Flutter operate the video editing View.
    • 6. I used our company’s video editing SDK to simply practice the functions of video playback and pause, as followsFigure 3
      • 1. The following video player is the Native code on Android.
      • 2. The two play and stop buttons above are the codes for the Flutter.
      • 3. It’s an internal code, so it can’t be put on Github.

Third, the tail

Recently, THE frequency of updating articles has become longer, partly because I am busy at work, and partly because I want to write more articles for my readers. So I hope you can continue to pay attention to my nuggets, Jane, wechat public number, I will always share original boutique articles to you.

No anxiety peddling, no headlines. Share something interesting about the world. Topics include but are not limited to: science fiction, science, technology, Internet, Programmer, computer programming. Below is my wechat public number: the world’s interesting things, do a lot of goods waiting for you to see.