Day22 - Flutter - 混合开发

概述

  • 调用原生功能
  • 嵌入原有项目
  • Flutter模块调试
一、调用原生功能
  • 1.1、Camera
    某些应用程序可能需要使用移动设备进行拍照或者选择相册中的照片,Flutter官方提供了插件:image_picker

    • 1.1.1、添加依赖
      添加对image_picker的依赖:https://pub.dev/packages/image_picker,在项目的 pubspec.ymal 里面添加下面的依赖即可,然后执行右上角的 Pub get

      dependencies:
           image_picker: ^0.6.7+1
      
    • 1.1.2、平台配置
      对iOS平台,想要访问相册或者相机,需要获取用户的允许:
      依然是修改 info.plist 文件:/ios/Runner/Info.plist
      添加对相册的访问权限:Privacy - Photo Library Usage Description
      添加对相机的访问权限:Privacy - Camera Usage Description

      • 拓展:其他的权限
      • 相机权限:Privacy - Camera Usage Description 是否允许此App使用你的相机?
      • 相册权限:Privacy - Photo Library Usage Description 是否允许此App访问你的媒体资料库?
      • 通讯录权限:Privacy - Contacts Usage Description 是否允许此App访问你的通讯录?
      • 蓝牙权限:Privacy - Bluetooth Peripheral Usage Description 是否许允此App使用蓝牙?
      • 使用期间定位权限:Privacy - Location When In Use Usage Description 是否允许此App使用定位服务?
      • 始终定位权限:Privacy - Location Always Usage Description 是否允许此App始终使用定位服务?
      • 语音转文字权限:Privacy - Speech Recognition Usage Description 是否允许此App使用语音识别?
      • 日历权限:Privacy - Calendars Usage Description 是否允许此App使用日历?
      • 健康—读取数据: Privacy - Health Share Usage Description 是否允许此App读取健康数据?
      • 健康—写入数据: Privacy - Health Share Usage Description 是否允许此App写入健康数据?
      • 读取HomeKit: Privacy - HomeKit Usage Description 是否允许此App访问HomeKit?
      • 麦克风:Privacy - Microphone Usage Description 是否允许此App访问麦克风?
      • 提醒事项: Privacy - Reminders Usage Description 是否允许此App访问提醒事项?
      • 运动与健身: Privacy - Motion Usage Description 是否允许此App访问运动与健身?
      • 面部ID权限: Privacy - Face ID Usage Description 是否允许此App访问Face ID?

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


    • 1.1.3、代码实现
      image_picker 的核心代码是 getImage 方法:
      可以传入数据源、图片的大小、质量、前置后置摄像头等
      数据源是必传参数:ImageSource 枚举类型: camera:相机gallery:相册

      Future<PickedFile> getImage({
         @required ImageSource source,
         double maxWidth,
         double maxHeight,
         int imageQuality,
         // 默认后置摄像头
         CameraDevice preferredCameraDevice = CameraDevice.rear,
      }) {
         return platform.pickImage(
            source: source,
            maxWidth: maxWidth,
            maxHeight: maxHeight,
            imageQuality: imageQuality,
            preferredCameraDevice: preferredCameraDevice,
         );
      }
      

      案例演练:

      import 'dart:io';
      import 'package:flutter/material.dart';
      import 'package:image_picker/image_picker.dart';
      
      class JKCameraScreen extends StatefulWidget {
         @override
         _JKCameraScreenState createState() => _JKCameraScreenState();
      }
      
      class _JKCameraScreenState extends State<JKCameraScreen> {
         PickedFile _imageFile;
         final ImagePicker _picker = ImagePicker();
      
         @override
         Widget build(BuildContext context) {
              return Center(
                 child: Column(
                    children: [
                       RaisedButton(
                          child: Text('选择一个相册'),
                          onPressed: _pickImage
                       ),
                       _imageFile == null ? Text('请选择一张照片') : Image.file(File(_imageFile.path))
                    ],
                 ),
              );
         }
      
         void _pickImage() async {
              print('选择相册');
              PickedFile pickedFile = await _picker.getImage(source: ImageSource.gallery);
              setState(() {
                  _imageFile = pickedFile;
              });
         }
      }
      
      案例效果
  • 1.2、电池信息
    某些原生的信息,如果没有很好的插件,我们可以通过、platform channels(平台通道) 来获取信息。

    • 1.2.1、平台通过介绍
      平台通过是如何工作的呢?

      • 消息使用platform channels(平台通道)在客户端(UI)和宿主(平台)之间传递;
      • 消息和响应以异步的形式进行传递,以确保用户界面能够保持响应;

      调用过程大致如下:

      • 1.客户端(Flutter端)发送与方法调用相对应的消息
      • 2.平台端(iOS、Android端)接收方法,并返回结果;
        • iOS端通过FlutterMethodChannel做出响应;
        • Android端通过MethodChannel做出响应;

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

      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
        
        • 提示: i代表 iOSa 代表 android
    • 1.2.3、写Dart代码
      在Dart代码中,我们需要创建一个MethodChannel对象:

      • 创建该对象时,需要传入一个name,该name是区分多个通信的名称
      • 可以通过调用该对象的invokeMethod来给对应的平台发送消息进行通信
        • 该调用是异步操作,需要通过await获取then回调来获取结果

          import 'package:flutter/material.dart';
          import 'package:flutter/services.dart';
          
          void main() => runApp(MyApp());
          
          class MyApp extends StatelessWidget {
              @override
              Widget build(BuildContext context) {
                  return MaterialApp(
                      // 启动要显示的界面
                      home: HomePage(),
                  );
              }
          }
          
          class HomePage extends StatelessWidget {
             @override
             Widget build(BuildContext context) {
                 return Scaffold(
                     appBar: AppBar(
                        title: Text("原生电池的调用"),
                     ),
                     body: JKBatteryLevel(),
                 );
             }
          }
          
          class JKBatteryLevel extends StatefulWidget {
              @override
              _JKBatteryLevelState createState() => _JKBatteryLevelState();
          }
          
          class _JKBatteryLevelState extends State<JKBatteryLevel> {
               // 定义一个平台通道
               static const platform1 = const MethodChannel('com.jk/battery');
          
               int _batterylevel = 0;
          
               @override
               Widget build(BuildContext context) {
                  return Center(
                     child: Column(
                        children: [
                            RaisedButton(
                                 child: Text('获取剩余电量'),
                                 onPressed: _buildLevelInfo
                            ),
                            Text('电量:${_batterylevel}')
                        ],
                     ),
                  );
              }
          
              void _buildLevelInfo() async {
                   // 调用原生的电池信息
                   final result = await platform1.invokeMethod('getBatteryInfo');
                    setState(() {
                        _batterylevel = result;
                    });
              }
          }
          

          当我们通过 platform.invokeMethod调用对应平台方法时,需要在对应的平台实现其操作:
          iOS 中可以通过 Objective-CSwift 来实现
          Android 中可以通过 Java 或者 Kotlin 来实现

    • 1.2.4、编写iOS代码

      • <1>、Swift 代码,在 AppDelegate.swift 里面写代码

        import UIKit
        import Flutter
        
        @UIApplicationMain
        @objc class AppDelegate: FlutterAppDelegate {
            override func application(
                _ application: UIApplication,
               didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
            ) -> Bool {
        
               // 实现获取电量信息的功能
               // 1、获取FlutterViewController
               let flutterController: FlutterViewController = window.rootViewController as! FlutterViewController
        
               // 2、创建 FlutterMethodChannel
               /**
                 name:static const platform1 = const MethodChannel('com.jk/battery'); 的 com.jk/battery,名字自己定义: 域名/名字
                 binaryMessenger:  二进制消息
                */
               let channel = FlutterMethodChannel(name: "com.jk/battery", binaryMessenger: flutterController.binaryMessenger);
        
               // 3.监听channnel方法
               channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
                   guard call.method == "getBatteryInfo" else {
                        // 找不到该方法
                        result(FlutterMethodNotImplemented)
                        return;
                   }
        
                   let device = UIDevice.current
                   // 电池电量的探测,设置为true,才能更好的获取电量
                   device.isBatteryMonitoringEnabled = true
        
                   if device.batteryState == .unknown {
                        result(FlutterError(code: "Unknown", message: "Battery is unknown", details: nil))
                    } else {
                        result(Int(device.batteryLevel * 100))
                    }
        
                }
        
                GeneratedPluginRegistrant.register(with: self)
                return super.application(application, didFinishLaunchingWithOptions: launchOptions)
           }
        }
        
      iOS设备
      • <2>、OC 代码

        #import "AppDelegate.h"
        #import "GeneratedPluginRegistrant.h"
        
        @implementation AppDelegate
        
        - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        
            // 1.获取FlutterViewController(是应用程序的默认Controller)
            FlutterViewController *flutterController = (FlutterViewController *)self.window.rootViewController;
        
            // 2.获取MethodChannel(方法通道)
            FlutterMethodChannel *batteryChannel = [FlutterMethodChannel methodChannelWithName:@"com.jk/battery" binaryMessenger:flutterController.binaryMessenger];
        
            // 3.监听方法调用(会调用传入的回调函数)
            __weak typeof(self) weakSelf = self;
            [batteryChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
                // 3.1.判断是否是getBatteryInfo的调用
                if ([@"getBatteryInfo" isEqualToString:call.method]) {
                    // 1.iOS中获取信息的方式
                    int batteryLevel = [weakSelf getBatteryLevel];
                    // 2.如果没有获取到,那么返回给Flutter端一个异常
                    if (batteryLevel == -1) {
                        result([FlutterError errorWithCode:@"UNAVAILABLE"
                                message:@"Battery info unavailable"
                                details:nil]);
                    } else {
                        // 3.通过result将结果回调给Flutter端
                        result(@(batteryLevel));
                    }
                } else {
                     // 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调
                    result(FlutterMethodNotImplemented);
                }
             }];
        
             [GeneratedPluginRegistrant registerWithRegistry:self];
             // Override point for customization after application launch.
             return [super application:application didFinishLaunchingWithOptions:launchOptions];
        }
        
        - (int)getBatteryLevel {
            // 获取信息的方法
            UIDevice* device = UIDevice.currentDevice;
            device.batteryMonitoringEnabled = YES;
            if (device.batteryState == UIDeviceBatteryStateUnknown) {
                 return -1;
            } else {
                 return (int)(device.batteryLevel * 100);
            }
        }
        
        @end
        
    • 1.2.5、编写 Android 代码

      • <1>、Ktolin 代码

        package com.example.batterylevel
        
        import android.content.Context
        import android.content.ContextWrapper
        import android.content.Intent
        import android.content.IntentFilter
        import android.os.BatteryManager
        import android.os.Build
        import io.flutter.embedding.android.FlutterActivity
        import io.flutter.embedding.engine.FlutterEngine
        import io.flutter.plugin.common.MethodChannel
        
        class MainActivity: FlutterActivity() {
        
           private val CHANNEL = "com.jk/battery"
        
           override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
               super.configureFlutterEngine(flutterEngine)
        
               // 1.创建MethodChannel对象
               val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        
               // 2.添加调用方法的回调
               methodChannel.setMethodCallHandler { call, result ->
                   if (call.method == "getBatteryInfo") {
                        // 2.1.1.调用另外一个自定义方法回去电量信息
                        val batteryLevel = getBatteryLevel()
        
                        // 2.1.2. 判断是否正常获取到
                        if (batteryLevel != -1) {
        
                             // 获取到返回结果
                             result.success(batteryLevel)
                        } else {
                             // 获取不到抛出异常
                             result.error("UNAVAILABLE", "Battery level not available.", null)
                        }
                   } else {
                        // 2.2.如果调用的方法是getBatteryInfo,那么正常执行
                        result.notImplemented()
                   }
               }
            }
        
            private fun getBatteryLevel(): Int {
                val batteryLevel: Int
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
                   val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
                   batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
                } else {
                   val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
                   batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
                }
                return batteryLevel
            }
        }
        
        Android设备
      • <2>、Java 代码
        实现思路和上面是一致的,只是使用了Java来实现:

        package com.example.batterylevel2;
        
        import androidx.annotation.NonNull;
        import io.flutter.embedding.android.FlutterActivity;
        import io.flutter.embedding.engine.FlutterEngine;
        import io.flutter.plugins.GeneratedPluginRegistrant;
        import io.flutter.plugin.common.MethodChannel;
        import android.content.ContextWrapper;
        import android.content.Intent;
        import android.content.IntentFilter;
        import android.os.BatteryManager;
        import android.os.Build.VERSION;
        import android.os.Build.VERSION_CODES;
        import android.os.Bundle;
        
        public class MainActivity extends FlutterActivity {
             private static final String CHANNEL = "com.jk/battery";
        
             @Override
             public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
                  // 1.创建MethodChannel对象
                  MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
        
                  // 2.添加调用方法的回调
                  methodChannel.setMethodCallHandler(
                      (call, result) -> {
        
                      // 2.1.如果调用的方法是getBatteryInfo,那么正常执行
                      if (call.method.equals("getBatteryInfo")) {
        
                          // 2.1.1.调用另外一个自定义方法回去电量信息
                          int batteryLevel = getBatteryLevel();
        
                          // 2.1.2. 判断是否正常获取到
                          if (batteryLevel != -1) {
                              // 获取到返回结果
                             result.success(batteryLevel);
                          } else {
                             // 获取不到抛出异常
                            result.error("UNAVAILABLE", "Battery level not available.", null);
                          }
                      } else {
                          // 2.2.如果调用的方法是getBatteryInfo,那么正常执行
                          result.notImplemented();
                      }
                   }
                 );
             }
        
             private int getBatteryLevel() {
                 int batteryLevel = -1;
                 if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
                     BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
                     batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
                 } else {
                     Intent intent = new ContextWrapper(getApplicationContext()).
                     registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
                     batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
                     intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
                 }
        
                 return batteryLevel;
             }
        }
        
