当前位置:   article > 正文

Flutter 混合开发、与原生交互、在原生中嵌入flutter模块_flutter混合开发

flutter混合开发

一. 调用原生功能

1.1. Camera

某些应用程序可能需要使用移动设备进行拍照或者选择相册中的照片,Flutter官方提供了插件:image_picker

1.1.1. 添加依赖

添加对image_picker的依赖:

  • https://pub.dev/packages/image_picker

  1. dependencies:
  2.   image_picker: ^0.6.5

1.1.2. 平台配置

对iOS平台,想要访问相册或者相机,需要获取用户的允许:

  • 依然是修改info.plist文件:/ios/Runner/Info.plist

  • 添加对相册的访问权限:Privacy - Photo Library Usage Description

  • 添加对相机的访问权限:Privacy - Camera Usage Description

修改info.plist文件

之后选择相册或者访问相机时,会弹出如下的提示框:

image-20200412171028021

1.1.3. 代码实现

image_picker的核心代码是pickImage方法:

  • 可以传入数据源、图片的大小、质量、前置后置摄像头等

  • 数据源是必传参数:ImageSource枚举类型

    • camera:相机

    • gallery:相册

  1.   static Future<File> pickImage(
  2.       {@required ImageSource source,
  3.       double maxWidth,
  4.       double maxHeight,
  5.       int imageQuality,
  6.       CameraDevice preferredCameraDevice = CameraDevice.rear}) async

案例演练:

  1. import 'package:flutter/material.dart';
  2. import 'dart:io';
  3. import 'package:image_picker/image_picker.dart';
  4. class HYCameraScreen extends StatefulWidget {
  5.   static const String routeName = "/camera";
  6.   @override
  7.   _HYCameraScreenState createState() => _HYCameraScreenState();
  8. }
  9. class _HYCameraScreenState extends State<HYCameraScreen> {
  10.   File _image;
  11.   @override
  12.   Widget build(BuildContext context) {
  13.     return Scaffold(
  14.       appBar: AppBar(
  15.         title: Text("Camera"),
  16.       ),
  17.       body: Center(
  18.         child: Column(
  19.           mainAxisAlignment: MainAxisAlignment.center,
  20.           children: <Widget>[
  21.             _image == null ? Text("未选择图片"): Image.file(_image),
  22.             RaisedButton(
  23.               child: Text("选择照片"),
  24.               onPressed: _pickImage,
  25.             )
  26.           ],
  27.         ),
  28.       ),
  29.     );
  30.   }
  31.   void _pickImage() async {
  32.     File image = await ImagePicker.pickImage(source: ImageSource.gallery);
  33.     setState(() {
  34.       _image = image;
  35.     });
  36.   }
  37. }

案例效果

1.2. 电池信息

某些原生的信息,如果没有很好的插件,我们可以通过platform channels(平台通道)来获取信息。

1.2.1. 平台通过介绍

平台通过是如何工作的呢?

  • 消息使用platform channels(平台通道)在客户端(UI)和宿主(平台)之间传递;

  • 消息和响应以异步的形式进行传递,以确保用户界面能够保持响应;

平台通道架构图

调用过程大致如下:

  • 1.客户端(Flutter端)发送与方法调用相对应的消息

  • 2.平台端(iOS、Android端)接收方法,并返回结果;

    • iOS端通过FlutterMethodChannel做出响应;

    • Android端通过MethodChannel做出响应;

Flutter、iOS、Android端数据类型的对应关系:

数据类型对应表

1.2.2. 创建测试项目

我们这里创建一个获取电池电量信息的项目,分别通过iOS和Android原生代码来获取对应的信息:

创建方式一:默认创建方式

  • 目前默认创建的Flutter项目,对应iOS的编程语言是Swift,对应Android的编程语言是kotlin

flutter create batterylevel

创建方式二:指定编程语言

  • 如果我们希望指定编程语言,比如iOS编程语言为Objective-C,Android的编程语言为Java

flutter create -i objc -a java batterylevel2

