Flutter Add to App issue log

insert image description here
It was connected to the application a while ago Flutter, and the official Multiple FlutterEngine management solution is used. Currently, it is running well online. Here is a summary of the problems encountered.

There is no problem in integrating Flutter into the existing application as a whole, just follow the instructions in the document and combine the demo operation. After accessing multiple languages, the dark mode can also run normally as the native part. But still encountered some details in the actual development.

Above the fold optimization

As mentioned in the official documentation, even with preheated , it still takes some time to display the content FlutterEnginefor the first time . FlutterIn order to further improve the user experience, Flutterit is supported to display the splash screen page before the first frame is rendered.

The problem I encountered here is that there are four tabs on the home page, and the third tab is the Flutter page. So when switching to it, the first load will be a white screen.

I provide two optimization methods here. The first method can be used CachedEngineFragmentBuilderin shouldDelayFirstAndroidViewDraw(). It indicates whether to delay the Android drawing process until the Flutter UI is displayed.

FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .shouldDelayFirstAndroidViewDraw(true)
    .build();

There are two points to note when using this method:

  • Render mode must be used RenderMode.surface.
  • This time delay does not disappear, it just shows a different strategy. If it is the first time to click the tab to switch, after clicking, it will wait for the Flutter display to finish before switching. On low-end machines there will be noticeable stuttering here.

The second method can be used SplashScreento display a splash page. We let the hosted FlutterFragmentActivity implement the method SplashScreenProviderof the interface provideSplashScreen.

@Override
public SplashScreen provideSplashScreen() {
    
    
    return new DrawableSplashScreen(this.getResources().getDrawable(R.drawable.xxx), 
    ImageView.ScaleType.CENTER_CROP, 300);
}
  • The above parameters are to set the splash screen image, the cropping method of the image, and the transition animation time when the Flutter UI appears.
  • The rendering mode must be used for the transition animation to take effect RenderMode.texture.

These two methods have their own applicable scenarios, and the first one can be used in most cases. Since the background of our page is a picture, we can use the second method. Display the image first, and then use the animation transition to display the page content. The waiting time when switching for the first time can be avoided using the first method.

In addition, RenderMode.surfacethere is currently a bug in the rendering mode (Flutter 3.10), which is when loading the page onResume. FlutterFragmentwill suddenly appear because SurfaceViewit is at the top of the view hierarchy. So it covers other Fragment pages. There is no such problem in current use RenderMode.texture.

Follow-up issues can be found here .

Activity is basically the same as Fragment, and uses RenderMode.surfacethe rendering mode by default. So the Activity will not be opened until the first frame of the page is rendered. If the background of the flutter page is a picture, when you enter the page for the first time, it will flash for a while because the picture loads for a certain period of time (release is relatively better). So you can also consider the splash screen solution.

The above questions correspond to the source code FlutterActivityAndFragmentDelegate location onCreateView:

 View onCreateView(
      LayoutInflater inflater,
      @Nullable ViewGroup container,
      @Nullable Bundle savedInstanceState,
      int flutterViewId,
      boolean shouldDelayFirstAndroidViewDraw) {
    
    
    Log.v(TAG, "Creating FlutterView.");
    ...

    SplashScreen splashScreen = host.provideSplashScreen();

    if (splashScreen != null) {
    
    
      FlutterSplashView flutterSplashView = new FlutterSplashView(host.getContext());
      flutterSplashView.setId(ViewUtils.generateViewId(FLUTTER_SPLASH_VIEW_FALLBACK_ID));
      flutterSplashView.displayFlutterViewWithSplash(flutterView, splashScreen);

      return flutterSplashView;
    }

    if (shouldDelayFirstAndroidViewDraw) {
    
    
      delayFirstAndroidViewDraw(flutterView);
    }
    return flutterView;
  }

    private void delayFirstAndroidViewDraw(FlutterView flutterView) {
    
    
    if (host.getRenderMode() != RenderMode.surface) {
    
    
      throw new IllegalArgumentException(
          "Cannot delay the first Android view draw when the render mode is not set to"
              + " `RenderMode.surface`.");
    }

    if (activePreDrawListener != null) {
    
    
      flutterView.getViewTreeObserver().removeOnPreDrawListener(activePreDrawListener);
    }

    activePreDrawListener =
        new OnPreDrawListener() {
    
    
          @Override
          public boolean onPreDraw() {
    
    
            if (isFlutterUiDisplayed && activePreDrawListener != null) {
    
    
              flutterView.getViewTreeObserver().removeOnPreDrawListener(this);
              activePreDrawListener = null;
            }
            return isFlutterUiDisplayed;
          }
        };
    flutterView.getViewTreeObserver().addOnPreDrawListener(activePreDrawListener);
  }

exception handling

  1. FlutterFragment, IllegalStateException
IllegalStateException: The requested cached FlutterEngine did not exist in the FlutterEngineCache: 'my_engine_id'
       at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.setupFlutterEngine (FlutterActivityAndFragmentDelegate.java:280)
       at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onAttach (FlutterActivityAndFragmentDelegate.java:189)
       at io.flutter.embedding.android.FlutterFragment.onAttach (FlutterFragment.java:1046)
       at androidx.fragment.app.Fragment.performAttach (Fragment.java:2922)
       at androidx.fragment.app.FragmentStateManager.attach (FragmentStateManager.java:464)
       at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:275)
       at androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:112)
       at androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1647)
       at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3128)
       at androidx.fragment.app.FragmentManager.dispatchCreate (FragmentManager.java:3061)
       at androidx.fragment.app.FragmentController.dispatchCreate (FragmentController.java)
       at androidx.fragment.app.FragmentActivity.onCreate (FragmentActivity.java:276)

