(原创)Flutter与Native页面互相跳转

news2024/9/22 3:52:16

前言

实际开发混合项目时,常常会有页面跳转的需求
如果是原生界面和flutter界面需要互相跳转
这种情况应该怎么处理呢?
今天这篇博客主要就来介绍下这个情况
其实想一下,这个问题可以拆成四个小的问题来分析:
1:原生界面自己怎么跳转?
2:Flutter界面互相跳转
2:原生界面跳转Flutter
3:Flutter跳转原生界面
第一点很好理解
拿android来说
就是startActivity
本篇博客主要讲一下后面三点的办法
当然,本篇博客主要讲我知道的方法
如果有更好的方法,欢迎评论区留言

Flutter界面互相跳转

Navigator跳转

Flutter界面互相跳转,一般使用Navigator类
比如我们要跳转到另外一个页面,我们可以这样写:

final result = await Navigator.push(
      context, MaterialPageRoute(builder: (context) => SecondPage(params: "firstpage",)));

使用Navigator.push进行跳转,参数放在目标页面的构造方法内
result 为页面返回后传递的值
而SecondPage退出的时候,则可以这样写:

Navigator.pop(context, "hi good");

第二个参数是返回给上一个页面的数据

getx跳转

除了使用自带的Navigator,我们还可以借助第三方库
先引入getx:

  get: 4.6.0

跳转的时候:

final result = await Get.to(GetxSecondPage(params: 'firstpage',));

返回的时候:

Get.back(result: "hi good");

当然,这是最基础的用法
我们还可以自定义路由
首先我们要使用GetMaterialApp:

class GetxJump extends StatelessWidget {
  const GetxJump({super.key});

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: "/",
      getPages: [
        GetPage(name: '/', page: () => GetxFirstPage()),
        GetPage(name: '/GetxSecondPage', page: () => GetxSecondPage()),
      ],
      // home: GetxFirstPage(),
    );
  }
}

在getPages里定义我们的路由,'/'为默认的第一个页面
这时候就不用配置home属性了
然后GetxFirstPage跳转到GetxSecondPage的时候:

final result = await Get.toNamed("/GetxSecondPage",arguments: "firstpage--arguments");

返回的逻辑还是一样的,用Get.back即可

原生界面跳转Flutter

fragment显示Flutter页面

Flutter的页面,其实都是存在于一个叫做FlutterActivity的安卓页面上的
我们可以打开我们的任意一个Flutter项目,可以看到MainActivity是继承的FlutterActivity
在这里插入图片描述
其实我们也可以把Flutter的页面理解为一个webview
在讲跳转之前,我们先尝试,用一个fragment来显示Flutter页面
我们在Flutter项目中创建一个Activity,布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:gravity="center">

  <FrameLayout
    android:id="@+id/flutterframelayout"
    android:layout_width="match_parent"
    android:layout_height="500dp"
    />

</LinearLayout>

然后我们在Activity的onCreate方法中写如下代码:

    var flutterFragment = FlutterFragment.createDefault()
    supportFragmentManager
      .beginTransaction()
      .add(R.id.flutterframelayout, flutterFragment)
      .commit()

注意,我们的Activity并没有继承FlutterActivity
而是直接继承最基础的AppCompatActivity
这时候我们运行代码,会发现我们也显示出了Flutter的Hello world界面
在这里插入图片描述
这时候flutter的页面大小就是我们fragment的大小,所以看起来有点怪
其实我们还可以让这个flutter页面和我们的Activity通信
这样一个页面就又有原生又有flutter界面了
具体通信可以查看这篇博客:
Flutter与Native通信的方式:MethodChannel
讲这个主要是为了帮助大家更好的认识Flutter
讲完这点,再来介绍一个东西:
FlutterEngine
我们刚刚用FlutterFragment.createDefault()来创建了一个FlutterFragment
其实FlutterFragment还有两种创建方式
这边都列出来:

FlutterFragment.withNewEngine().build<FlutterFragment>()  重新创建一个Engine,指定main方法作为Flutter程序入口
FlutterFragment.createDefault()  使用默认的,指定main方法作为Flutter程序入口
FlutterFragment.withCachedEngine("engine_id")  根据engine_id找到Engine,然后Engine可以配置自己指定的程序入口

可以看到,这里面有一个Engine,那么Engine是什么呢?
这里的Engine其实指的是FlutterEngine
下面就介绍下它

认识Engine

