flutter 混合開發

flutter 混合開發

參考:
https://flutter.dev/docs/development/add-to-app/android/project-setup

for android 頁面嵌入

配置architectures
Flutter currently only supports building ahead-of-time (AOT) compiled libraries for armeabi-v7a and arm64-v8a.

android {
  //...
  defaultConfig {
    ndk {
      // Filter for architectures supported by Flutter.
      abiFilters 'armeabi-v7a', 'arm64-v8a'
    }
  }
}
Java 8 requirement
android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}
Add the Flutter module as a dependency

Option A - Depend on the Android Archive (AAR)

This option allows your team to build the host app without installing the Flutter SDK.

  1. 通過命令窗口執行以下命令或者,通過Androidstudio中的build->flutter->build AAR
 cd some/path/my_flutter
 $ flutter build aar

執行完後會生成以下文件:

build/host/outputs/repo
└── com
    └── example
        └── my_flutter
            ├── flutter_release
            │   ├── 1.0
            │   │   ├── flutter_release-1.0.aar
            │   │   ├── flutter_release-1.0.aar.md5
            │   │   ├── flutter_release-1.0.aar.sha1
            │   │   ├── flutter_release-1.0.pom
            │   │   ├── flutter_release-1.0.pom.md5
            │   │   └── flutter_release-1.0.pom.sha1
            │   ├── maven-metadata.xml
            │   ├── maven-metadata.xml.md5
            │   └── maven-metadata.xml.sha1
            ├── flutter_profile
            │   ├── ...
            └── flutter_debug
                └── ...
  1. edit app/build.gradle
android {
  // ...
}

repositories {
  maven {
    url 'some/path/my_flutter/build/host/outputs/repo'
    // This is relative to the location of the build.gradle file
    // if using a relative path.
  }
  maven {
    url 'http://download.flutter.io'
  }
}

dependencies {
  // ...
  debugImplementation 'com.example.flutter_module:flutter_debug:1.0'
  profileImplementation 'com.example.flutter_module:flutter_profile:1.0'
  releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}

Option B - Depend on the module’s source code

This option enables a one-step build for both your Android project and Flutter project. This option is convenient when you work on both parts simultaneously and rapidly iterate, but your team must install the Flutter SDK to build the host app.

  1. 修改settings.gradle
include ':app'                                     // assumed existing content
setBinding(new Binding([gradle: this]))                                 // new
evaluate(new File(                                                      // new
  settingsDir.parentFile,                                               // new
  'my_flutter/.android/include_flutter.groovy'                          // new
))                                                                      // new
  1. 添加依賴
dependencies {
  implementation project(':flutter')
}

Adding a Flutter screen to an Android app

A Flutter screen can be added as a normal, opaque screen, or as a see-through, translucent screen.

// flutter升級後,view相關的api有所變化,推薦儘量使用FlutterActivity的方式

FlutterActivity使用如下:

方式一
/*
* 這種形式啓動,不能重寫getInitialRoute方法,否則會死循環直至棧溢出
*/
class FlutterActivityPractise : FlutterActivity() {


    var initParams:String?=null

    companion object{

        val EXTRA_INITIAL_ROUTE = "initial_route"
        private const val PAGE_ROUTE = "order_list"
        fun withNewEngine(): NewEngineIntentBuilder {
            return object : NewEngineIntentBuilder(FlutterActivityPractise::class.java) {

            }
        }

        fun start(context: Context, initParams: String?) {
            //createJsonTemplate(PAGE_ROUTE,"url","args-----")
            val intent = withNewEngine().initialRoute(initParams.let {
                if(TextUtils.isEmpty(initParams)) "/." else initParams!!
            }).build(context)
            context.startActivity(intent)



        }


        private fun createJsonTemplate(
            pageRoute: String?,
            url: String?,
            args: String?
        ): String {
            return JsonTemplate(
                pageRoute, url, args
            ).toJson()
        }

    }

}
方式二
/*
* 這種形式啓動,可以重寫getInitialRoute方法,對參數進一步處理
*/
class FlutterActivityPractise2 : FlutterActivity() {


    private var initParams: String? = null