1.2.3. 编写Dart代码

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

  • 创建该对象时,需要传入一个name,该name是区分多个通信的名称

  • 可以通过调用该对象的invokeMethod来给对应的平台发送消息进行通信

    • 该调用是异步操作,需要通过await获取then回调来获取结果

  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5.   @override
  6.   Widget build(BuildContext context) {
  7.     return MaterialApp(
  8.       title'Flutter Demo',
  9.       themeThemeData(
  10.           primarySwatch: Colors.blue, splashColor: Colors.transparent),
  11.       homeHYBatteryScreen(),
  12.     );
  13.   }
  14. }
  15. class HYBatteryScreen extends StatefulWidget {
  16.   static const String routeName "/battery";
  17.   @override
  18.   _HYBatteryScreenState createState() => _HYBatteryScreenState();
  19. }
  20. class _HYBatteryScreenState extends State<HYBatteryScreen{
  21.   // 核心代码一:
  22.   static const platform const MethodChannel("coderwhy.com/battery");
  23.   int _result = 0;
  24.   @override
  25.   Widget build(BuildContext context) {
  26.     return Scaffold(
  27.       appBarAppBar(
  28.         titleText("Battery"),
  29.       ),
  30.       bodyCenter(
  31.         childColumn(
  32.           children: <Widget>[
  33.             Text("当前电池信息: $_result"),
  34.             RaisedButton(
  35.               childText("获取电池信息"),
  36.               onPressed: getBatteryInfo,
  37.             )
  38.           ],
  39.         ),
  40.       ),
  41.     );
  42.   }
  43.   void getBatteryInfo() async {
  44.     // 核心代码二
  45.     final int result = await platform.invokeMethod("getBatteryInfo");
  46.     setState(() {
  47.       _result = result;
  48.     });
  49.   }
  50. }

当我们通过 platform.invokeMethod 调用对应平台方法时,需要在对应的平台实现其操作:

  • iOS中可以通过Objective-C或Swift来实现

  • Android中可以通过Java或者Kotlin来实现

1.2.4. 编写iOS代码

1.2.4.1. Swift代码实现

