android原生混編Flutter

花了一天時(shí)間初步進(jìn)行接入,有些坑還沒有踩全。包括第一次加載flutter界面的時(shí)候顯示很慢等。
以下是代碼集成方式,后續(xù)探索產(chǎn)物集成方式。


image

接入步驟

  • 第一步、新建android原生項(xiàng)目

  • 第二步、新建Flutter Module

    1. 通過(guò)命令行創(chuàng)建。切換到android項(xiàng)目的同級(jí)目錄下(這里建議直接使用Terminal)。執(zhí)行如下命令:

      flutter create -t module my_flutter
      

      其中my_flutter為改module名字。

    2. 直接使用AS創(chuàng)建。File --> New --> New Flutter Project,然后選擇Flutter Module。然后填寫module的名稱、路徑。最后填寫module的包名,點(diǎn)擊Finish就創(chuàng)建好了一個(gè)Flutter Module。

  • 第三步在android項(xiàng)目中引入 Flutter Module

    1. 在app下的build.gradle文件中添加以下配置

      compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
      }
      

      可以解決版本兼容性問題。如果不配置可能會(huì)報(bào)錯(cuò)Invoke-customs are only supported starting with Android O (--min-api 26)。

    2. 在項(xiàng)目的根目錄下的setting.gradle文件中配置

      include ':app'
      // 加入下面配置
      setBinding(new Binding([gradle: this]))
      evaluate(new File(
              settingsDir.parentFile,
              'FlutterDemo/my_flutter/.android/include_flutter.groovy'
      ))
      

      需要修改為自己的module名字。

    3. 編譯成功后在app的build.gradle文件下添加依賴。

      implementation project(':flutter')
      

android 與 Flutter交互

Flutter在1.12版本后舍棄了部分類,導(dǎo)致交互這里有較大變動(dòng)。

android原生跳轉(zhuǎn)到Flutter界面

一、Activity形式

  1. Activity形式。新建一個(gè)Android Activity。進(jìn)行跳轉(zhuǎn)。代碼如下:

    public class FlutterPageActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_flutter_page);
            initViews();
        }
    
        private void initViews() {
            FlutterView flutterView = new FlutterView(this);
            FrameLayout.LayoutParams lp =  new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            FrameLayout flContainer = findViewById(R.id.fl_container);
            flContainer.addView(flutterView, lp);
            FlutterEngine flutterEngine = new FlutterEngine(this);
            flutterEngine.getNavigationChannel().setInitialRoute("route1");
            flutterEngine.getDartExecutor().executeDartEntrypoint(
                    DartExecutor.DartEntrypoint.createDefault()
            );
            flutterView.attachToFlutterEngine(flutterEngine);
        }
    }
    
    • 因?yàn)閕o.flutter.facade,此處采用FlutterView(繼承自FrameLayout)替代了原來(lái)的Flutter.createView();

    • **attachToFlutterEngine(FlutterEngine flutterEngine) ** 方法的作用就是flutter的ui顯示到FlutterView中。

    • FlutterEngine 負(fù)責(zé)在android端執(zhí)行Dart代碼的引擎。

    • flutterEngine.getNavigationChannel().setInitialRoute("route1"); 設(shè)置界面路由。如果不設(shè)置默認(rèn)是“/”界面。

    • 傳參可以采用類似get拼接的形式,例如:

      "route1?{\"name\":\"LiLei\"}"
      

      將路由和參數(shù)采用?進(jìn)行隔開,后續(xù)可以添加上json字符串。在Flutter 端解析的時(shí)候采用window.defaultRouteName 獲取路由名稱和參數(shù)。

      String url = window.defaultRouteName;
      // route名稱
      String route =
          url.indexOf('?') == -1 ? url : url.substring(0, url.indexOf('?'));
      // 參數(shù)Json字符串
      String paramsJson =
          url.indexOf('?') == -1 ? '{}' : url.substring(url.indexOf('?') + 1);
      // 解析參數(shù)
      Map<String, dynamic> params = json.decode(paramsJson);
      
    • 在flutter中路由的簡(jiǎn)單寫法。

      void main() => runApp(_widgetForRoute(window.defaultRouteName));
      Widget _widgetForRoute(String route) {
        switch (route) {
          case 'route1':
            return MyApp();
          default:
            return Center(
              child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
            );
        }
      }
      
  2. 創(chuàng)建好Activity后原生界面A 就可以采用Intent的方式進(jìn)行跳轉(zhuǎn)。

    Intent intent = new Intent(MainActivity.this, FlutterPageActivity.class);
    startActivity(intent);
    