Engine翻译过来有引擎,发动机的意思
说明它的确是Flutter中很重要的一部分
Engine有一个很重要的作用就是负责配置Flutter页面的方法入口
比如,我们在写最简单的Flutter项目时,会发现Flutter的代码都是以main方法作为入口
就像这样:

void main() => runApp(const MyApp());

加上前面说的,Flutter的页面是类似以webview的形式放在安卓的Activity上面的
那么他们是怎么关联起来的呢?
下面就会一一讲到
这部分内容有点多,如果想直接看用法,
可以点到“跳转到一个FlutterActivity”目录查看怎么使用就好了

FlutterFragment如何被创建

我们先看下一个FlutterFragmentActivity是怎么处理的
在FlutterFragmentActivity的onCreate方法里,我们找到了一个ensureFlutterFragmentCreated方法:
在这里插入图片描述
这个方法的代码我贴出来:

  private void ensureFlutterFragmentCreated() {
    if (flutterFragment == null) {
      // If both activity and fragment have been destroyed, the activity restore may have
      // already recreated a new instance of the fragment again via the FragmentActivity.onCreate
      // and the FragmentManager.
      flutterFragment = retrieveExistingFlutterFragmentIfPossible();
    }
    if (flutterFragment == null) {
      // No FlutterFragment exists yet. This must be the initial Activity creation. We will create
      // and add a new FlutterFragment to this Activity.
      flutterFragment = createFlutterFragment();
      FragmentManager fragmentManager = getSupportFragmentManager();
      fragmentManager
          .beginTransaction()
          .add(FRAGMENT_CONTAINER_ID, flutterFragment, TAG_FLUTTER_FRAGMENT)
          .commit();
    }
  }

可以看到,就是调用createFlutterFragment去创建了一个Fragment
其实这个Fragment就是显示我们Flutter页面的Fragment
不信再来看下createFlutterFragment方法:

  
  protected FlutterFragment createFlutterFragment() {
    final BackgroundMode backgroundMode = getBackgroundMode();
    final RenderMode renderMode = getRenderMode();
    final TransparencyMode transparencyMode =
        backgroundMode == BackgroundMode.opaque
            ? TransparencyMode.opaque
            : TransparencyMode.transparent;
    final boolean shouldDelayFirstAndroidViewDraw = renderMode == RenderMode.surface;

    if (getCachedEngineId() != null) {
      Log.v(
          TAG,
          "Creating FlutterFragment with cached engine:\n"
              + "Cached engine ID: "
              + getCachedEngineId()
              + "\n"
              + "Will destroy engine when Activity is destroyed: "
              + shouldDestroyEngineWithHost()
              + "\n"
              + "Background transparency mode: "
              + backgroundMode
              + "\n"
              + "Will attach FlutterEngine to Activity: "
              + shouldAttachEngineToActivity());

      return FlutterFragment.withCachedEngine(getCachedEngineId())
          .renderMode(renderMode)
          .transparencyMode(transparencyMode)
          .handleDeeplinking(shouldHandleDeeplinking())
          .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
          .destroyEngineWithFragment(shouldDestroyEngineWithHost())
          .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
          .build();
    } else {
      Log.v(
          TAG,
          "Creating FlutterFragment with new engine:\n"
              + "Background transparency mode: "
              + backgroundMode
              + "\n"
              + "Dart entrypoint: "
              + getDartEntrypointFunctionName()
              + "\n"
              + "Dart entrypoint library uri: "
              + (getDartEntrypointLibraryUri() != null ? getDartEntrypointLibraryUri() : "\"\"")
              + "\n"
              + "Initial route: "
              + getInitialRoute()
              + "\n"
              + "App bundle path: "
              + getAppBundlePath()
              + "\n"
              + "Will attach FlutterEngine to Activity: "
              + shouldAttachEngineToActivity());

      return FlutterFragment.withNewEngine()
          .dartEntrypoint(getDartEntrypointFunctionName())
          .dartLibraryUri(getDartEntrypointLibraryUri())
          .dartEntrypointArgs(getDartEntrypointArgs())
          .initialRoute(getInitialRoute())
          .appBundlePath(getAppBundlePath())
          .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent()))
          .handleDeeplinking(shouldHandleDeeplinking())
          .renderMode(renderMode)
          .transparencyMode(transparencyMode)
          .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
          .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
          .build();
    }
  }