代码相关的操作步骤如下:

  • 1.获取FlutterViewController(是应用程序的默认Controller)

  • 2.获取MethodChannel(方法通道)

    • 注意:这里需要根据我们创建时的名称来获取

  • 3.监听方法调用(会调用传入的回调函数)

    • iOS中获取信息的方式

    • 如果没有获取到,那么返回给Flutter端一个异常

    • 通过result将结果回调给Flutter端

    • 3.1.判断是否是getBatteryInfo的调用,告知Flutter端没有实现对应的方法

    • 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调

  1. import UIKit
  2. import Flutter
  3. @UIApplicationMain
  4. @objc class AppDelegate: FlutterAppDelegate {
  5.   override func application(
  6.     _ application: UIApplication,
  7.     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  8.   ) -> Bool {
  9.     
  10.     // 1.获取FlutterViewController(是应用程序的默认Controller)
  11.     let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
  12.     
  13.     // 2.获取MethodChannel(方法通道)
  14.     let batteryChannel = FlutterMethodChannel(name: "coderwhy.com/battery",
  15.                                               binaryMessenger: controller.binaryMessenger)
  16.     
  17.     // 3.监听方法调用(会调用传入的回调函数)
  18.     batteryChannel.setMethodCallHandler({
  19.       [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
  20.       // 3.1.判断是否是getBatteryInfo的调用,告知Flutter端没有实现对应的方法
  21.       guard call.method == "getBatteryInfo" else {
  22.         result(FlutterMethodNotImplemented)
  23.         return
  24.       }
  25.       // 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调
  26.       self?.receiveBatteryLevel(result: result)
  27.     })
  28.     
  29.     GeneratedPluginRegistrant.register(withself)
  30.     return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  31.   }
  32.     
  33.   private func receiveBatteryLevel(result: FlutterResult) {
  34.     // 1.iOS中获取信息的方式
  35.     let device = UIDevice.current
  36.     device.isBatteryMonitoringEnabled = true
  37.     
  38.     // 2.如果没有获取到,那么返回给Flutter端一个异常
  39.     if device.batteryState == UIDevice.BatteryState.unknown {
  40.       result(FlutterError(code"UNAVAILABLE",
  41.                           message: "Battery info unavailable",
  42.                           details: nil))
  43.     } else {
  44.       // 3.通过result将结果回调给Flutter端
  45.       result(Int(device.batteryLevel * 100))
  46.     }
  47.   }
  48. }

1.2.4.2. Objective-C代码实现

实现思路和上面是一致的,只是使用了Objective-C来实现:

  • 可以参考注释内容

  1. #import <Flutter/Flutter.h>
  2. #import "AppDelegate.h"
  3. #import "GeneratedPluginRegistrant.h"
  4. @implementation AppDelegate
  5. - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  6.   // 1.获取FlutterViewController(是应用程序的默认Controller)
  7.   FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
  8.   // 2.获取MethodChannel(方法通道)
  9.   FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
  10.                                           methodChannelWithName:@"coderwhy.com/battery"
  11.                                           binaryMessenger:controller.binaryMessenger];
  12.   
  13.   // 3.监听方法调用(会调用传入的回调函数)
  14.   __weak typeof(self) weakSelf = self;
  15.   [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
  16.     // 3.1.判断是否是getBatteryInfo的调用
  17.     if ([@"getBatteryInfo" isEqualToString:call.method]) {
  18.       // 1.iOS中获取信息的方式
  19.       int batteryLevel = [weakSelf getBatteryLevel];
  20.       // 2.如果没有获取到,那么返回给Flutter端一个异常
  21.       if (batteryLevel == -1) {
  22.         result([FlutterError errorWithCode:@"UNAVAILABLE"
  23.                                    message:@"Battery info unavailable"
  24.                                    details:nil]);
  25.       } else {
  26.         // 3.通过result将结果回调给Flutter端
  27.         result(@(batteryLevel));
  28.       }
  29.     } else {
  30.       // 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调
  31.       result(FlutterMethodNotImplemented);
  32.     }
  33.   }];
  34.   [GeneratedPluginRegistrant registerWithRegistry:self];
  35.   return [super application:application didFinishLaunchingWithOptions:launchOptions];
  36. }
  37. - (int)getBatteryLevel {
  38.   // 获取信息的方法
  39.   UIDevice* device = UIDevice.currentDevice;
  40.   device.batteryMonitoringEnabled = YES;
  41.   if (device.batteryState == UIDeviceBatteryStateUnknown) {
  42.     return -1;
  43.   } else {
  44.     return (int)(device.batteryLevel * 100);
  45.   }
  46. }
  47. @end

1.2.5. 编写Android代码

1.2.5.1. Kotlin代码实现

实现思路和上面是一致的,只是使用了Kotlin来实现:

  • 可以参考注释内容

  1. import androidx.annotation.NonNull
  2. import io.flutter.embedding.android.FlutterActivity
  3. import io.flutter.embedding.engine.FlutterEngine
  4. import io.flutter.plugin.common.MethodChannel
  5. import android.content.Context
  6. import android.content.ContextWrapper
  7. import android.content.Intent
  8. import android.content.IntentFilter
  9. import android.os.BatteryManager
  10. import android.os.Build.VERSION
  11. import android.os.Build.VERSION_CODES
  12. class MainActivity: FlutterActivity() {
  13.     private val CHANNEL = "coderwhy.com/battery"
  14.     override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
  15.         // 1.创建MethodChannel对象
  16.         val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) 
  17.         
  18.         // 2.添加调用方法的回调
  19.         methodChannel.setMethodCallHandler {
  20.             // Note: this method is invoked on the main thread.
  21.             call, result ->
  22.             // 2.1.如果调用的方法是getBatteryInfo,那么正常执行
  23.             if (call.method == "getBatteryInfo") {
  24.                 // 2.1.1.调用另外一个自定义方法回去电量信息
  25.                 val batteryLevel = getBatteryLevel()
  26.                 
  27.                 // 2.1.2. 判断是否正常获取到
  28.                 if (batteryLevel != -1) {
  29.                     // 获取到返回结果
  30.                     result.success(batteryLevel)
  31.                 } else {
  32.                     // 获取不到抛出异常
  33.                     result.error("UNAVAILABLE""Battery level not available."null)
  34.                 }
  35.             } else {
  36.                 // 2.2.如果调用的方法是getBatteryInfo,那么正常执行
  37.                 result.notImplemented()
  38.             }
  39.         }
  40.     }
  41.     private fun getBatteryLevel(): Int {
  42.         val batteryLevel: Int
  43.         if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
  44.             val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
  45.             batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
  46.         } else {
  47.             val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
  48.             batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
  49.         }
  50.         return batteryLevel
  51.     }
  52. }