二、Fragment形式

  1. 新建一個(gè)Fragment 繼承自Fragment。

  2. 在fragment的onCreate方法中創(chuàng)建FlutterView。具體方法和Activity中相同。

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
       FlutterView flutterView = new FlutterView(getContext());
       FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
       mFlutterEngine = new FlutterEngine(getContext());
       mFlutterEngine.getNavigationChannel().setInitialRoute("route1");
       mFlutterEngine.getDartExecutor().executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
       );
       flutterView.attachToFlutterEngine(mFlutterEngine);
       return flutterView;
     }
    
    • 這里將FlutterEngine方法設(shè)置為成員變量為了后續(xù)界面間傳值。
  3. 在原生Activity中添加加載方法。

    public class FlutterFragmentActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_flutter_fragment);
            initViews();
        }
    
        private void initViews() {
            //寫法1
            getSupportFragmentManager().beginTransaction().replace(R.id.fl_container, new FlutterPageFragment()).commit();
    
            //寫法2
            FlutterFragment fragment = FlutterFragment.withNewEngine().initialRoute("route1").build();
            getSupportFragmentManager().beginTransaction().replace(R.id.fl_container, fragment).commit();
    
            //寫法3
            //通過(guò)FlutterFragment引入Flutter編寫的頁(yè)面
            FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
                    .initialRoute("route2")
                    .build();
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.fl_container, flutterFragment)
                    .commit();
        }
    }
    
    • 寫法3中 在androidx環(huán)境中 flutterFragment需要繼承自androidx的fragment。

Flutter界面跳轉(zhuǎn)到Android原生界面

  1. 在布局中添加一個(gè)按鈕。

    static const nativeChannel = const MethodChannel('com.example.flutter/native');
    

    首先要定義一個(gè)channel,全局唯一,需要和android端約定好。

    RaisedButton(
         child: Text('跳轉(zhuǎn)到原生界面'),
         onPressed:() {
         // 返回給上一頁(yè)的數(shù)據(jù)
         Map<String, dynamic> result = {'name': '我從Flutter頁(yè)面過(guò)來(lái)了'};
         nativeChannel.invokeMethod('jumpToNative', result);
    }),
    
    • dart采用map形式發(fā)送。定義好key value
    • 通過(guò)nativeChannel.invokeMethod('jumpToNative', result); 方法傳遞出去。其中第一個(gè)參數(shù)是和android端預(yù)定好的方法名。
  2. 在fragment中進(jìn)行接收

    private static final String CHANNEL_NATIVE = "com.example.flutter/native";
    
    @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            MethodChannel nativeChannel = new MethodChannel(mFlutterEngine.getDartExecutor(), CHANNEL_NATIVE);
            nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
                @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                    switch (methodCall.method) {
                        case "jumpToNative":
                            // 跳轉(zhuǎn)原生頁(yè)面
                            Intent jumpToNativeIntent = new Intent(getActivity(), NativeActivity.class);
                            jumpToNativeIntent.putExtra("name", (String) methodCall.argument("name"));
                             //因?yàn)閷懙膁emo所以直接采用了魔法數(shù)字 方便文章中看的直觀
                            startActivityForResult(jumpToNativeIntent, 1001);
                            break;
                        default:
                            result.notImplemented();
                            break;
                    }
                }
            });
        }
    

    在實(shí)現(xiàn)Flutter頁(yè)面跳轉(zhuǎn)Android原生頁(yè)面之前首先介紹一下Platform Channel,它是Flutter和原生通信的工具,有三種類型:

    • BasicMessageChannel:用于傳遞字符串和半結(jié)構(gòu)化的信息,F(xiàn)lutter和平臺(tái)端進(jìn)行消息數(shù)據(jù)交換時(shí)候可以使用。
    • MethodChannel:用于傳遞方法調(diào)用(method invocation),F(xiàn)lutter和平臺(tái)端進(jìn)行直接方法調(diào)用時(shí)候可以使用。
    • EventChannel:用于數(shù)據(jù)流(event streams)的通信,F(xiàn)lutter和平臺(tái)端進(jìn)行事件監(jiān)聽、取消等可以使用。

    Flutter 跳轉(zhuǎn)到原生界面主要通過(guò)MethodChannel來(lái)實(shí)現(xiàn)。

    • 在創(chuàng)建MethodChannel的時(shí)候需要傳入一個(gè)常量。需要保證唯一性。和dart端約定好采用一樣的常量。就是上文中提到的nativeChannel 。
    • 第一個(gè)參數(shù)是BinaryMessenger類型。需要通過(guò)mFlutterEngine.getDartExecutor()方法獲取到。
      • 這里有兩個(gè)前提:
        • a. 需要界面加載完成后。所以我們寫在onViewCreated中;
        • b. 需要從flutterEngine中獲取,所以我們將該變量改成成員變量。
    • 通過(guò)setMethodCallHandler回調(diào)方式判斷返回的方法名。在進(jìn)行相應(yīng)處理。
    • 獲取到想要的方法名之后,使用intent將獲取到的數(shù)據(jù)發(fā)送出去。
    • 這里采用startActivityForResult 方法是因?yàn)樵谠缑嬷凶隽藬?shù)據(jù)回調(diào)。

