Flutter 混合开发

调用原生功能

Camera

添加依赖

添加对 image_picker 的依赖:

dependencies:
  flutter:
    sdk: flutter
  image_picker: ^0.8.4+8
平台配置

对iOS平台,想要访问相册或者相机,需要获取用户的允许,修改info.plist文件:/ios/Runner/Info.plist

  • 添加对相册的访问权限:NSPhotoLibraryUsageDescription
  • 添加对相机的访问权限:NSCameraUsageDescription
  • 添加对麦克风的访问权限:NSMicrophoneUsageDescription
image.png
代码实现
void _imagePicker() async {
  /**
   * 可以传入数据源、图片的大小、质量、前置后置摄像头等
   * 数据源是必传参数:ImageSource枚举类型
   *  camera:相机
   *  gallery:相册
   * */
  PickedFile? pickedFile = await ImagePicker.platform.pickImage(source: ImageSource.gallery);

  setState(() {
    String path = (pickedFile?.path)!;
    _imageFile = File(path);
  });
}

// Image.file(_imageFile!)

电池信息

编写Dart代码

在Dart代码中,我们需要创建一个 MethodChannel 对象:

  • 创建该对象时,需要传入一个 name,该 name 是区分多个通信的名称,必须唯一。
  • 可以通过调用该对象的 invokeMethod 来给对应的平台发送消息进行通信,该调用是异步操作,需要通过 await 获取 then 回调来获取结果。
class _MyHomePageState extends State {
  static const platform = const MethodChannel("gaowenli.com/battery");

  int _result = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [Text("当前电量 $_result")],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          getBatteryInfo();
        },
        child: const Icon(Icons.add),
      ),
    );
  }

  void getBatteryInfo() async {
    final int result = await platform.invokeMethod("getBatteryInfo");
    setState(() {
      _result = result;
    });
  }
}
编写iOS代码
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
       
      // 1.获取FlutterViewController(是应用程序的默认Controller)
      let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
          
      // 2.获取MethodChannel(方法通道)
      let batteryChannel = FlutterMethodChannel(name: "gaowenli.com/battery",
                                                    binaryMessenger: controller.binaryMessenger)
          
      // 3.监听方法调用(会调用传入的回调函数)
      batteryChannel.setMethodCallHandler ({[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
          // 3.1.判断是否是getBatteryInfo的调用,告知Flutter端没有实现对应的方法
          guard call.method == "getBatteryInfo" else {
            result(FlutterMethodNotImplemented)
            return
          }
          // 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调
          self?.receiveBatteryLevel(result: result)
      })
       
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    
    private func receiveBatteryLevel(result: FlutterResult) {
        // 1.iOS中获取信息的方式
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
    
        // 2.如果没有获取到,那么返回给Flutter端一个异常
        if device.batteryState == UIDevice.BatteryState.unknown {
            result(FlutterError(code: "UNAVAILABLE",
                          message: "Battery info unavailable",
                          details: nil))
        } else {
            // 3.通过result将结果回调给Flutter端
            result(Int(device.batteryLevel * 100))
        }
    }
}

嵌入原有项目

创建Flutter模块

对于需要进行混合开发的原有项目,Flutter可以作为一个库或者模块,继承进现有项目中。

  • 模块引入到你的Android或iOS应用中,以使用Flutter渲染一部分的UI,或者共享的Dart代码。
  • 在Flutter v1.12中,添加到现有应用的基本场景已经被支持,每个应用在同一时间可以集成一个全屏幕的Flutter实例。

但是,目前一些场景依然是有限制的:

  • 运行多个Flutter实例,或在屏幕局部上运行Flutter可能会导致不可以预测的行为;
  • 在后台模式使用Flutter的能力还在开发中(目前不支持);
  • 将Flutter库打包到另一个可共享的库或将多个Flutter库打包到同一个应用中,都不支持;
  • 添加到应用在Android平台的实现基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能会有不可预知的行为。

创建Flutter Module

flutter create --template module hello_flutter

创建一个 iOS 项目

image.png

嵌入iOS项目

嵌入到现有iOS项目有多种方式:

  • 可以使用 CocoaPods 依赖管理和已安装的 Flutter SDK ;
  • 也可以通过手动编译 Flutter engine 、你的 dart 代码和所有 Flutter plugin 成 framework ,用 Xcode 手动集成到你的应用中,并更新编译设置;

iOS项目 hello_swift 初始化CocoaPods:

pod init

安装CocoaPods的依赖:

pod install

编译Podfile文件:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

# 添加模块所在路径
flutter_application_path = '../hello_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'flutter_swift' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # 安装Flutter模块
  install_all_flutter_pods(flutter_application_path)
    
  # Pods for flutter_d

end

重新执行安装CocoaPods的依赖:

pod install
image.png
Swift代码

为了在既有的iOS应用中展示Flutter页面,需要启动 Flutter EngineFlutterViewController

通常建议为我们的应用预热一个 长时间存活 的FlutterEngine:

  • 我们将在应用启动的 app delegate 中创建一个 FlutterEngine,并作为属性暴露给外界。
import UIKit
import FlutterPluginRegistrant

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    // 1.创建一个FlutterEngine对象
    lazy var flutterEngine = FlutterEngine(name: "my flutter engine")

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // 2.启动flutterEngine
         flutterEngine.run()
        
        return true
    }
}

此时在启动的 ViewController 控制器中就可以弹出 FlutterViewController

import UIKit
import Flutter

class ViewController: UIViewController {

    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        // 2.创建FlutterViewController对象(需要先获取flutterEngine)
        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine;
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil);
        flutterViewController.view.backgroundColor = .blue
        self.present(flutterViewController, animated: true, completion: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .gray
    }
}

我们也可以省略预先创建的 FlutterEngine :

  • 不推荐这样来做,因为在第一针图像渲染完成之前,可能会出现明显的延迟。
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
        flutterViewController.view.backgroundColor = .cyan
        self.present(flutterViewController, animated: true, completion: nil)
    }

出现错误 Failed to find assets path for "Frameworks/App.framework/flutter_assets" 导致flutter 白屏

Failed to find assets path for "Frameworks/App.framework/flutter_assets"
Metal API Validation Enabled
[VERBOSE-2:engine.cc(196)] Engine run configuration was invalid.
[VERBOSE-2:shell.cc(574)] Could not launch engine with configuration.
flutter: Observatory listening on http://127.0.0.1:59589/qPJkTN288T4=/

查看 flutter项目中 /hello_flutter/.ios/Flutter/ 文件夹下面是否有 App.frameworkFlutter.framework

image.png

iOS项目中是否有 App.frameworkFlutter.framework

image.png

如果没有可以从 /hello_flutter/build/ios/Debug-iphoneos/ 文件夹下复制一份

iOS项目重新执行 pod install


Flutter模块调试

一旦将Flutter模块继承到你的项目中,并且使用Flutter平台的API运行Flutter引擎或UI,那么就可以像普通的Android或者iOS一样来构建自己的Android或者iOS项目了

但是Flutter的有一个非常大的优势是其快速开发,也就是hot reload。

那么对应Flutter模块,我们如何使用hot reload加速我们的调试速度呢?

可以使用 flutter attach

  • --app-id是指定哪一个应用程序
  • -d是指定连接哪一个设备
image.png

连接成功后,按键 R 即可热更新

image.png

你可能感兴趣的:(Flutter 混合开发)