    companion object{
        val EXTRA_INITIAL_ROUTE = "initial_route"
        private const val PAGE_ROUTE = "order_list"
        fun start(context:Context){
            val intent = Intent(context, FlutterActivityPractise2::class.java)
            intent.putExtra(EXTRA_INITIAL_ROUTE,
                createJsonTemplate(
                    PAGE_ROUTE,
                    "url",
                    "args-----"
                )
            )
            context.startActivity(intent)
        }


        private fun createJsonTemplate(
            pageRoute: String?,
            url: String?,
            args: String?
        ): String {
            return JsonTemplate(
                pageRoute, url, args
            ).toJson()
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initParams = intent.getStringExtra(EXTRA_INITIAL_ROUTE);
    }

    override fun getInitialRoute(): String {
        return createJsonTemplate(
                    PAGE_ROUTE,
                    "url",
                    "args--xxx---"
                )
    }

}
加快flutter啓動(對flutter engine 做預熱),一般在應用啓動的時候進行預熱,後面直接使用FlutterEngineCache.getInstance().get(ENGINE_ID)獲取引擎
companion object{
        const val ENGINE_ID = "1"
    }

    override fun onCreate() {
        super.onCreate()

        val flutterEngine = FlutterEngine(this)
        flutterEngine
            .dartExecutor
            .executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
            )
        FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine)
    }

add a flutter fragment

If an Activity is equally applicable for your application needs, consider using a FlutterActivity instead of a FlutterFragment, which is quicker and easier to use.

FlutterFragment allows developers to control the following details of the Flutter experience within the Fragment:

  1. Initial Flutter route.
  2. Dart entrypoint to execute.
  3. Opaque vs translucent background.
  4. Whether FlutterFragment should control its surrounding Activity.
  5. Whether a new FlutterEngine or a cached FlutterEngine should be used.
採坑:

1.當使用預熱引擎時,對1的設置無效。(思考:考慮啓動完,主動向本地請求跳轉配置,重新刷新頁面)
2. flutter對Android fragment提供了2種渲染方式,默認是surface,另一種是texture,2者區別,

surface 性能更高,但是不能跟Android的view視圖交錯,只能在最下面和最上面,不支持動畫;
texture 相比前者性能低一點,但是前者的缺點都不存在,

當我們嚮應用中添加FlutterFragment時,默認的renderMode是surface,所以當出現從flutterFragment向本地的頁面跳轉時,flutterFragment會覆蓋在目標頁面上面,這時候手動設置一下renderMode爲texture 就好了。

下面是我總結的2種添加FlutterFragment的方式:

  1. 這種不需要創建新的類,這種方式無法定製化eg:不能動態的傳遞參數,不滿足車隊現在的架構使用
FlutterFragment.withNewEngine()
//                .dartEntrypoint("mySpecialEntrypoint")
                .renderMode(FlutterView.RenderMode.texture)
//                .transparencyMode(FlutterView.TransparencyMode.opaque) 
 //               .shouldAttachEngineToActivity(true)
                .initialRoute("order_list")
                .build<FlutterFragment>()
  1. 這種方式需要新建一個類,不過可以實現更好的定製化,滿足車隊現在的架構
class TestFlutterFragment : FlutterFragment(){

    private var name:String? = "szm"

    companion object{

        fun withNewEngine():CusEngineFragmentBuilder{
            return CusEngineFragmentBuilder(TestFlutterFragment::class.java)
        }
    }



    class CusEngineFragmentBuilder(@NonNull subclass: Class<out FlutterFragment?>):NewEngineFragmentBuilder(subclass){

        var name:String?=null

        fun name(name:String):CusEngineFragmentBuilder{
            this.name = name
            return this
        }

        override fun createArgs(): Bundle {
            val bundle = super.createArgs()
            bundle.putString("name",name)
            return bundle
        }


    }


    // 提供過度頁面,可定製
    override fun provideSplashScreen(): SplashScreen? {
//        // Load the splash Drawable.
//        val splash: Drawable = activity.getResources().getDrawable(R.drawable.my_splash)
//
//        // Construct a DrawableSplashScreen with the loaded splash Drawable and
//        // return it.
//        return DrawableSplashScreen(splash)

        return SplashScreenWithTransition()
    }


}

### 使用

val fragmentManager: FragmentManager = supportFragmentManager

        flutterFragment = fragmentManager
            .findFragmentByTag(TAG_FLUTTER_FRAGMENT) as TestFlutterFragment?

        if (flutterFragment == null) {

            var newFlutterFragment = TestFlutterFragment.withNewEngine().name("sm")
                .dartEntrypoint("mySpecialEntrypoint")
                .renderMode(FlutterView.RenderMode.texture)
                .transparencyMode(FlutterView.TransparencyMode.opaque) 
                .shouldAttachEngineToActivity(true)
                .initialRoute("fragment_no_ext_args")
                .build<TestFlutterFragment>()
            flutterFragment = newFlutterFragment
            fragmentManager
                .beginTransaction()
                .add(
                    R.id.fragment_container,
                    flutterFragment as Fragment,
                    TAG_FLUTTER_FRAGMENT
                )
                .commit()
        }


