(原創)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方法:

  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: 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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35252.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35252.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35252.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

什么是全局代理,手機怎么設置全局代理

目錄 什么是全局代理 全局代理的優缺點 優點 缺點 手機怎么設置全局代理 注意事項 總結 在計算機網絡和信息安全中&#xff0c;全局代理是一種常用的技術手段&#xff0c;用于將網絡流量通過代理服務器進行轉發和處理。本文將介紹什么是全局代理&#xff0c;探討全局代理…

pyspark筆記 pyspark.sql.functions

col qqpyspark 筆記 pyspark.sql.function col VS select_UQI-LIUWJ的博客-CSDN博客 取某一列 lit 創建一個包含指定值的列 date_trunc 將日期截取成由第一個參數指定的字符串值 year, yyyy, yy——截取到年month,mon,mm——截取到月day,dd ——截取到天microsecondmillis…

SpringBoot WebSocket配合react 使用消息通信

引入websocket依賴 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>配置websocket import org.springframework.context.annotation.Bean; import org.spr…

Highcharts引入

Highcharts是和jQuery一起使用的&#xff0c;所以需要下載好jQuery jQuery下載方式&#xff1a;訪問&#xff1a;http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js&#xff0c;然后全選復制到自己新建的txt文檔中&#xff0c;最后把擴展名改為js。 Highcharts下載方式&…

pytest運行時參數說明,pytest詳解,pytest.ini詳解

一、Pytest簡介 1.pytest是一個非常成熟的全功能的Python測試框架&#xff0c;主要有一下幾個特點&#xff1a; 簡單靈活&#xff0c;容易上手&#xff0c;支持參數化 2.能夠支持簡單的單元測試和復雜的功能測試&#xff0c;還可以用來做selenium、appium等自動化測試&#xf…

使用sqlplus連接oracle,提示ORA-01034和ORA-27101

具體內容如下 PL/SQL Developer 處 登錄時 終端處 登錄時 ERROR: ORA-01034: ORACLE not available ORA-27101: shared memory realm does not exist Process ID: 0 Session ID: 0 Serial number: 0 解決方法是執行以下命令 sqlplus /nolog conn / as sysdba startup …

【Hilog】鴻蒙系統日志源碼分析

【Hilog】鴻蒙系統日志源碼分析 Hilog采用C/S結構&#xff0c;Hilogd作為服務端提供日志功能。Client端通過API調用&#xff08;最終通過socket通訊&#xff09;與HiLogd打交道。簡易Block圖如下。 這里主要分析一下。Hilog的讀、寫、壓縮落盤&#xff0c;以及higlog與android…

學術論文GPT源碼解讀:從chatpaper、chatwithpaper到gpt_academic

前言 之前7月中旬&#xff0c;我曾在微博上說準備做“20個LLM大型項目的源碼解讀” 針對這個事&#xff0c;目前的最新情況是 已經做了的&#xff1a;LLaMA、Alpaca、ChatGLM-6B、deepspeedchat、transformer、langchain、langchain-chatglm知識庫準備做的&#xff1a;chatpa…

GitHub上受歡迎的Android UI Library

內容 抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新ViewPager圖表(Chart)菜單(Menu)浮動菜單對話框空白頁滑動刪除手勢操作RecyclerViewCardColorDrawableSpinner布局模糊效果TabBarAppBar選擇器(Picker)跑馬燈日歷時間主題樣式ImageView通知聊…

chapter 1 formation of crystal, basic concepts

chapter 1 晶體的形成 1.1 Quantum Mechanics and atomic structure 1.1.1 Old Quantum Theory problems of planetary model: atom would be unstableradiate EM wave of continuous frequency to solve the prablom of planetary model: Bohr: Quantum atomic structureP…

React 實現文件分片上傳和下載