Android设备

1.2.5.1. Java代码实现

实现思路和上面是一致的,只是使用了Java来实现:

  • 可以参考注释内容

  1. package com.example.batterylevel2;
  2. import androidx.annotation.NonNull;
  3. import io.flutter.embedding.android.FlutterActivity;
  4. import io.flutter.embedding.engine.FlutterEngine;
  5. import io.flutter.plugins.GeneratedPluginRegistrant;
  6. import io.flutter.plugin.common.MethodChannel;
  7. import android.content.ContextWrapper;
  8. import android.content.Intent;
  9. import android.content.IntentFilter;
  10. import android.os.BatteryManager;
  11. import android.os.Build.VERSION;
  12. import android.os.Build.VERSION_CODES;
  13. import android.os.Bundle;
  14. public class MainActivity extends FlutterActivity {
  15.   private static final String CHANNEL = "coderwhy.com/battery";
  16.   @Override
  17.   public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
  18.     // 1.创建MethodChannel对象
  19.     MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
  20.     // 2.添加调用方法的回调
  21.     methodChannel.setMethodCallHandler(
  22.         (call, result) -> {
  23.           // 2.1.如果调用的方法是getBatteryInfo,那么正常执行
  24.           if (call.method.equals("getBatteryInfo")) {
  25.             // 2.1.1.调用另外一个自定义方法回去电量信息
  26.             int batteryLevel = getBatteryLevel();
  27.             // 2.1.2. 判断是否正常获取到
  28.             if (batteryLevel != -1) {
  29.               // 获取到返回结果
  30.               result.success(batteryLevel);
  31.             } else {
  32.               // 获取不到抛出异常
  33.               result.error("UNAVAILABLE""Battery level not available."null);
  34.             }
  35.           } else {
  36.             // 2.2.如果调用的方法是getBatteryInfo,那么正常执行
  37.             result.notImplemented();
  38.           }
  39.         }
  40.       );
  41.   }
  42.   private int getBatteryLevel() {
  43.     int batteryLevel = -1;
  44.     if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
  45.       BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
  46.       batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
  47.     } else {
  48.       Intent intent = new ContextWrapper(getApplicationContext()).
  49.               registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
  50.       batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1* 100/
  51.               intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
  52.     }
  53.     return batteryLevel;
  54.   }
  55. }

二. 嵌入原有项目

首先,我们先明确一点:Flutter设计初衷并不是为了和其它平台进行混合开发,它的目的是为了打造一个完整的跨平台应用程序。

但是,实际开发中,原有项目完全使用Flutter进行重构并不现实,对于原有项目我们更多可能采用混合开发的方式。

2.1. 创建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 my_flutter

创建完成后,该模块和普通的Flutter项目一直,可以通过Android Studio或VSCode打开、开发、运行;

目录结构如下:

  • 和之前项目不同的iOS和Android项目是一个隐藏文件,并且我们通常不会单独打开它们再来运行;

  • 它们的作用是将Flutter Module进行编译,之后继承到现有的项目中;

  1. my_flutter/
  2. ├── .ios/
  3. ├── .android/
  4. ├── lib/
  5. │ └── main.dart
  6. ├── test/
  7. └── pubspec.yaml

2.2. 嵌入iOS项目

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

  • 可以使用 CocoaPods 依赖管理和已安装的 Flutter SDK ;

  • 也可以通过手动编译 Flutter engine 、你的 dart 代码和所有 Flutter plugin 成 framework ,用 Xcode 手动集成到你的应用中,并更新编译设置;

目前iOS项目几乎都已经使用Cocoapods进行管理,所以推荐使用第一种CocoaPods方式;

我们按照如下的方式,搭建一个需要继承的iOS项目:

1.为了进行测试,我们这里创建一个默认的iOS项目:使用Xcode创建即可