太长不想看的话直接看结论:
其实就是通过判断getCachedEngineId(缓存的id)是否有缓存的id
然后来创建Fragment
如果有id,withCachedEngine方法会创建一个CachedEngineFragmentBuilder
否则就是withNewEngine创建一个NewEngineFragmentBuilder
两个FragmentBuilder的build方法其实都是利用反射去创建了FlutterFragment
并且在createArgs方法内进行了传参
如下:

    
    public <T extends FlutterFragment> T build() {
      try {
        ("unchecked")
        T frag = (T) fragmentClass.getDeclaredConstructor().newInstance();
        if (frag == null) {
          throw new RuntimeException(
              "The FlutterFragment subclass sent in the constructor ("
                  + fragmentClass.getCanonicalName()
                  + ") does not match the expected return type.");
        }

        Bundle args = createArgs();
        frag.setArguments(args);

        return frag;
      } catch (Exception e) {
        throw new RuntimeException(
            "Could not instantiate FlutterFragment subclass (" + fragmentClass.getName() + ")", e);
      }
    }

等下,这种创建Fragment的方式和我们上面写的fragment显示Flutter页面的例子不是一样吗?
对!
我们上面其实就是自己写了个简单的FlutterFragmentActivity
我们自己创建了FlutterFragment然后显示了出来
而FlutterFragmentActivity默认帮我们创建了,并且默认入口是:main()方法
查看上面代码
发现如果找不到getCachedEngineId
走的是:

FlutterFragment.withNewEngine()

所以我们再来看FlutterFragment创建的三种方式:

FlutterFragment.withNewEngine().build<FlutterFragment>()  重新创建一个Engine,指定main方法作为Flutter程序入口
FlutterFragment.createDefault()  使用默认的,指定main方法作为Flutter程序入口
FlutterFragment.withCachedEngine("engine_id")  根据engine_id找到Engine,然后Engine可以配置自己指定的程序入口

其实第二种内部调用的就是第一种,二者是一样的

  
  public static CachedEngineFragmentBuilder withCachedEngine( String engineId) {
    return new CachedEngineFragmentBuilder(engineId);
  }
  
  
  public static FlutterFragment createDefault() {
    return new NewEngineFragmentBuilder().build();
  }
  
  //可以看到是一样的
  
  public static NewEngineFragmentBuilder withNewEngine() {
    return new NewEngineFragmentBuilder();
  }

那么,NewEngineFragmentBuilder和CachedEngineFragmentBuilder有什么区别呢?

FragmentBuilder

刚刚已经分析了,通过判断getCachedEngineId(缓存的id)是否有缓存的id
有就通过CachedEngineFragmentBuilder创建Fragment
否则通过NewEngineFragmentBuilder创建Fragment
先看CachedEngineFragmentBuilder
它拿到缓存的engineId后通过Bundle 传给了创建的FlutterFragment

Bundle args = new Bundle();
args.putString(ARG_CACHED_ENGINE_ID, engineId);

FlutterFragment通过getCachedEngineId方法得到了engineId

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

那么getCachedEngineId是什么时候调用的呢?
会发现这个方法是来自于FlutterActivityAndFragmentDelegate.Host接口
FlutterFragment刚好实现了这个接口
然后FlutterActivityAndFragmentDelegate的setupFlutterEngine方法中会调用getCachedEngineId
先得到缓存的cachedEngineId
然后用单例类FlutterEngineCache根据cachedEngineId就得到了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;
    }

而setupFlutterEngine方法是被FlutterActivityAndFragmentDelegate的onAttach调用
FlutterActivityAndFragmentDelegate的onAttach方法最终又被FlutterFragment的onAttach方法调用
如下代码:

  
  public void onAttach( Context context) {
    super.onAttach(context);
    delegate = delegateFactory.createDelegate(this);
    delegate.onAttach(context);
    if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) {
      requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
    }
    context.registerComponentCallbacks(this);
  }

这时候我们就清楚了,在FlutterFragment调用onAttach生命周期方法的时候
就根据传递的cachedEngineId得到了缓存的FlutterEngine
那么如果用的是NewEngineFragmentBuilder,
这时候没有cachedEngineId会怎么办呢?其实就会直接创建一个FlutterEngine
所以我们大概了解了
两个FragmentBuilder的区别主要在于创建FlutterFragment是否有缓存的Engineid
然后根据这种不同来决定是否使用缓存的FlutterEngine还是直接创建新的
那么FlutterEngine又有什么作用呢?

FlutterEngine

