preface

Last article we covered some basic usage and source code analysis of the Android Hybrid Flutter project. You can see that the Flutter basically works, but there will be a short white screen or black screen when the Flutter is started. In this article, we will discuss this point in depth

Android Projects embed Flutter Module

The body of the

First let’s take a look at the re-enactment

Rendermode. surface (rendermode. texture later)

When analyzing the Flutter Android startup source, we mentioned that there is a method overwritten in FlutterActivity called Flutter

provideSplashScreen

@Nullable @Override public SplashScreen provideSplashScreen() { Drawable manifestSplashDrawable = getSplashScreenFromManifest(); if (manifestSplashDrawable ! = null) { return new DrawableSplashScreen(manifestSplashDrawable); } else { return null; } } private Drawable getSplashScreenFromManifest() { try { ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); Bundle metadata = activityInfo.metaData; int splashScreenId = metadata ! = null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; return splashScreenId ! = 0? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP ? getResources().getDrawable(splashScreenId, getTheme()) : getResources().getDrawable(splashScreenId) : null; } catch (PackageManager.NameNotFoundException e) { // This is never expected to happen. return null; }}Copy the code

In the Flutter pure project, the temporary black and white screen when Android starts ->Flutter starts was fixed.

In Android’s project to embed Flutter, the purpose is to solve the problem of a temporary black and white screen when Flutter starts up.

It is closed until a callback is received to draw the first frame.

It default to find corresponding package name list file name as “IO. Flutter. Embedding. Android. SplashScreenDrawable” meta – data, but this is our flutter Module without the corresponding listing file, So we’ll create a new class that inherits FlutterActivity and then override the corresponding methods to show it

Let’s focus on this class

class MyFlutterActivity : FlutterActivity() {
    override fun provideSplashScreen(): SplashScreen? {
        return DrawableSplashScreen(resources.getDrawable(R.drawable.loading))
    }
}
Copy the code

Re-providesplashscreen returns a DrawableSplashScreen.

  startActivity(
  	Intent(root.context, MyFlutterActivity::class.java)
  )
Copy the code

You can see that a loading page can solve the problem of a blank screen (possibly ugly 🤓).

Now let’s go through the manifest file configuration, and let me switch to a startup diagram to see a little bit more clearly

<activity android:name=".ui.MyFlutterActivity">
  <meta-data
    android:name="io.flutter.embedding.android.SplashScreenDrawable"
    android:resource="@drawable/launch_bg" />
</activity>
Copy the code

In this way, if we don’t need to customize the DrawableSplashScreen style, we don’t need to rewrite the provideSplashScreen, it’s already done internally. Or rewrite calls super. ProvideSplashScreen ();

In fact, the problem can be solved at this point, as shown in the picture below

DrawableSplashScreen supports custom styles and hiding times,

DrawableSplashScreen(
                it,
                ImageView.ScaleType.CENTER, 200
  )
Copy the code

I won’t show you the effect. Let’s try it

As you can see, we solved the problem of white and black screen startup by configuring the startup diagram.

RenderMode.texture

In fact, there is a web article on how to solve the black screen, which is to set the Activity Theme

 <item name="android:windowIsTranslucent">true</item>
Copy the code

However, this setting does not work on version 1.20

The rendermode. texture mode actually works (or was it the texture mode used in the original version? Do your own research on this.)

Let’s take a look at the following code

/**
     * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
     * {@link BackgroundMode#transparent}.
     *
     * <p>The default background mode is {@link BackgroundMode#opaque}.
     *
     * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
     * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
     * FlutterTextureView} to support transparency. This choice has a non-trivial performance
     * impact. A transparent background should only be used if it is necessary for the app design
     * being implemented.
     *
     * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
     * BackgroundMode#transparent} must have a theme applied to it that includes the following
     * property: {@code <item name="android:windowIsTranslucent">true</item>}.
     */
    @NonNull
    public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
      this.backgroundMode = backgroundMode.name();
      return this;
    }
Copy the code

WindowIsTranslucent = true if rendermode. texture mode is used.

   override fun getRenderMode(): RenderMode {
        return RenderMode.texture
    }
Copy the code

Here we just override getRenderMode to return to Texture mode and comment out the launch page Settings