创建一个iOS项目

2.将项目加入CocoaPods进行管理

  • 电脑上需要已经安装了CocoaPods

初始化CocoaPods:

pod init

安装CocoaPods的依赖:

pod install

编译Podfile文件:

  1. # Uncomment the next line to define a global platform for your project
  2. # platform :ios, '9.0'
  3. # 添加模块所在路径
  4. flutter_application_path = '../my_flutter'
  5. load File.join(flutter_application_path, '.ios''Flutter''podhelper.rb')
  6. target 'ios_my_test' do
  7.   # Comment the next line if you don't want to use dynamic frameworks
  8.   use_frameworks!
  9.   
  10.   # 安装Flutter模块
  11.   install_all_flutter_pods(flutter_application_path)
  12.   
  13.   # Pods for ios_my_test
  14. end

重新执行安装CocoaPods的依赖:

pod install

但是一般可能会有人会报错

Add `flutter_post_install(installer)` to your Podfile `post_install` block to build Flutter plugins:

post_install do |installer|

  flutter_post_install(installer)

end

[!] Invalid `Podfile` file: Missing `flutter_post_install(installer)` in Podfile `post_install` block.

#  from /Users/xxx/Desktop/TestTestTest/Podfile:14

#  -------------------------------------------

#    # 安装Flutter模块

>    install_all_flutter_pods(flutter_application_path)

#    use_frameworks!

#  -------------------------------------------

顶部已经告诉我们了,要添加

post_install do |installer|

  flutter_post_install(installer)

end

所以最后就是

platform :ios, '10.0'

# 添加模块所在路径

flutter_application_path = '../my_module_flutter'

load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

post_install do |installer|

  flutter_post_install(installer) if defined?(flutter_post_install)

end

target 'TestTestTest' do

  use_frameworks!

  # 安装Flutter模块

  install_all_flutter_pods(flutter_application_path)

#  pod 'xxx'

end

2.2.1. Swift代码

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

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

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

  1. import UIKit
  2. import FlutterPluginRegistrant
  3. @UIApplicationMain
  4. class AppDelegateUIResponderUIApplicationDelegate {
  5.    // 1.创建一个FlutterEngine对象
  6.     lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
  7.     
  8.     func application(_ applicationUIApplicationdidFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKeyAny]?) -> Bool {
  9.        // 2.启动flutterEngine
  10.         flutterEngine.run()
  11.         return true
  12.     }
  13. }

在启动的ViewController中,创建一个UIButton,并且点击这个Button时,弹出FlutterViewController

  1. import UIKit
  2. import Flutter
  3. class ViewControllerUIViewController {
  4.     override func viewDidLoad() {
  5.         super.viewDidLoad()
  6.         
  7.        // 1.创建一个按钮
  8.         let button = UIButton(type: UIButton.ButtonType.custom)
  9.         button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
  10.         button.setTitle("Show Flutter", for: .normal)
  11.         button.frame = CGRect(x: 80, y: 210, width: 160, height: 40)
  12.         button.backgroundColor = UIColor.blue
  13.         self.view.addSubview(button)
  14.     }
  15.     
  16.     @objc func showFlutter() {
  17.         // 2.创建FlutterViewController对象(需要先获取flutterEngine)
  18.         let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine;
  19.         let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil);
  20.         navigationController?.pushViewController(flutterViewController, animated: true);
  21.     }
  22. }

代码效果

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

  • 不推荐这样来做,因为在第一针图像渲染完成之前,可能会出现明显的延迟。

  1. func showFlutter() {
  2.   let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
  3.   present(flutterViewController, animated: true, completion: nil)
  4. }

2.2.2. Objective-C代码

如果上面的代码希望使用Objective-C也是可以实现的:

  • 代码的逻辑是完成一直的

AppDelegate.h代码:

  1. @import UIKit;
  2. @import Flutter;
  3. @interface AppDelegate : FlutterAppDelegate 
  4. @property (nonatomic,strong) FlutterEngine *flutterEngine;
  5. @end