二、嵌入原有项目

首先,我们先明确一点: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进行编译,之后继承到现有的项目中;

          my_flutter/
          ├── .iOS/
          ├── .android/
          ├── lib/
          │   └── main.dart
          ├── test/
          └── pubspec.yaml
          
      Flutter Module
  • 2.2、嵌入iOS项目

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

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

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

    • 我们按照如下的方式,搭建一个需要继承的iOS项目:我们暂且起名字:testdemoios

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



      • 2、将项目加入CocoaPods进行管理,电脑上需要已经安装了CocoaPods,直接百度输入 CocoaPods即可搜到很多的教程,按着教程来就好

        初始化CocoaPods:

        cd 进入刚才创建的 testdemoios
        pod init
        

        编译Podfile文件:

        # platform :ios, '9.0'
        
        # 添加模块所在路径,记得 `command + s` 保存
        flutter_application_path = '../../my_flutter/my_flutter'
        load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
        
        target 'testdemoios' do
            use_frameworks!
        
            # 安装Flutter模块
            install_all_flutter_pods(flutter_application_path)
        
        end
        

        提示: flutter_application_path = '../../my_flutter/my_flutter' 后面的路径,我们可以放一个统一的位置,方便团队开发

        myflutter路径

        testdemoios路径

        路径配置

        安装成功

        安装CocoaPods的依赖

        pod install
        
    • 2.2.1、Swift代码里面嵌入 上面 my_flutter 包
      为了在既有的iOS应用中展示Flutter页面,需要启动 Flutter Engine和 FlutterViewController。
      通常建议为我们的应用预热一个 长时间存活 的FlutterEngine:
      我们将在应用启动的 AppDelegate.swift 中创建一个 FlutterEngine,并作为属性暴露给外界。

      import UIKit
      import Flutter
      @UIApplicationMain
      class AppDelegate: UIResponder, UIApplicationDelegate {
      
           var window: UIWindow?
           lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
         
           func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
      
                // 开启引擎
                flutterEngine.run()
              
                return true
            }
      }
      

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

      import UIKit
      import Flutter
      class ViewController: UIViewController {
      
         override func viewDidLoad() {
            super.viewDidLoad()
      
              view.backgroundColor = .green
      
              let button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
              button .setTitle("进入 Flutter 界面", for: .normal)
              button.backgroundColor = .brown
              button.center = view.center
              button.addTarget(self, action: #selector(click), for: .touchUpInside)
              view.addSubview(button)
         }
      
         @objc func click() {
      
              let flutterVC = FlutterViewController(engine: (UIApplication.shared.delegate as! AppDelegate).flutterEngine, nibName: nibName, bundle: nil)
              self .present(flutterVC, animated: true, completion: nil)
      
         }
      }
      
      代码效果
      • 提示:我当时运行代码报错:framework not found FlutterPluginRegistrant,我进行了一下 pod update 就好了\

      • 我们也可以省略预先创建的 FlutterEngine :不推荐这样来做,因为在第一针图像渲染完成之前,可能会出现明显的延迟。

        func showFlutter() {
            let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
            present(flutterViewController, animated: true, completion: nil)
        }
        
    • 2.2.2、Objective-C代码
      如果上面的代码希望使用Objective-C也是可以实现的:代码的逻辑是完成一致的

      • AppDelegate.h代码:

        @import UIKit;
        @import Flutter;
        
        @interface AppDelegate : FlutterAppDelegate 
        @property (nonatomic,strong) FlutterEngine *flutterEngine;
        @end
        
      • AppDelegate.m代码:

        #import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Used to connect plugins.
        
        #import "AppDelegate.h"
        
        @implementation AppDelegate
        
        - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
        
            self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
            [self.flutterEngine run];
        
            [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
            return [super application:application didFinishLaunchingWithOptions:launchOptions];
        }
        
        @end
        
      • ViewController.m代码

        @import Flutter;
        #import "AppDelegate.h"
        #import "ViewController.h"
        
        @implementation ViewController
        - (void)viewDidLoad {
           [super viewDidLoad];
        
              UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
              [button addTarget:self action:@selector(showFlutter) forControlEvents:UIControlEventTouchUpInside];
              [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
              button.backgroundColor = UIColor.blueColor;
              button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
              [self.view addSubview:button];
        }
        
        - (void)showFlutter {
             FlutterEngine *flutterEngine = ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
             FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
             [self presentViewController:flutterViewController animated:YES completion:nil];
        }
        @end
        

    扩展:在原生项目开启 Flutter的热更新和热加载,也就是flutter代码修改完,原生项目不用再启动,就可以直接看到flutter代码的修改内容
    步骤如下:
    1、关闭模拟器开启的项目,杀死模拟器的APP
    2、进入module项目的根目录,终端执行下面的代码,选择对用的设备

    flutter attach
    
    截屏2021-09-15 19.50.43.png

    3、打开app,在flutter修改完项目后,在终端输入对应的指令,app界面跟着变化

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

    • 编译为AAR文件(Android Archive):通过Flutter编译为aar,添加相关的依赖
    • 依赖模块的源码方式,在gradle进行配置

    这里我们采用第二种方式

    • 1>、创建一个Android的测试项目,使用Android Studio创建

      创建一个Android的测试项目,使用Android Studio创建
    • 2>、添加相关的依赖

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

        修改Android项目中的settings.gradle文件
        include ':app'
        rootProject.name = "testdemoandroid"
        
        setBinding(new Binding([gradle: this]))                                 // new
        evaluate(new File(                                                      // new
             settingsDir.parentFile,                                               // new
              '../my_flutter/my_flutter/.android/include_flutter.groovy'                          // new
        ))  
        

        提示: File() 后面的路径是 my_flutter 项目的路径,我放置的和上面iOS那个图一样

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

        dependencies {
           implementation project(':flutter')
        }
        

        编译代码,可能会出现如下错误: 1、这是因为从Java8开始才支持接口方法;2、Flutter Android引擎使用了该Java8的新特性

        报错

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

        通过设置Android项目工程的build.gradle配置使用Java8编译:
        compileOptions {
            sourceCompatibility 1.8
            targetCompatibility 1.8
        }
        

        接下来,我们这里尝试添加一个Flutter的screen到Android应用程序中
        Flutter提供了一个FlutterActivity来展示Flutter界面在Android应用程序中,我们需要先对FlutterActivity进行注册:

        • 在AndroidManifest.xml中进行注册

          <activity
             android:name="io.flutter.embedding.android.FlutterActivity"
             android:theme="@style/AppTheme"
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
             android:hardwareAccelerated="true"
             android:windowSoftInputMode="adjustResize"
          />
          
      • 2.3.1、Java代码

        package com.jk.testandroid;
        import androidx.appcompat.app.AppCompatActivity;
        import android.os.Bundle;
        import io.flutter.embedding.android.FlutterActivity;
        
        public class MainActivity extends AppCompatActivity {
           @Override
           protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
                  // setContentView(R.layout.activity_main);
                  startActivity(
                      FlutterActivity.createDefaultIntent(this)
                  );
              }
        }
        

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

        package com.jk.testandroid;
        import androidx.appcompat.app.AppCompatActivity;
        import android.os.Bundle;
        import io.flutter.embedding.android.FlutterActivity;
        
        public class MainActivity extends AppCompatActivity {
           @Override
           protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                //  setContentView(R.layout.activity_main);
                startActivity(
                   FlutterActivity
                    .withNewEngine()
                    .initialRoute("/my_route")
                    .build(currentActivity)
                );
           }
        }
        
      • 2.3.2、Kotlin代码

        package com.jk.test_demo_a_k
        
        import androidx.appcompat.app.AppCompatActivity
        import android.os.Bundle
        import io.flutter.embedding.android.FlutterActivity
        
        class MainActivity : AppCompatActivity() {
            override fun onCreate(savedInstanceState: Bundle?) {
                  super.onCreate(savedInstanceState)
                  //        setContentView(R.layout.activity_main)
                 startActivity(
                     FlutterActivity.createDefaultIntent(this)
                 )
            }
        }
        

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

        package com.coderwhy.test_demo_a_k
        
        import androidx.appcompat.app.AppCompatActivity
        import android.os.Bundle
        import io.flutter.embedding.android.FlutterActivity
        
        class MainActivity : AppCompatActivity() {
             override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                //         setContentView(R.layout.activity_main)
               startActivity(
                  FlutterActivity
                      .withNewEngine()
                      .initialRoute("/my_route")
                      .build(this)
                  );
             }
        }
        
三、Flutter模块调试

一旦将Flutter模块继承到你的项目中,并且使用Flutter平台的API运行Flutter引擎或UI,那么就可以先普通的Android或者iOS一样来构建自己的Android或者iOS项目了
但是Flutter的有一个非常大的优势是其快速开发,也就是hot reload。
那么对应Flutter模块,我们如何使用hot reload加速我们的调试速度呢?

  • 可以使用 flutter attach

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