顾名思义,FlutterEngine是Flutter的引擎和发动机
我们还是看两个NewEngineFragmentBuilder
发现FlutterFragment.withNewEngine()创建了NewEngineFragmentBuilder之后
马上调用了dartEntrypoint(getDartEntrypointFunctionName())方法
里面调用的getDartEntrypointFunctionName方法源码如下

  
  public String getDartEntrypointFunctionName() {
    try {
      Bundle metaData = getMetaData();
      String desiredDartEntrypoint =
          metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
      return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
    } catch (PackageManager.NameNotFoundException e) {
      return DEFAULT_DART_ENTRYPOINT;
    }
  }

可以看到,是根据key来取一个字符串
取不到这返回默认的DEFAULT_DART_ENTRYPOINT
而这个默认的值为

static final String DEFAULT_DART_ENTRYPOINT = "main";

这下我们终于看到了,就是main方法
getDartEntrypointFunctionName得到的字符串
会赋值给NewEngineFragmentBuilder的dartEntrypoint
并最终通过Bundle传递给FlutterFragment

args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint);

FlutterFragment在getDartEntrypointFunctionName方法中得到这个参数
可以看到,得不到的时候默认取main

  
  
  public String getDartEntrypointFunctionName() {
    return getArguments().getString(ARG_DART_ENTRYPOINT, "main");
  }

现在我们可以做一个初步的总结:
FlutterFragmentActivity创建了Fragment用于显示Flutter页面,
然后通过创建NewEngineFragmentBuilder并调用NewEngineFragmentBuilder的dartEntrypoint方法
从而设置了Flutter的程序入口

那么具体是怎么设置的呢?
其实getDartEntrypointFunctionName方法也是来自于FlutterActivityAndFragmentDelegate.Host接口
并且被FlutterActivityAndFragmentDelegate的doInitialFlutterViewRun方法调用
doInitialFlutterViewRun方法又在FlutterActivityAndFragmentDelegate的onStart方法中被调用
FlutterActivityAndFragmentDelegate的onStart方法,被FlutterFragment的onStart方法调用
这时候我们就知道了,归根结底还是FlutterFragment的生命周期
然后我们继续看doInitialFlutterViewRun方法在得到这个字符串后会做什么
重点来了:

    DartExecutor.DartEntrypoint entrypoint =
        libraryUri == null
            ? new DartExecutor.DartEntrypoint(
                appBundlePathOverride, host.getDartEntrypointFunctionName())
            : new DartExecutor.DartEntrypoint(
                appBundlePathOverride, libraryUri, host.getDartEntrypointFunctionName());
    flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint, host.getDartEntrypointArgs());

可以看到,根据getDartEntrypointFunctionName返回的程序入口
从而创建了DartExecutor.DartEntrypoint类
并且调用flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint, host.getDartEntrypointArgs());
从而完成了入口的设置
这时候我们就清楚了,
在FlutterFragment调用onAttach生命周期方法的时候
就根据传递的cachedEngineId得到了缓存的FlutterEngine
没有缓存则创建新的FlutterEngine
然后,FlutterFragment调用onStart生命周期方法的时候
会触发FlutterActivityAndFragmentDelegate的onStart方法里面的doInitialFlutterViewRun方法
方法内部调用getDartEntrypointFunctionName从而得到程序入口
并且把这个入口配置给到了FlutterEngine

那么,下一个问题
NewEngineFragmentBuilder配置的入口可以改吗?
当然!
我们也可以通过调用flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint, host.getDartEntrypointArgs());
完成入口的设置
文章后面就会讲具体怎么设置
回到上面两个FragmentBuilder
我们也可以知道
不管是缓存的,还是新创建的FragmentBuilder
归根结底都要得到一个FlutterEngine
而FlutterEngine才是真正设置入口的类
他的executeDartEntrypoint方法需要传入一个DartEntrypoint类
DartEntrypoint类的dartEntrypointFunctionName属性用来配置入口方法的名称

最后看下executeDartEntrypoint方法吧
内部利用flutterJNI去寻找入口,这块我们就不继续看下去了

  public void executeDartEntrypoint(
       DartEntrypoint dartEntrypoint,  List<String> dartEntrypointArgs) {
    if (isApplicationRunning) {
      Log.w(TAG, "Attempted to run a DartExecutor that is already running.");
      return;
    }

    TraceSection.begin("DartExecutor#executeDartEntrypoint");
    try {
      Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint);
      flutterJNI.runBundleAndSnapshotFromLibrary(
          dartEntrypoint.pathToBundle,
          dartEntrypoint.dartEntrypointFunctionName,
          dartEntrypoint.dartEntrypointLibrary,
          assetManager,
          dartEntrypointArgs);

      isApplicationRunning = true;
    } finally {
      TraceSection.end();
    }
  }