AppDelegate.m代码:

  1. #import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Used to connect plugins.
  2. #import "AppDelegate.h"
  3. @implementation AppDelegate
  4. - (BOOL)application:(UIApplication *)application
  5.     didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
  6.       
  7.   self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
  8.   [self.flutterEngine run];
  9.       
  10.   [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  11.   return [super application:application didFinishLaunchingWithOptions:launchOptions];
  12. }
  13. @end

ViewController.m代码:

  1. @import Flutter;
  2. #import "AppDelegate.h"
  3. #import "ViewController.h"
  4. @implementation ViewController
  5. - (void)viewDidLoad {
  6.     [super viewDidLoad];
  7.     // Make a button to call the showFlutter function when pressed.
  8.     UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
  9.     [button addTarget:self
  10.                action:@selector(showFlutter)
  11.      forControlEvents:UIControlEventTouchUpInside];
  12.     [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
  13.     button.backgroundColor = UIColor.blueColor;
  14.     button.frame = CGRectMake(80.0210.0160.040.0);
  15.     [self.view addSubview:button];
  16. }
  17. - (void)showFlutter {
  18.     FlutterEngine *flutterEngine =
  19.         ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
  20.     FlutterViewController *flutterViewController =
  21.         [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
  22.     [self presentViewController:flutterViewController animated:YES completion:nil];
  23. }
  24. @end

启动不同的flutter页面

  1. import UIKit
  2. import Flutter
  3. @UIApplicationMain
  4. class AppDelegate: FlutterAppDelegate {
  5. lazy var flutterEngine1 = FlutterEngine(name: "main")
  6. lazy var flutterEngine2 = FlutterEngine(name: "second")
  7. override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  8. // Override point for customization after application launch.
  9. flutterEngine1.run(withEntrypoint: "main", initialRoute: "/")
  10. flutterEngine2.run(withEntrypoint: "main", initialRoute: "second")
  11. return true
  12. }
  13. // MARK: UISceneSession Lifecycle
  14. override func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
  15. // Called when a new scene session is being created.
  16. // Use this method to select a configuration to create the new scene with.
  17. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
  18. }
  19. override func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
  20. // Called when the user discards a scene session.
  21. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
  22. // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
  23. }
  24. }
  1. import UIKit
  2. import Flutter
  3. class ViewController: UIViewController {
  4. override func viewDidLoad() {
  5. super.viewDidLoad()
  6. // Do any additional setup after loading the view.
  7. let button1 = UIButton(type:UIButton.ButtonType.custom)
  8. button1.addTarget(self, action: #selector(showMain), for: .touchUpInside)
  9. button1.setTitle("show main!", for: UIControl.State.normal)
  10. button1.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
  11. button1.backgroundColor = UIColor.blue
  12. self.view.addSubview(button1)
  13. let button2 = UIButton(type:UIButton.ButtonType.custom)
  14. button2.addTarget(self, action: #selector(showSecond), for: .touchUpInside)
  15. button2.setTitle("show second!", for: UIControl.State.normal)
  16. button2.frame = CGRect(x: 80.0, y: 310.0, width: 160.0, height: 40.0)
  17. button2.backgroundColor = UIColor.blue
  18. self.view.addSubview(button2)
  19. }
  20. @objc func showMain() {
  21. let flutterEngine1 = (UIApplication.shared.delegate as! AppDelegate).flutterEngine1
  22. let flutterViewController =
  23. FlutterViewController(engine: flutterEngine1, nibName: nil, bundle: nil)
  24. present(flutterViewController, animated: true, completion: nil)
  25. }
  26. @objc func showSecond() {
  27. let flutterEngine2 = (UIApplication.shared.delegate as! AppDelegate).flutterEngine2
  28. let flutterViewController =
  29. FlutterViewController(engine: flutterEngine2, nibName: nil, bundle: nil)
  30. present(flutterViewController, animated: true, completion: nil)
  31. }
  32. }

2.3.嵌入Android项目

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

  • 编译为AAR文件(Android Archive)

    • 通过Flutter编译为aar,添加相关的依赖

  • 依赖模块的源码方式,在gradle进行配置

这里我们采用第二种方式

1.创建一个Android的测试项目

  • 使用Android Studio创建

创建Android项目

2.添加相关的依赖

修改Android项目中的settings.gradle文件:

  1. // Include the host app project.
  2. include ':app'                                     // assumed existing content
  3. setBinding(new Binding([gradle: this]))                                 // new
  4. evaluate(new File(                                                      // new
  5.   settingsDir.parentFile,                                               // new
  6.   'my_flutter/.android/include_flutter.groovy'                          // new
  7. ))   

另外,我们需要在Android项目工程的build.gradle中添加依赖:

  1. dependencies {
  2.   implementation project(':flutter')
  3. }

编译代码,可能会出现如下错误:

  • 这是因为从Java8开始才支持接口方法

  • Flutter Android引擎使用了该Java8的新特性

image-20200413161942859

解决办法:通过设置Android项目工程的build.gradle配置使用Java8编译:

  1.   compileOptions {
  2.     sourceCompatibility 1.8
  3.     targetCompatibility 1.8
  4.   }

接下来,我们这里尝试添加一个Flutter的screen到Android应用程序中

Flutter提供了一个FlutterActivity来展示Flutter界面在Android应用程序中,我们需要先对FlutterActivity进行注册:

  • 在AndroidManifest.xml中进行注册

  1. <activity
  2.   android:name="io.flutter.embedding.android.FlutterActivity"
  3.   android:theme="@style/AppTheme"
  4.   android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  5.   android:hardwareAccelerated="true"
  6.   android:windowSoftInputMode="adjustResize"
  7.   />

2.3.1. Java代码

  1. package com.coderwhy.testandroid;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import io.flutter.embedding.android.FlutterActivity;
  5. public class MainActivity extends AppCompatActivity {
  6.     @Override
  7.     protected void onCreate(Bundle savedInstanceState) {
  8.         super.onCreate(savedInstanceState);
  9. //        setContentView(R.layout.activity_main);
  10.         startActivity(
  11.             FlutterActivity.createDefaultIntent(this)
  12.         );
  13.     }
  14. }

也可以在创建时,传入默认的路由:

  1. package com.coderwhy.testandroid;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import io.flutter.embedding.android.FlutterActivity;
  5. public class MainActivity extends AppCompatActivity {
  6.     @Override
  7.     protected void onCreate(Bundle savedInstanceState) {
  8.         super.onCreate(savedInstanceState);
  9. //        setContentView(R.layout.activity_main);
  10.         startActivity(
  11.           FlutterActivity
  12.           .withNewEngine()
  13.           .initialRoute("/my_route")
  14.           .build(currentActivity)
  15.         );
  16.     }
  17. }

将Flutter页面嵌入到Android项目

2.3.2. Kotlin代码

  1. package com.coderwhy.test_demo_a_k
  2. import androidx.appcompat.app.AppCompatActivity
  3. import android.os.Bundle
  4. import io.flutter.embedding.android.FlutterActivity
  5. class MainActivity : AppCompatActivity() {
  6.     override fun onCreate(savedInstanceState: Bundle?) {
  7.         super.onCreate(savedInstanceState)
  8. //        setContentView(R.layout.activity_main)
  9.         startActivity(
  10.             FlutterActivity.createDefaultIntent(this)
  11.         )
  12.     }
  13. }

也可以在创建时指定路由:

  1. package com.coderwhy.test_demo_a_k
  2. import androidx.appcompat.app.AppCompatActivity
  3. import android.os.Bundle
  4. import io.flutter.embedding.android.FlutterActivity
  5. class MainActivity : AppCompatActivity() {
  6.     override fun onCreate(savedInstanceState: Bundle?) {
  7.         super.onCreate(savedInstanceState)
  8. //        setContentView(R.layout.activity_main)
  9.         startActivity(
  10.             FlutterActivity
  11.                 .withNewEngine()
  12.                 .initialRoute("/my_route")
  13.                 .build(this)
  14.         );
  15.     }
  16. }

三. Flutter模块调试

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

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

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

  • 可以使用flutter attach

  1. # --app-id是指定哪一个应用程序
  2. # -d是指定连接哪一个设备
  3. flutter attach --app-id com.coderwhy.ios-my-test -d 3D7A877C-B0DD-4871-8D6E-0C5263B986CD

attach调试模式

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/1009174
推荐阅读
  

闽ICP备14008679号