<activity android:name=".ui.MyFlutterActivity" android:theme="@style/MyTheme" > <! -- <meta-data--> <! -- android:name="io.flutter.embedding.android.SplashScreenDrawable"--> <! -- android:resource="@drawable/launch_bg" />--> </activity> <style name="MyTheme" parent="@style/AppTheme"> <item name="android:windowIsTranslucent">true</item> </style>Copy the code

You can see that the black screen problem is solved. But this only works with texture mode

Bottom line: The white and black screen can be fixed if the startup page is configured, either in Surface or Texture mode

Extended content

And then we move on to an extension, which is what we talked about earlier

@Nullable @Override public FlutterEngine provideFlutterEngine(@NonNull Context context) { // No-op. Hook for subclasses.  return null; }Copy the code
  1. As you can see, FlutterActivity overrides this method and simply returns null. On the surface it may seem pointless, but in fact it is very important (personally understood) in mixed development, as explained in 3 below

  2. By inheriting the method of FlutterActivity, which is actually the most commonly used pattern in our mixed development, it has the following benefits.

    1. Override provideSplashScreen to set up the startup screen/manifest file configuration meta-data

    2. Override getCachedEngineId to provide a caching Engine

    3. Rewrite the provideFlutterEngine to provide a cache Engine or instantiate a new Engine(you can set the method name for dart’s first startup execution)

    4. Override getInitialRoute to specify the initial routing path

    5. Rewrite getDartEntrypointFunctionName, specify the start execution method name for the first time

    6. Overwrite onFlutterUiDisplayed, Flutter displays the first frame’s callback listen

    7. Rewrite shouldDestroyEngineWithHost, judge is the engine FlutterActivity destroyed when destroyed

    Next we’ll use a FlutterEngine initialized in the Application and added to the cache pool

    getCachedEngineId

    class MyApplication : Application() {
        private lateinit var flutterEngine :FlutterEngine
        override fun onCreate() {
            super.onCreate()
            flutterEngine = FlutterEngine(this,null,false)
            flutterEngine.dartExecutor.executeDartEntrypoint(
                DartExecutor.DartEntrypoint(FlutterMain.findAppBundlePath(), "main1")
            )
            FlutterEngineCache.getInstance().put("my_engine_id",flutterEngine)
        }
    }
    Copy the code
    class MyFlutterActivity : FlutterActivity() {
        override fun getCachedEngineId(): String? {
            return "my_engine_id"
        }
    }
    Copy the code

    To see what happens, we specify main1 as the startup method name

You can see that an engine that uses caching loads significantly faster because FlutterEnigne is instantiated and put into the cache pool when the program starts

provideFlutterEngine

override fun provideFlutterEngine(context: Context): FlutterEngine? {
        return FlutterEngineCache.getInstance().get("my_engine_id")
}
Copy the code

Fetch the FlutterEngine from the cache and return it. The effect is the same. Let’s go straight to Debug

Next we instantiate a new Engine, and specify the start method is called main, and through the shouldDestroyEngineWithHost method returns true – > with the Activity destroyed destroyed

class MyFlutterActivity : FlutterActivity() {

    override fun provideFlutterEngine(context: Context): FlutterEngine? {
        val customFlutterEngine = FlutterEngine(this, null, false)
        customFlutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint(FlutterMain.findAppBundlePath(), "main")
        )
        return customFlutterEngine;
    }

    override fun shouldDestroyEngineWithHost(): Boolean {
        return true
    }
}
Copy the code

Next we configure the boot route and the boot method, as well as the callback that displays the first frame

class MyFlutterActivity : FlutterActivity() {

    override fun provideFlutterEngine(context: Context): FlutterEngine? {
        return FlutterEngine(this, null, false)
    }

    override fun shouldDestroyEngineWithHost(): Boolean {
        return true
    }

    override fun getDartEntrypointFunctionName(): String {
        return "main1"
    }

    override fun getInitialRoute(): String {
        return "/second"
    }

    override fun onFlutterUiDisplayed() {
        super.onFlutterUiDisplayed()
        Toast.makeText(this, "onFlutterUiDisplayed", Toast.LENGTH_LONG).show()
    }
}
Copy the code

conclusion

From this article, we can learn

  • White and black screen handling when Android Flutter Module is mixed
  • How does hybrid development better manage Engine or make it easier to manage

Through these four articles, I have further improved my understanding of the source code of Flutter on the Android application layer

In the next article we will start to explain some of the ways that Flutter interacts with Android