知道了这一块,我们做个最终总结:
1:FlutterFragmentActivity根据Intent是否传递过来缓存的engineid决定采用不同方式创建FlutterFragment
有缓存,则用CachedEngineFragmentBuilder
无缓存,则用NewEngineFragmentBuilder
二者都是利用反射创建了FlutterFragment
并且将engineid传递给了FlutterFragment
2:FlutterFragment根据是否有engineid,来得到一个FlutterEngine
有engineid,通过单例类FlutterEngineCache的Map得到缓存的FlutterEngine
无engineid,创建新的FlutterEngine
这一步在FlutterFragment的onAttach方法执行
3:FlutterFragment的onStart方法判断是否有ARG_DART_ENTRYPOINT参数
没有就默认返回main作为程序入口
ARG_DART_ENTRYPOINT参数的值赋值给了创建的DartExecutor.DartEntrypoint类
并最终被FlutterEngine的executeDartEntrypoint使用了这个类
从而完成入口的配置

这是关于FlutterFragmentActivity的整体逻辑
继承的是FragmentActivity
其实还有另外一个FlutterActivity类
继承的是Activity
并实现FlutterActivityAndFragmentDelegate.Host接口
FlutterActivity类也是可以显示Flutter页面的Activity
和FlutterFragmentActivity有一些区别
这块我没有细看了
貌似他是用FlutterActivityAndFragmentDelegate创建不同的flutterView来显示Flutter页面的
这块感兴趣的可以看下,目前我基本都是用FlutterFragmentActivity
了解了这么多,我们跳转到一个FlutterActivity其实就很简单了

跳转到一个FlutterActivity

和上面讲的NewEngineFragmentBuilder、CachedEngineFragmentBuilder一样
Flutter提供了两个IntentBuilder方便我们跳转
NewEngineIntentBuilder和CachedEngineIntentBuilder
区别其实就是是否有缓存的engineid
下面介绍具体跳转方式

NewEngineIntentBuilder

这个就比较简单了,首先在我们的目标Activity里面创建NewEngineIntentBuilder
注意目标Activity要继承FlutterFragmentActivity

  companion object {
    fun NewEngineIntentBuilder(): NewEngineIntentBuilder {
      return NewEngineIntentBuilder(MainActivity3::class.java)
    }
  }

然后跳转代码为:

      startActivity(MainActivity3
        .NewEngineIntentBuilder()
        .build(this))

可以看到,创建了NewEngineIntentBuilder并调用它的build方法即可
build方法其实就是返回了一个intent

    
    public Intent build( Context context) {
      Intent intent =
          new Intent(context, activityClass)
              .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
              .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
              .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
      if (dartEntrypointArgs != null) {
        intent.putExtra(EXTRA_DART_ENTRYPOINT_ARGS, new ArrayList(dartEntrypointArgs));
      }
      return intent;
    }

通过这种方式,最终目标Activity创建Fragment的时候
用的将会是NewEngineFragmentBuilder
从而创建新的FlutterEngine
而这种跳转页面的方式,只能跳转到Flutter的main方法
也就是不能决定Flutter的程序入口
要改变入口,还是需要FlutterEngine

CachedEngineIntentBuilder

使用CachedEngineIntentBuilder跳转
一样在目标Activity里创建CachedEngineIntentBuilder

  companion object {
    fun withCachedEngine(cachedEngineId: String): CachedEngineIntentBuilder {
      return CachedEngineIntentBuilder(MainActivity3::class.java, cachedEngineId)
    }
  }

跳转的代码这样写:

      startActivity(MainActivity3
        .withCachedEngine(App.your_engine_id)
        .build(this).apply {
          putExtra("method", "showtestpage")
          putExtra("params", "参数params")
        })

其实也是创建了CachedEngineIntentBuilder并调用它的build方法即可
build方法其实也是返回了一个intent

    
    public Intent build( Context context) {
      return new Intent(context, activityClass)
          .putExtra(EXTRA_CACHED_ENGINE_ID, cachedEngineId)
          .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, destroyEngineWithActivity)
          .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
    }

那么区别在哪呢?
就在于withCachedEngine方法内传递的cachedEngineId
这个cachedEngineId需要我们定义好
我这里是定义在Application里
定义好之后,我们再去创建FlutterEngine

  private val flutterEngine by lazy {
    //显示默认的页面,会找flutter的main方法
    FlutterEngine(this).apply {
      dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
    }
  }

然后把创建的FlutterEngine存入到单例类FlutterEngineCache中

FlutterEngineCache.getInstance().put(your_engine_id, flutterEngine)