for iOS 頁面嵌入

參考:https://flutter.dev/docs/development/add-to-app/ios/project-setup

  1. Create a Flutter module

  2. Embed the Flutter module in your existing application

There are two ways to embed Flutter in your existing application.

  1. Use the CocoaPods dependency manager and installed Flutter SDK. (Recommended.)
  2. Create frameworks for the Flutter engine, your compiled Dart code, and all Flutter plugins. Manually embed the frameworks, and update your existing application’s build settings in Xcode.

Note: Your app will not run on a simulator in Release mode because Flutter does not yet support output x86 ahead-of-time (AOT) binaries for your Dart code. You can run in Debug mode on a simulator or a real device, and Release on a real device.

Option A - Embed with CocoaPods and the Flutter SDK

This method requires every developer working on your project to have a locally installed version of the Flutter SDK. Simply build your application in Xcode to automatically run the script to embed your Dart and plugin code. This allows rapid iteration with the most up-to-date version of your Flutter module without running additional commands outside of Xcode.

假設目錄結構如下,

some/path/
├── my_flutter/
│   └── .ios/
│       └── Flutter/
│         └── podhelper.rb
└── MyApp/
    └── Podfile

命令行進入根目錄,執行pod init,生成pod文件,然後執行pod install 會在根目錄生成一些其他安裝文件,其中有一個xxx.xcworkspace文件,我們通過這個文件打開工程

  1. Add the following lines to your Podfile:
flutter_application_path = '../my_flutter'
 load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  1. For each Podfile target that needs to embed Flutter
target 'MyApp' do
   install_all_flutter_pods(flutter_application_path)
 end
  1. Run pod install.

Note: When you change the Flutter plugin dependencies in my_flutter/pubspec.yaml, run flutter pub get in your Flutter module directory to refresh the list of plugins read by the podhelper.rb script. Then, run pod install again from in your application atsome/path/MyApp.

Option B - Embed frameworks in Xcode

Alternatively, you can generate the necessary frameworks and embed them in your application by manually editing your existing Xcode project. You may do this if members of your team can’t locally install Flutter SDK and CocoaPods, or if you don’t want to use CocoaPods as a dependency manager in your existing applications. You must run flutter build ios-framework every time you make code changes in your Flutter module.

  1. 假設你想要在some/path/MyApp/Flutter/ 生成frameworks
flutter build ios-framework --output=some/path/MyApp/Flutter/

執行完會生成以下文件

some/path/MyApp/
└── Flutter/
    ├── Debug/
    │   ├── Flutter.framework
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework
    │   └── example_plugin.framework (each plugin with iOS platform code is a separate framework)
      ├── Profile/
      │   ├── Flutter.framework
      │   ├── App.framework
      │   ├── FlutterPluginRegistrant.framework
      │   └── example_plugin.framework
      └── Release/
          ├── Flutter.framework
          ├── App.framework
          ├── FlutterPluginRegistrant.framework
          └── example_plugin.framework

Tip: With Xcode 11 installed, you can generate XCFrameworks instead of universal frameworks by adding the flags --xcframework --no-universal.

  1. 配置剛剛生成的frameworks 可以使用

For example, you can drag the frameworks from some/path/MyApp/Flutter/Release/ in Finder into your targets’s build settings > General > Frameworks, Libraries, and Embedded Content. Then, select “Embed & Sign” from the drop-down list.

In the target’s build settings, add $(PROJECT_DIR)/Flutter/Release/ to your Framework Search Paths (FRAMEWORK_SEARCH_PATHS).

