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<MyHomePage> {
  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: <Widget>[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<UITouch>, 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<UITouch>, 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容