这里your_engine_id就是key
要和withCachedEngine方法内传递的cachedEngineId保持一致才可以找到对应的FlutterEngine
通过这种方式,最终目标Activity创建Fragment的时候
用的将会是CachedEngineFragmentBuilder
从而找到缓存的FlutterEngine
而这种跳转页面的方式,
可以通过创建DartExecutor.DartEntrypoint类
并且调用flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint, host.getDartEntrypointArgs());
便完成了入口的设置

入口和传参

那么具体怎么设置呢?
其实就在于我们创建FlutterEngine的方式
其实创建FlutterEngine的方式和创建FlutterFragment创建的方式差不多:
FlutterFragment提供这三种方式创建(第一种和第二种其实一样):

FlutterFragment.withNewEngine().build<FlutterFragment>()  重新创建一个Engine,指定main方法作为Flutter程序入口
FlutterFragment.createDefault()  使用默认的,指定main方法作为Flutter程序入口
FlutterFragment.withCachedEngine("engine_id")  根据engine_id找到Engine,然后Engine可以配置自己指定的程序入口

FlutterEngine则是提供三种方式创建(第一种和第二种其实一样):

FlutterEngine(this).apply {
  dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
}

//createDefault内部其实就是这样写的
FlutterEngine(this).apply {
  var entrypoint=DartEntrypoint(FlutterMain.findAppBundlePath(), "main")
  dartExecutor.executeDartEntrypoint(entrypoint)
}

FlutterEngine(this).apply {
  var entrypoint = DartExecutor.DartEntrypoint(
    FlutterMain.findAppBundlePath(), "testMethod")
  dartExecutor.executeDartEntrypoint(entrypoint)
}

这里testMethod就是我们定义的程序入口了
这样在跳转到我们目标Activity后
Flutter就会加载main.dart文件里的testMethod()方法作为程序入口了
注意:
testMethod()必须写在main.dart文件里
这块我暂时也没找到其他方法
页面跳转有了,那么传参呢?
可以看到我刚刚跳转的时候传递了两个参数给Intent

          putExtra("method", "showtestpage")
          putExtra("params", "参数params")

那么在目标Activity的configureFlutterEngine方法里我们可以这样处理:

  override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    var   channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "paramsChannel")
    intent?.apply {
      val path = getStringExtra("method") ?: ""
      val params = getStringExtra("params") ?: ""
      Log.d("MainActivity3", "path是:"+path)
      Log.d("MainActivity3", "params是:"+params)
      channel.invokeMethod(path, params)
    }
  }

会发现其实还是用MethodChannel进行通信
然后来到Flutter端的代码
定义MethodChannel

  var commonNativeChannel = MethodChannel('paramsChannel');

在initState里面:

  
  void initState() {
    commonNativeChannel.setMethodCallHandler((MethodCall call) async {
      print('拿到参数: = ${call.method}');
      switch (call.method) {
        case 'showtestpage':
          print('拿到参数: = ${call.arguments}');
          //这里可以根据参数设置跳转不同page,我这里刷新一下显示,不做跳转处理
          setState(() {
            params = call.arguments.toString();
          });
          break;
        default:
          print('Unknowm method ${call.method}');
          //触发Android端的notImplemented方法
          throw MissingPluginException();
      }
    });
    super.initState();
  }

这样,指定跳转Flutter页面和传参就完成了

Flutter跳转原生界面

Flutter跳转指定原生页面需要用到MethodChannel进行通信
对MethodChannel不了解的可以看我的这篇博客:
Flutter与Native通信的方式:MethodChannel
下面展示具体的用法:
首先是在Flutter端定义好MethodChannel的字段和方法
这里用两个按钮的点击事件来触发跳转
分别调用安卓端的jumpActivity和finish方法
当然也可以传参,具体传参可以看上面的MethodChannel博客

class _MyjumpAndroidPageState extends State<MyjumpAndroidPage> {
  var commonNativeChannel = MethodChannel('ForNativePlugin');
  
  void initState() {
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(onPressed: () {
              commonNativeChannel.invokeMethod("jumpActivity");
            }, child: Text("点击跳转到android页面")),
            ElevatedButton(onPressed: () {
              commonNativeChannel.invokeMethod("finish");
            }, child: Text("点击关闭当前Flutter页面"))
          ],
        ),
      ),
    );
  }
}

然后来到我们安卓端
继承FlutterFragmentActivity类
照样创建MethodChannel
在configureFlutterEngine方法中
对这些被调用的方法进行处理即可
我这边只是打印了下日志
正常情况下StartActivity即可