Tip: To embed the Debug version of the Flutter frameworks in your Debug build configuration and the Release version in your Release configuration, in your MyApp.xcodeproj/project.pbxproj, try replacing path = Flutter/Release/example.framework; with path = “Flutter/$(CONFIGURATION)/example.framework”; for all added frameworks. (Note the added ".)

You must also add (PROJECTDIR)/Flutter/(PROJECT_DIR)/Flutter/(CONFIGURATION) to your Framework Search Paths build setting.

flutter提供了三種 native和dart 通信的機制:

  1. EventChannel
  2. MethodChannel 單次通信
  3. BasicMessageChannel

一般我們使用MethodChannel就好了,下面我對dart端的通信進行了封裝,並且附上使用方式

for dart 通信實現

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// 封裝通信相關的
abstract class AbsState<T extends StatefulWidget > extends State<T>{


  EventChannel _eventChannel;
  MethodChannel _methodChannel ;
  BasicMessageChannel _basicMessageChannel ;

  StreamSubscription _streamSubscription;


  void eventChannelListen<T>(streamName,void onData(T event),
      {Function onError, void onDone(), bool cancelOnError}){
    if(_eventChannel == null){
      _eventChannel = EventChannel('EventChannelPlugin');
    }
    _streamSubscription = _eventChannel.receiveBroadcastStream(streamName).listen(onData);
  }


  Future<T> methodChannelInvoke<T>(String method, [ dynamic arguments ]) async {
    if(_methodChannel == null){
      _methodChannel = const MethodChannel('MethodChannelPlugin');
    }
    return _methodChannel.invokeMethod(method,arguments);
  }


  void  setBasicMessageChannelHandler(Future<dynamic> handler(dynamic message),){
    if(_basicMessageChannel == null){
      _basicMessageChannel =
      const BasicMessageChannel('BasicMessageChannelPlugin',StringCodec());
    }
    _basicMessageChannel.setMessageHandler(handler);
  }

  Future<dynamic> send(dynamic message) async {
    if(_basicMessageChannel == null){
      _basicMessageChannel =
      const BasicMessageChannel('BasicMessageChannelPlugin',StringCodec());
    }
    return _basicMessageChannel.send(message);
  }


  @override
  void dispose() {
    if(_streamSubscription != null){
      _streamSubscription.cancel();
      _streamSubscription = null;
    }
    super.dispose();
  }

}

### 使用

class _OrderListPageState extends AbsState<OrderListPage> {


@override
  void initState() {
    eventChannelListen("we", onData_,onError: onError_);
    setBasicMessageChannelHandler((message)=>Future<String>((){
      setState(() {
        msg = 'BasicMessageChannel:'+message;
      });
      print('----msg--$msg');
      return "收到Native的消息:" + message;
    }));
    bindMethodHandler(handler:_handleNavigationInvocation);
    super.initState();
  }


Void testUse(){
   // methodChannel
    methodChannelInvoke("jumpToReceiptPage",data.orderSn);
    // _basicMessageChannel
    send(value);
}

}


for Android 通信實現

#### EventChannel

class EventChannelPlugin : EventChannel.StreamHandler {

    private var eventSink:EventChannel.EventSink? = null

    override fun onListen(p0: Any?, p1: EventChannel.EventSink?) {
        Log.e("listen--",p0.toString())
        eventSink = p1
    }

    override fun onCancel(p0: Any?) {
        eventSink = null
    }


    companion object{
        fun registerWith(flutterView: BinaryMessenger):EventChannelPlugin{
            var eventChannelPlugin = EventChannelPlugin()
            EventChannel(flutterView, "EventChannelPlugin").setStreamHandler(eventChannelPlugin)
            return eventChannelPlugin
        }
    }


    fun send(params:Any){
        eventSink?.success(params)
    }

}


#### MethodChannel

public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {


    private IMethodMsgCallback iMethodMsgCallback;

    public void setiMethodMsgCallback(IMethodMsgCallback iMethodMsgCallback) {
        this.iMethodMsgCallback = iMethodMsgCallback;
    }

    public static MethodChannelPlugin registerWith(BinaryMessenger flutterView){
        MethodChannel methodChannel = new MethodChannel(flutterView,"MethodChannelPlugin");
        MethodChannelPlugin instance = new MethodChannelPlugin();
        methodChannel.setMethodCallHandler(instance);
        return instance;
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method){// 處理來自Dart的方法調用
            case "send":
                showMessage(methodCall.arguments);
                result.success("MethodChannelPlugin收到:" + methodCall.arguments);
                break;
            case "commonArgs":
                result.success(parseMapToJson());
                break;
                default:
//                    result.notImplemented();
                    if(iMethodCallBack != null){
                        iMethodCallBack.onMethodChannelCallBack(methodCall,result);
                    }
        }
    }

    private void showMessage(Object arguments) {
//        Toast.makeText(activity, arguments.toString(), Toast.LENGTH_SHORT).show();
        if(iMethodMsgCallback != null){
            iMethodMsgCallback.onMsg(arguments.toString());
        }
    }


    private String parseMapToJson(){
        Gson gson = new Gson();
        HashMap<String, Object> params =  ApnInit.getHeads();
        String token = Configurator.getInstance().getConfiguration(ConfigKeys.TOKEN);
        if (!TextUtils.isEmpty(token)) {
            params.put("token", token);
        }
        return gson.toJson(params);
    }


    public interface IMethodMsgCallback{
        void onMsg(String msg);
    }


    private IMethodCallBack iMethodCallBack;

    public void setiMethodCallBack(IMethodCallBack iMethodCallBack) {
        this.iMethodCallBack = iMethodCallBack;
    }

    public interface IMethodCallBack{
        void onMethodChannelCallBack(MethodCall methodCall, MethodChannel.Result result);
    }

}



