(原创)Flutter与Native页面互相跳转
前言
实际开发混合项目时,常常会有页面跳转的需求
如果是原生界面和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"><FrameLayoutandroid: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方法:
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();}}
protected
太长不想看的话直接看结论:
其实就是通过判断getCachedEngineId(缓存的id)是否有缓存的id
然后来创建Fragment
如果有id,withCachedEngine方法会创建一个CachedEngineFragmentBuilder
否则就是withNewEngine创建一个NewEngineFragmentBuilder
两个FragmentBuilder的build方法其实都是利用反射去创建了FlutterFragment
并且在createArgs方法内进行了传参
如下:
<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);}}
public
等下,这种创建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可以配置自己指定的程序入口
其实第二种内部调用的就是第一种,二者是一样的
static CachedEngineFragmentBuilder withCachedEngine( String engineId) {return new CachedEngineFragmentBuilder(engineId);} public static FlutterFragment createDefault() {return new NewEngineFragmentBuilder().build();}//可以看到是一样的 public static NewEngineFragmentBuilder withNewEngine() {return new NewEngineFragmentBuilder();}
public
那么,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
String getCachedEngineId() {return getArguments().getString(ARG_CACHED_ENGINE_ID, null);}
public
那么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方法调用
如下代码:
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);}
public
这时候我们就清楚了,在FlutterFragment调用onAttach生命周期方法的时候
就根据传递的cachedEngineId得到了缓存的FlutterEngine
那么如果用的是NewEngineFragmentBuilder,
这时候没有cachedEngineId会怎么办呢?其实就会直接创建一个FlutterEngine
所以我们大概了解了
两个FragmentBuilder的区别主要在于创建FlutterFragment是否有缓存的Engineid
然后根据这种不同来决定是否使用缓存的FlutterEngine还是直接创建新的
那么FlutterEngine又有什么作用呢?
FlutterEngine
顾名思义,FlutterEngine是Flutter的引擎和发动机
我们还是看两个NewEngineFragmentBuilder
发现FlutterFragment.withNewEngine()创建了NewEngineFragmentBuilder之后
马上调用了dartEntrypoint(getDartEntrypointFunctionName())方法
里面调用的getDartEntrypointFunctionName方法源码如下
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;}}
public
可以看到,是根据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
String getDartEntrypointFunctionName() {return getArguments().getString(ARG_DART_ENTRYPOINT, "main");}
public
现在我们可以做一个初步的总结:
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
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;}
public
通过这种方式,最终目标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
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);}
public
那么区别在哪呢?
就在于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: MethodChannelcompanion 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
相关文章:

(原创)Flutter与Native页面互相跳转
前言 实际开发混合项目时,常常会有页面跳转的需求 如果是原生界面和flutter界面需要互相跳转 这种情况应该怎么处理呢? 今天这篇博客主要就来介绍下这个情况 其实想一下,这个问题可以拆成四个小的问题来分析: 1:原生界…...

web集群学习--基于CentOS构建LVS-DR集群、配置nginx负载均衡
基于CentOS构建LVS-DR集群 环境准备 主机名 ip地址 node1 192.168.1.140 client node2 192.168.1.141 LVS node3 192.168.1.142 RS1 node4 192.168.1.143 RS2配置 1.关闭防火墙和SELinux [rootclient~]# systemctl stop firewalld [rootclient~]# systemctl disabl…...

基于 FPGA 的电机控制
FPGA 非常适合精密电机控制,在这个项目中,我们将创建一个简单的电机控制程序,在此基础上可以构建更复杂的应用。 需要的硬件 Digilent Pmod HB3 介绍 我们可以用一个简单的 8 位微控制器来控制电机,输出一个简单的脉宽调制波形。然…...

STM32F429IGT6使用CubeMX配置IIC通信(AT2402芯片)
1、硬件电路 写地址:0xA0 读地址:0xA1 存储容量:256Byte 2、设置RCC,选择高速外部时钟HSE,时钟设置为180MHz 3、配置IIC 4、生成工程配置 5、部分代码 #define IIC_WRITE_ADDR 0xA0 // IIC写地址 #define IIC_READ_ADDR 0xA1 …...

JS逆向系列之猿人学爬虫第14题-备而后动-勿使有变
文章目录 题目地址参数分析参考jspython 调用往期逆向文章推荐题目地址 https://match.yuanrenxue.cn/match/14题目难度标的是困难,主要难在js混淆部分。 参数分析 初始抓包有无限debugger反调试,可以直接hook 函数构造器过掉无限debugger Function.prototype.__construc…...
学cpp看的那点书
C C Primer 语言基础学习 C Templates The Complete Guide (2nd Edition) 学习模板,更好的阅读 STL 源码,毕竟C 标准库大部分是模板。 The C Standard Library 全称 The C Standard Library A Tutorial and Reference Second Edition简单的了解标…...