class MainActivity4 : FlutterFragmentActivity() {
  private lateinit var flutterMethodChannel: MethodChannel

  companion object {
    fun withCachedEngine(cachedEngineId: String): CachedEngineIntentBuilder {
      return CachedEngineIntentBuilder(MainActivity4::class.java, cachedEngineId)
    }
  }

  override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    flutterMethodChannel = MethodChannel(flutterEngine.dartExecutor, "ForNativePlugin")
    flutterMethodChannel.setMethodCallHandler { methodCall, result ->
      when (methodCall.method) {
        "finish" -> {
          Toast.makeText(this, "关闭页面", Toast.LENGTH_SHORT).show()
          finish()
        }
        "jumpActivity" -> {
          Toast.makeText(this, "跳转页面", Toast.LENGTH_SHORT).show()
        }
        else -> {
          // 表明没有对应实现
          result.notImplemented()
        }
      }
    }
  }
}

源码

关于Flutter与Native页面互相跳转大体上就是这么多内容了
下面分享下这篇文章涉及到的源码地址:
new_gradlesetting_native_jump_flutter

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/855688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【工作记录】docker安装gitlab、重置密码@20230809

前言 本文记录下基于docker安装gitlab并重置管理员密码的过程。 作为记录的同时也希望能帮助到需要的朋友们。 搭建过程 1. 准备好docker环境并启动docker [rootslave-node1 docker-gitlab]# docker version Client:Version: 18.06.1-ceAPI version: 1.38…

Profibus-DP转modbus RTU网关modbus rtu和tcp的区别

捷米JM-DPM-RTU网关在Profibus总线侧实现主站功能&#xff0c;在Modbus串口侧实现从站功能。可将ProfibusDP协议的设备&#xff08;如&#xff1a;EH流量计、倍福编码器等&#xff09;接入到Modbus网络中&#xff1b;通过增加DP/PA耦合器&#xff0c;也可将Profibus PA从站接入…

智能质检技术的核心环节:语音识别和自然语言处理

随着呼叫中心行业的快速发展和客户服务需求的不断提高&#xff0c;越来越多的企业开始采用智能质检技术&#xff0c;以提高呼叫中心的质量和效率。而在智能质检技术中&#xff0c;语音识别和自然语言处理是其核心环节&#xff0c;对于提高质检的准确性和效率具有重要作用。 语音…

el-select与el-tree结合使用,实现select框下拉使用树形结构选择数据

使用el-select与el-tree&#xff0c;实现如下效果&#xff0c; 代码如下&#xff1a; 注意点&#xff1a;搜索input框的代码一点放在option上面&#xff0c;不要放在option里面&#xff0c;否则一点击搜索框&#xff0c;下拉框就会收起来&#xff0c;不能使用。 <el-select…

基于 JMeter API 开发性能测试平台

目录 背景&#xff1a; 常用的 JMeter 类和功能的解释&#xff1a; JMeter 编写性能测试脚本的大致流程示意图&#xff1a; 源码实现方式&#xff1a; (1) 环境初始化 (2) 环境初始化 (3) 创建测试计划 (4) 创建 ThreadGroup (5) 创建循环控制器 (6) 创建 Sampler (…

ArcGIS Pro暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例应用

GIS是利用电子计算机及其外部设备&#xff0c;采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲&#xff0c;它是在一定的地域内&#xff0c;将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来&#xff0c;达到对地理和属性信息的综合管理。GIS的…

数据挖掘具体步骤

数据挖掘具体步骤 1、理解业务与数据 2、准备数据 数据清洗&#xff1a; 缺失值处理&#xff1a; 异常值: 数据标准化&#xff1a; 特征选择&#xff1a; 数据采样处理&#xff1a; 3、数据建模 分类问题&#xff1a; 聚类问题&#xff1a; 回归问题 关联分析 集成学习 image B…

linux - 用户权限

认知root用户 无论是Windows、Macos、Linux均采用多用户的管理模式进行权限管理 在Linux系统中,拥有最大权限的账户名为: root(超级管理员) root用户拥有最大的系统操作权限&#xff0c;而普通用户在许多地方的权限是受限的。普通用户的权限&#xff0c;一般在其HOME目录内是不…

Simulation 线性静力分析流程

有限元仿真分析软件有很多&#xff0c;但是分析的流程却是大同小异&#xff0c;今天给大家分享的是Simulation的线性静力分析流程。 1.构思分析方案。 确定研究对象&#xff0c;研究的方法、验证方案等等。听起来比较空洞&#xff0c;实践过程中我建议首先需要把目标和有限元分…