React 實現文件分片上傳和下載 在開發中&#xff0c;文件的上傳和下載是常見的需求。然而&#xff0c;當面對大型文件時&#xff0c;直接的上傳和下載方式可能會遇到一些問題&#xff0c;比如網絡傳輸不穩定、文件過大導致傳輸時間過長等等。為了解決這些問題&#xff0c;我們…

Vue中自定義.js變量

1、定義.js文件 order.js文件內容&#xff1a; // 訂單是否報賬 const EXPENESS_STATUS_NO0; const EXPENESS_STATUS_YES1; // 狀態 0-未發貨 1-發貨 2-確認收獲 const STATUS_NO0; const STATUS_SEND1; const STATUS_DELIVERY2; // 如何不加這個&#xff0c;vue中引…

yolov5、YOLOv7、YOLOv8改進:注意力機制CA

論文題目&#xff1a;《Coordinate Attention for Efficient Mobile NetWork Design》論文地址&#xff1a; https://arxiv.org/pdf/2103.02907.pdf 本文中&#xff0c;作者通過將位置信息嵌入到通道注意力中提出了一種新穎的移動網絡注意力機制&#xff0c;將其稱為“Coordin…

Nagle算法--網絡優化算法

Nagle Nagle算法是一種網絡優化算法&#xff0c;旨在減少小數據包的網絡傳輸次數&#xff0c;提高網絡傳輸效率。該算法由John Nagle在1984年提出&#xff0c;并被廣泛應用于TCP協議中。 Nagle算法的原理是將較小的數據包進行緩存&#xff0c;在緩存數據包的發送時機到來時&am…

拓撲布局和建立小型網絡

練習 2.6.1&#xff1a;拓撲布局和建立小型網絡 地址表 本實驗不包括地址表。 拓撲圖 學習目標 正確識別網絡中使用的電纜物理連接點對點交換網絡驗證每個網絡的基本連通性 簡介&#xff1a; 許多網絡問題都可以在網絡的物理層解決。因此&#xff0c;必須清楚了解網絡連接…

Python數據分析實戰-列表字符串、字符串列表、字符串的轉化(附源碼和實現效果)

實現功能 str([None,master,hh]) ---> [None,"master","hh"] ---> "None,master,hh" 實現代碼 import re import astx1 str([None,master,hh]) print(x1)x2 ast.literal_eval(x1) print(x2)x3 ",".join(str(item) for item…

阿里云服務器是什么?阿里云服務器有什么優缺點?

阿里云服務器是什么&#xff1f;云服務器ECS是一種安全可靠、彈性可伸縮的云計算服務&#xff0c;云服務器可以降低IT成本提升運維效率&#xff0c;免去企業或個人前期采購IT硬件的成本&#xff0c;阿里云服務器讓用戶像使用水、電、天然氣等公共資源一樣便捷、高效地使用服務器…

Controller是線程安全嗎?如何實現線程安全

測試是否是線程安全 RequestMapping("/test") RestController public class TestController {//1、定義num&#xff0c;判斷不同線程訪問的時候&#xff0c;num的返回結果是否一致private Integer num0;/*** 2、定義兩個方法*/GetMapping("/count1")publi…

【UE4 RTS】08-Setting up Game Clock

前言 本篇實現的效果是在游戲運行后能夠記錄當前的游戲時間&#xff08;年月日時分秒&#xff09;&#xff0c;并且可以通過修改變量從而改變游戲時間進行的快慢。 效果 步驟 1. 在Blueprints文件夾中新建如下兩個文件夾&#xff0c;分別命名為“GameSettings”、“Player”…

JZ33二叉搜索樹的后序遍歷序列

題目地址&#xff1a;二叉搜索樹的后序遍歷序列_牛客題霸_牛客網 題目回顧&#xff1a; 解題思路&#xff1a; 使用棧 棧的特點是&#xff1a;先進后出。 通讀題目后&#xff0c;我們可以得出&#xff0c;二叉搜索樹是左子節點小于根節點&#xff0c;右子節點大于根節點。 …