【C++】常用容器-string容器
1.string基本概念 2.string构造函数 #include <iostream> using namespace std;//string容器 void test01() {string s1;//创建空字符串,调用无参构造函数cout << "str1 " << s1 << endl;//什么都不输出const char* str "…...

SSH无法连接kali,拒绝密码
1,cd /etc/ssh 2,systemctl start ssh.server 3,vim /etc/ssh/sshd_config 将黄色文字改成这样 4,systemctl restart ssh 然后去连接就好了...

竞赛项目 深度学习的口罩佩戴检测 - opencv 卷积神经网络 机器视觉 深度学习
文章目录 0 简介1 课题背景🚩 2 口罩佩戴算法实现2.1 YOLO 模型概览2.2 YOLOv32.3 YOLO 口罩佩戴检测实现数据集 2.4 实现代码2.5 检测效果 3 口罩佩戴检测算法评价指标3.1 准确率(Accuracy)3.2 精确率(Precision)和召回率(Recall)3.3 平均精…...

redis 数据结构(一)
Redis 为什么那么快 redis是一种内存数据库,所有的操作都是在内存中进行的,还有一种重要原因是:它的数据结构的设计对数据进行增删查改操作很高效。 redis的数据结构是什么 redis数据结构是对redis键值对值的数据类型的底层的实现,…...

【高频面试题】JVM篇
文章目录 一、JVM组成1.什么是程序计数器2.什么是Java堆?3.能不能介绍一下方法区(元空间)4.你听过直接内存吗5.什么是虚拟机栈6.垃圾回收是否涉及栈内存?7.栈内存分配越大越好吗?8.方法内的局部变量是否线程安全?9.什么…...

第十三次CCF计算机软件能力认证
第一题:跳一跳 近来,跳一跳这款小游戏风靡全国,受到不少玩家的喜爱。 简化后的跳一跳规则如下:玩家每次从当前方块跳到下一个方块,如果没有跳到下一个方块上则游戏结束。 如果跳到了方块上,但没有跳到方块的…...

无人驾驶实战-第十二课(强化学习自动驾驶系统)(完)
在七月算法上报了《无人驾驶实战》课程,老师讲的真好。好记性不如烂笔头,记录一下学习内容。 课程入口,感兴趣的也可以跟着学一下。 ————————————————————————————————————————— 强化学习ÿ…...
【flask sqlalchmey】一次性将返回的列表对象或者 一行数据对象转成dict---flask-sqlalchemy输出json格式数据
def model_to_dict(object):return {c.name: getattr(object, c.name) for c in object.__table__.columns}#将一组数据转为list def scalars_to_list(object):return [model_to_dict(c) for c in object]class Sysdict(Base,SerializerMixin):__bind_key__ forest_fire_contr…...

goland插件推荐Rider UI Theme Pack
推荐一个goland配色插件Rider UI Theme Pack,里面自带visual assist配色,配色截图如下: 直接在plugins里面进行搜索或者在插件home page下载后进行安装均可。 总算找到一统vscode 和goland二者优势的插件了。...
人工智能面试常识-10
目录 1. 人工智能的常见用途和应用有哪些? 2. 什么是智能代理,它们如何在人工智能中使用?...

Android JNI开发从0到1,java调C,C调Java,保姆级教程详解
前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。 👉点击跳转到教程 第一步首先配置Android studio的NDK开发环境,首先在Android studio中下载NDK…...

STM32基于CubeIDE和HAL库 基础入门学习笔记:功能驱动与应用
文章目录: 一:LED与按键驱动程序 main.c 1.闪灯 led.h led.c 2.按键控制LED亮灭 key.h key.c 二:蜂鸣器与继电器驱动程序 main.c 1.蜂鸣器 buzzer.h buzzer.c delay.h delay.c 2.继电器 relay.h relay.c 三࿱…...
创建型模式 (Creational Patterns) 玄子Share 设计模式 GOF 全23种 + 七大设计原则
玄子Share 设计模式 GOF 全23种 七大设计原则 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWLAOFtO-1691793071647)(./assets/%E7%8E%84%E5%AD%90Share%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20GOF%20%E5%85%A823%E7%A7%8D%20%20%E4%B8%83%E…...

【脚踢数据结构】队列(顺序和链式)
(꒪ꇴ꒪ ),Hello我是祐言QAQ我的博客主页:C/C语言,Linux基础,ARM开发板,软件配置等领域博主🌍快上🚘,一起学习,让我们成为一个强大的攻城狮!送给自己和读者的一句鸡汤🤔&…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...