24届近5年东南大学自动化考研院校分析

今天给大家带来的是东南大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、东南大学 学校简介 东南大学是我国最早建立的高等学府之一&#xff0c;素有“学府圣地”和“东南学府第一流”之美誉。东南大学前身是创建于1902年的三江师范学堂。1921年经近代著名教育家…

软件测试的49种方法

1. α测试_Alpha测试 α测试&#xff0c;英文是Alpha testing。又称Alpha测试。 Alpha测试是由一个用户在开发环境下进行的测试&#xff0c;也可以是公司内部的用户在模拟实际操作环境下进行的受控测试&#xff0c;Alpha测试不能由该系统的程序员或测试员完成。 在系统开发接近…

【BASH】回顾与知识点梳理(十四)

【BASH】回顾与知识点梳理 十四 十四. 文件与目录的默认权限与隐藏权限14.1 文件预设权限&#xff1a;umaskumask 的利用与重要性&#xff1a;专题制作 14.2 文件隐藏属性chattr (配置文件案隐藏属性)lsattr (显示文件隐藏属性) 14.3 文件特殊权限&#xff1a; SUID, SGID, SBI…

短视频内容平台(如TikTok、Instagram Reel、YouTube Shorts)的系统设计

现在&#xff0c;短视频内容已成为新趋势&#xff0c;每个人都在从TikTok、Instagram、YouTube等平台上消费这些内容。让我们看看如何为TikTok创建一个系统。 这样的应用程序看起来很小&#xff0c;但在后台有很多事情正在进行。以下是相关的挑战&#xff1a; •由于该应用程序…

【Linux命令行与Shell脚本编程】第十八章 文本处理与编辑器基础

Linux命令行与Shell脚本编程 第十八章 文本处理与编辑器基础 文章目录 Linux命令行与Shell脚本编程第十八章 文本处理与编辑器基础 文本处理与编辑器基础8.1.文本处理8.1.1.sed编辑器8.1.1.1.在命令行中定义编辑器命令8.1.1.2.在命令行中使用多个编辑器命令8.1.1.3.从文件中读…

项目8.9总结

这两天写了评论和用户之间的关注&#xff0c;然后就是歌曲的上传&#xff0c;歌曲的上传很简陋&#xff0c;并且还有点问题 关注 歌曲上传

[Vue] Vue2和Vue3的生命周期函数

vue2有11个生命周期钩子, vue3有8个生命周期钩子 从vue创建、运行、到销毁总是伴随着各种事件, 创建、挂载、更新到销毁。 1.vue2系列生命周期 ⑴【beforecreate】实例创建前。 vue完全创建之前&#xff0c;会自动执行这个函数。 ⑵【Created】实例创建后。 这也是个生命…

OKHTTP缓存问题记录

使用okhttp的缓存功能 okhttp有一个功能就是适配http协议里面cache-control头 但是在实际使用的过程中&#xff0c;发现非文本内容&#xff0c;如果不对Response中的inputstream读取&#xff0c;就不会缓存下来 比如使用如下代码读取后才能缓存到cache目录 ResponseBody bod…

冷链冷藏仓储系统解决方案|海格里斯HEGERLS四向穿梭立体库助力冷链物流新模式

随着现代物流产业的迅速发展&#xff0c;四向穿梭车立体库因其在仓储体系中所具有的高效密集存储功能优势、运作成本优势与系统化智能化管理优势&#xff0c;已经成为自动化立体库的主流形式之一。而随着国内外仓储物流整体规模和低温产品消费需求的稳步增长&#xff0c;冷链市…

关于使用pycharm遇到只能使用unittest方式运行,无法直接选择Run

相信大家可能都遇到过这个问题&#xff0c;使用pycharm直接运行脚本的时候&#xff0c;只能选择unittest的方式&#xff0c;能愁死个人 经过几次各种尝试无果之后&#xff0c;博主就放弃死磕了&#xff0c;原谅博主是个菜鸟 后来遇到这样的问题&#xff0c;往往也就直接使用cm…

【ChatGPT 指令大全】销售怎么借力ChatGPT提高效率

目录 销售演说 电话销售 产出潜在客户清单 销售领域计划 销售培训计划 总结 随着人工智能技术的不断进步&#xff0c;我们现在有机会利用ChatGPT这样的智能助手来改进我们的销售工作。在接下来的时间里&#xff0c;我将为大家介绍如何运用ChatGPT提高销售效率并取得更好的…