從android原生界面返回到Flutter界面數(shù)據(jù)傳遞

  1. 接上面的跳轉(zhuǎn)原生界面成功后,在原生界面中添加按鈕進(jìn)行返回并傳值。

    tv_demo.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent();
                    intent.putExtra("message", "我從原生頁(yè)面回來(lái)了");
                    setResult(RESULT_OK, intent);
                    finish();
                }
    });
    
  2. 在上訴的fragment的onActivityResult方法中進(jìn)行接收。

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if(resultCode == Activity.RESULT_OK){
                Log.e(TAG,"接收到回調(diào)結(jié)果");
                // NativePageActivity返回的數(shù)據(jù)
                String message = data.getStringExtra("message");
                Map<String, Object> result = new HashMap<>();
                result.put("message", message);
                // 創(chuàng)建MethodChannel,這里的flutterView即Flutter.createView所返回的View
                MethodChannel flutterChannel = new MethodChannel(mFlutterEngine.getDartExecutor(), CHANNEL_FLUTTER);
                // 調(diào)用Flutter端定義的方法
                flutterChannel.invokeMethod("onActivityResult", result);
            }
        }
    
    • 調(diào)用了flutter中的方法將值傳入到flutter中并顯示在界面上。

    • 定義一個(gè)全局唯一常量,和flutter中約定好。

      private static final String CHANNEL_FLUTTER = "com.example.flutter/flutter";
      
      static const flutterChannel = const MethodChannel('com.example.flutter/flutter');
      
    • 在initState中進(jìn)行接收

      String _backResult = "初步設(shè)置";
      @override
        void initState() {
          super.initState();
          Future<dynamic> handler(MethodCall call) async {
            switch (call.method) {
              case 'onActivityResult':
              // 獲取原生頁(yè)面?zhèn)鬟f的參數(shù)
                print(call.arguments['message']);
                setState(() {
                  _backResult = call.arguments['message'];
                });
                break;
            }
          }
          flutterChannel.setMethodCallHandler(handler);
        }
      

      用變量來(lái)記錄

    • 新建一個(gè)text來(lái)顯示這label

      Text(
         '$_backResult'
      ),
      

Flutter界面返回到android原生界面數(shù)據(jù)傳遞

  1. 新建一個(gè)返回按鈕

    RaisedButton(
          child: Text('返回上一頁(yè)'),
          onPressed: () {
          // 返回給上一頁(yè)的數(shù)據(jù)
          Map<String, dynamic> result = {'message': '我從Flutter頁(yè)面回來(lái)了'};
          nativeChannel.invokeMethod('goBackWithResult', result);
    }),
    

    并執(zhí)行返回的方法。方法名和key提前約定好。

  2. 原生fragmen中先接收相應(yīng)的參數(shù),然后使用intent進(jìn)行傳遞。

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            MethodChannel nativeChannel = new MethodChannel(mFlutterEngine.getDartExecutor(), CHANNEL_NATIVE);
            nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
                @Override
                public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                    switch (methodCall.method) {
                        case "goBackWithResult":
                            // 返回上一頁(yè),攜帶數(shù)據(jù)
                            Intent backIntent = new Intent();
                            backIntent.putExtra("message", (String) methodCall.argument("message"));
                            getActivity().setResult(Activity.RESULT_OK, backIntent);
                            getActivity().finish();
                            break;
                        default:
                            result.notImplemented();
                            break;
                    }
                }
            });
        }
    
  1. 在上一個(gè)原生界面的activity中進(jìn)行接收信息。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (resultCode == RESULT_OK) {
                tv_result.setVisibility(View.VISIBLE);
                String result = data.getStringExtra("message");
                tv_result.setText("result" + result);
            }
     }
    

demo

https://github.com/ShawEw/FlutterAndroidDemo/tree/master

參考

http://www.itdecent.cn/p/7b6522e3e8f1
http://www.itdecent.cn/p/4a27e091af0b

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容