#### BasicMessageChannel

class BasicMessageChannelPlugin(var flutterView: BinaryMessenger) :
        BasicMessageChannel.MessageHandler<String> {

    var iReceiveCallBack:IReceiveCallBack?= null

    override fun onMessage(p0: String?, p1: BasicMessageChannel.Reply<String>) {
        Log.e("listen--","BasicMessageChannelPlugin------$p0")
        iReceiveCallBack?.onMessage(p0,p1)
    }

    private var messageChannelPlugin: BasicMessageChannel<String> =
            BasicMessageChannel(flutterView,"BasicMessageChannelPlugin",StringCodec.INSTANCE)

    companion object{
        fun registerWith(binaryMessenger: BinaryMessenger):BasicMessageChannelPlugin{
            return BasicMessageChannelPlugin(binaryMessenger)
        }
    }

    init {
        messageChannelPlugin.setMessageHandler(this)
    }

    fun send(message:String,callBack:BasicMessageChannel.Reply<String>?){
        messageChannelPlugin.send(message,callBack)
    }


    public interface IReceiveCallBack{
        fun onMessage(p0: String?, p1: BasicMessageChannel.Reply<String>)
    }

}


for iOS 通信實現

import UIKit
import Flutter
import SnapKit

class ViewController: UIViewController {
    
    let nativeController = TestViewController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        let nativeView = nativeController.view!
        nativeView.backgroundColor = UIColor.blue
        view.addSubview(nativeView)
        nativeView.snp.makeConstraints({make in
            make.top.left.equalToSuperview()
            make.width.equalToSuperview()
            make.height.equalToSuperview().dividedBy(2)
        })
        
        
        
        if let flutterEngine = (UIApplication.shared.delegate as? FlutterApp)?.flutterEngine {
            let flutterViewController = FlutterViewController(nibName: nil, bundle: nil)
            //                    let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
            
            flutterViewController.modalPresentationStyle = .overFullScreen
            //                    flutterViewController.setInitialRoute("fragment_no_ext_args")
            flutterViewController.setInitialRoute("order_list")
            view.addSubview(flutterViewController.view)
            flutterViewController.view.snp.makeConstraints({
                $0.left.equalToSuperview()
                $0.top.equalTo(nativeView.snp_bottom)
                $0.width.equalToSuperview()
                $0.height.equalToSuperview().dividedBy(2)
            })
            self.addChild(flutterViewController)
            
            
            //methodChannel
            let methodChannel = FlutterMethodChannel(name: "MethodChannelPlugin", binaryMessenger: flutterViewController.binaryMessenger)
            
            methodChannel.setMethodCallHandler { call, result in
                switch call.method{
                case "send":
                    self.nativeController.updateTvShow(txt: call.arguments as! String)
                default:
                    print(call.method)
                }
            }
            
            //FlutterBasicMessageChannel
            let basicMessageChannel = FlutterBasicMessageChannel(name: "BasicMessageChannelPlugin", binaryMessenger: flutterViewController.binaryMessenger,codec: FlutterStringCodec.sharedInstance())
            basicMessageChannel.setMessageHandler { (p0, reply) in
                self.nativeController.updateTvShow(txt:p0 as! String)
            }
            
            
            //FlutterEventChannel
            let eventChannel = FlutterEventChannel(name: "EventChannelPlugin", binaryMessenger: flutterViewController.binaryMessenger)
        
            eventChannel.setStreamHandler(self)
            
            
            
            nativeController.buttonClickBlock { (msg) in
                if self.nativeController.useMethodChannel(){
                    basicMessageChannel.sendMessage(msg)
                }else{
                    methodChannel.invokeMethod("send", arguments: msg)
                }
                
            }
            
        }
        
    }
    
    
}

extension ViewController: FlutterStreamHandler {
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.nativeController.updateTvShow(txt: arguments as! String)
        return nil
    }
    
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        return nil
    }
    
    
}
            

發佈了24 篇原創文章 · 獲贊 5 · 訪問量 4222
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章