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()
  1. 这种方式需要新建一个类,不过可以实现更好的定制化,满足车队现在的架构
class TestFlutterFragment : FlutterFragment(){

    private var name:String? = "szm"

    companion object{

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



    class CusEngineFragmentBuilder(@NonNull subclass: Class):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()
            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 ( P R O J E C T D I R ) / F l u t t e r / (PROJECT_DIR)/Flutter/ (PROJECTDIR)/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 extends State{


  EventChannel _eventChannel;
  MethodChannel _methodChannel ;
  BasicMessageChannel _basicMessageChannel ;

  StreamSubscription _streamSubscription;


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


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


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

  Future 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 {


@override
  void initState() {
    eventChannelListen("we", onData_,onError: onError_);
    setBasicMessageChannelHandler((message)=>Future((){
      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 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 {

    var iReceiveCallBack:IReceiveCallBack?= null

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

    private var messageChannelPlugin: BasicMessageChannel =
            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?){
        messageChannelPlugin.send(message,callBack)
    }


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

}


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

你可能感兴趣的:(android,flutter,混合开发)