Abnormal location:

  void setupFlutterEngine() {
    
    
    Log.v(TAG, "Setting up FlutterEngine.");

    // First, check if the host wants to use a cached FlutterEngine.
    String cachedEngineId = host.getCachedEngineId();
    if (cachedEngineId != null) {
    
    
      flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
      isFlutterEngineFromHost = true;
      if (flutterEngine == null) {
    
    
        throw new IllegalStateException(
            "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
                + cachedEngineId
                + "'");
      }
      return;
    }
    ...
  }

The reason for the analysis should be that after the page is recycled in the background, when the page is reopened, FlutterEngineit is found that it does not exist FlutterEngineCachein . Because cachedEngineIdit was obtained through getArguments()acquisition, it FlutterEnginehas onDetachbeen removed.

  @Override
  public String getCachedEngineId() {
    
    
    return getArguments().getString(ARG_CACHED_ENGINE_ID, null);
  }

So the processing method is to judge whether it exists FragmentActivity.onCreatebefore FlutterEngine, and create it when it does not exist.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        if (!FlutterEngineCache.getInstance().contains("my_engine_id")) {
    
    
            FlutterEngine flutterEngine = new FlutterEngine(this);
            flutterEngine.getDartExecutor().executeDartEntrypoint(
                    DartEntrypoint.createDefault()
            );
            FlutterEngineCache
                    .getInstance()
                    .put("my_engine_id", flutterEngine);
        }
        super.onCreate(savedInstanceState);
        ...
    }
  1. Individual devices appearUnsatisfiedLinkError
Caused by java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: No implementation found for
 void io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(float) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate and Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate__F)
       at java.util.concurrent.FutureTask.report(FutureTask.java:123)
       at java.util.concurrent.FutureTask.get(FutureTask.java:193)
	   at io.flutter.embedding.engine.loader.FlutterLoader.ensureInitializationComplete(FlutterLoader.java:221)

Caused by java.lang.UnsatisfiedLinkError: No implementation found for void io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(float) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate and Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate__F)
       at io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(FlutterJNI.java)
       at io.flutter.embedding.engine.FlutterJNI.updateRefreshRate(FlutterJNI.java:7)
       at io.flutter.embedding.engine.loader.FlutterLoader$1.call(FlutterLoader.java:27)
       at io.flutter.embedding.engine.loader.FlutterLoader$1.call(FlutterLoader.java)
       at java.util.concurrent.FutureTask.run(FutureTask.java:266)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at java.lang.Thread.run(Thread.java:923)

or:

Caused by java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xx/base.apk"],nativeLibraryDirectories=[/data/app/xx/lib/x86, /system/lib, /vendor/lib]]] couldn't find "libflutter.so"

It usually occurs during creation FlutterEngineor FlutterEngineGroup, and there is no better method at present, but you can catch such exceptions and do some bottom-up operations. Avoid direct crashes that affect users' use of other functions. This type of problem accounts for a small proportion. Currently, only two users have reported this exception.

Follow up on this issue can be found here .

hot reload

When debugging the mixed development module (Flutter version 3.10.x), it was found that when there are multiple Flutter pages (using FlutterEngineGroupCreate), hot reloading will cause the App to freeze. I found a related issue , I tried the beta 3.13.0 version and found this fixed. Waiting for the release of stable.

Pack

We all know that the performance of Flutter's debug mode is average, so when handing it over to the test, in order to avoid some experience problems. We can package the Flutter module into a release.

If you use the integration method of relying on Android Archive, you can use the package directly flutter_release. If you directly depend on the source code of the module, you can directly modify flutter/packages/flutter_tools/gradle/flutter.gradlethe source code:

   /**
     * Returns a Flutter build mode suitable for the specified Android buildType.
     *
     * The BuildType DSL type is not public, and is therefore omitted from the signature.
     *
     * @return "debug", "profile", or "release" (fall-back).
     */
    private static String buildModeFor(buildType) {
    
    
        if (buildType.name == "profile") {
    
    
            return "profile"
        } else if (buildType.debuggable) {
    
    
            return "debug"
        }
        return "release"
    }

Just change the above "debug" to "release". iOS flutter/packages/flutter_tools/bin/xcode_backend.dartis modified in .

Of course, direct modification is not very elegant, so you can write a packaging script to handle this operation. For example, using Dart to achieve the following:

// 读取文件内容
File file = File('xxx\flutter\packages\flutter_tools\gradle\flutter.gradle');
String content = file.readAsStringSync();
// 修改文件内容
String newContent = content.replaceAll('return "debug"', 'return "release"// weilu');
// 将修改后的内容写回文件
file.writeAsStringSync(newContent);

After execution, restore it.

other


If there are new problems encountered later, they will also be recorded here synchronously.

reference

Guess you like

Origin blog.csdn.net/qq_17766199/article/details/131992775