Flutter学习之混合开发

Posted GY-93

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter学习之混合开发相关的知识,希望对你有一定的参考价值。

Flutter学习之混合开发

1. 调用原生功能

有些时候,Flutter想要调用一些原生的能力,比如相机、相册、Map,这个时候我们就需要用到一些Flutter的一些插件了,在pub.dev上,官方给我们提供了许多的插件。

想要调用原生的能力,但是确实没有对应的插件,这个时候就可以需要我们去写一部分原生的代码实现这个功能,然后再使用Dart调用原生的代码,来达到调用原生能力的目的

还有一种是公司已经有原生的APP(androidios)但是新开发一个模块,或则重构模块功能的时候,我们希望使用Flutter来开发或则重构新的功能, 然后再把Flutter开发的代码集成到原生的项目中。

以上几点问题就造成了 混合开发

1.1 Camera(已有三方插件支持直接调用)

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

1.1.1 添加依赖

首先我们添加image_picker的依赖:

dependencies:
  image_picker: ^0.8.4+4

1.1.2 平台配置

对IOS平台,想要访问相机和相册,对于用户来说这是隐私,需要获取用户的允许:

  • 修改info.plist文件/ios/Runner/Info.plist
  • 添加对相册的访问权限:Privacy - Photo Library Usage Description
  • 添加对相机的访问权限: Privacy - Camera Usage Description

如果没有配置对应的权限,点击获取的话,APP是会崩溃的,所以这里我们需要使用Xcode打开项目 ,然后配置对应的权限

配置完对应的权限之后, 在运行demo,点击选择照片,会弹出如下提示框:

1.1.3 代码实现

image_picker的核心方法是pickerImage方法

 Future<XFile?> pickImage(
    required ImageSource source,
    double? maxWidth,
    double? maxHeight,
    int? imageQuality,
    CameraDevice preferredCameraDevice = CameraDevice.rear,
  ) 
    return platform.getImage(
      source: source,
      maxWidth: maxWidth,
      maxHeight: maxHeight,
      imageQuality: imageQuality,
      preferredCameraDevice: preferredCameraDevice,
    );
  
  • 可以传入数据源ImageSource、图片的大小、质量、前后摄像头等
  • 数据源是必传参数:ImageSource是一个枚举类型:
enum ImageSource 
  /// Opens up the device camera, letting the user to take a new picture.
  camera, //相机

  /// Opens the user's photo gallery.
  gallery,//相册

  • 案例演练:
import 'package:flutter/material.dart';
import 'dart:io';

import 'package:image_picker/image_picker.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  


class MyHomePage extends StatefulWidget 
  MyHomePage(Key? key, required this.title) : super(key: key);
  final String title;


  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  XFile? imageFile;
  final ImagePicker _picker = ImagePicker();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '访问原生功能相册',
            ),
            ElevatedButton(onPressed: _pickImage, child: Text("请选择一张图片")),
            imageFile == null ? Text("请您先选择一张照片") : Image.file(File(imageFile!.path)),
          ],
        ),
      ),
    );
  
/* 获取相册图片是异步的, 访问相册*/
  void _pickImage() async 
    //访问用户相册
    XFile? file = await _picker.pickImage(source: ImageSource.gallery) ;
    setState(() 
      imageFile = file;
    );
  



选择照片后效果:

1.2 电池信息(没有三方插件支持,需要编写原生代码)

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

1.2.1 平台通过介绍

官方文档介绍
平台通过是如何工作的?

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

调用过程大致如下:

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

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

1.2.2 创建测试项目

这里我们创建一个获取电池信息的demo,分别通过IOS和Android来获取对应的信息

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

  • Flutter默认的创建方式创建出来的项目,IOS对应是swift语言,Android对应的kotlin语言
flutter create xxx
  • 创建方式二:指定编程语言
    如果我们希望创建项目,IOS对应的Objetive-C语言,Android创建对应的Java语言,那么我们可以指定编程语言创建
flutter create -i objc -a java xxx

1.2.3 编写Dart代码

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

  • 创建对象时,需要传入一个name,该name是区分多个通信的名称
  • 可以通过调用该对象的invokeMethod来对应的平台发送消息进行通信
    • 该调用是异步操作,需要通过await获取then回调来获取结果
class _GYHomePageState extends State<GYHomePage> 
  //核心代码一:
  static const platform = MethodChannel("gy.com/battery");

  int _result = 0;
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title:const Text("battery")),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(onPressed: ()

            , child: const Text("获取手机电量")),
            Text("手机电量值==$_result"),
          ],
        ),
      ),
    );
  

  void getPhoneBattery() async 
    //核心代码二:
    final int result = await platform.invokeMethod("getBatteryInfo");
    setState(() 
      _result = result;
    );
  

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

  • IOS中可以通过Objective-C或swift来实现
  • Android可以通过Kotlin或Java来实现

1.2.4 编写IOS平台代码

1.2.4.1 swift代码实现

  • 代码相关操作步骤如下:
    • 获取FlutterViewController(是应用程序的默认Controller)
    • 获取MthodChannel(方法通道)(注意:这里需要根据我们创建名称来获取
    • 监听方法调用(会调用传入的回调函数)
      • IOS中获取信息的方式
      • 如果没有获取到,那么返回给Flutter端一个异常
      • 通过result将结果回调给Flutter端
      • 判断是否是getBatteryInfo的调用,告知Flutter端没有实现对应的方法
      • 如果调用的是getBatteryInfo方法,那么通过封装的另外一个方法实现回调
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate 
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool 
    //1.获取应用程序的FlutterViewController(是应用程序的默认Controller)
    if let controller: FlutterViewController = window.rootViewController as? FlutterViewController 
        //2.获取MethodChannel(方法通道) name: 是和flutter端定义好的通道名称
        let batteryChannel = FlutterMethodChannel(name: "gy.com/battery",
                                                  binaryMessenger: controller.binaryMessenger)
        
        //3.监听方法调用(会调用传入的回调函数)
        batteryChannel.setMethodCallHandler  [weak self] (call: FlutterMethodCall, result :@escaping FlutterResult) in
            guard let strongSelf = self else return
            
            //3.1判断回调方法是否是getBatteryInfo获取电量的方法
            guard call.method == "getBatteryInfo" else 
                result(FlutterMethodNotImplemented)
                return
            
            
            //3.2如果调用的是getBatteryInfo方法,那么封装另外一个方法实现回调
            strongSelf.receiveBatteryLevel(result: result)
        
    
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  
    
    
    /// 获取手机电量并回调
    /// - Parameter result: <#result description#>
    private func receiveBatteryLevel(result: FlutterResult) 
        //1.IOS中获取设备信息的方式
        let device = UIDevice.current
        //是否开启电池监控
        device.isBatteryMonitoringEnabled = true
        
        //2.如果没有获取到电池信息,那么返回给Flutter一个异常, IOS中模拟器是无法获取到电池电量的,只有真机才可以获取
        if device.batteryState == UIDevice.BatteryState.unknown 
            result(FlutterError(code: "UNAVAILABLE",
                                message: "无法获取到手机电池信息",
                                details: nil))
         else 
            //3. 通过result将结果回调给Flutter端
            result(Int(device.batteryLevel))
        
    

1.2.4.2 Objective-C代码实现

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

#import <Flutter/Flutter.h>
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"

@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions 
  // 1.获取FlutterViewController(是应用程序的默认Controller)
  FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

  // 2.获取MethodChannel(方法通道)
  FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
                                          methodChannelWithName:@"gy.com/battery"
                                          binaryMessenger:controller.binaryMessenger];
  
  // 3.监听方法调用(会调用传入的回调函数)
  __weak typeof(self) weakSelf = self;
  [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult 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:@"无法获取到手机电池信息"
                                   details:nil]);
       else 
        // 3.通过result将结果回调给Flutter端
        result(@(batteryLevel));
      
     else 
      // 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调
      result(FlutterMethodNotImplemented);
    
  ];

  [GeneratedPluginRegistrant registerWithRegistry:self];
  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代码

思路和上面是一致是 ,只是使用Kotlin来实现的,
编写Android代码,我们建议使用IDEA重新打开Android的项目, 不要在flutter工程里面的android文件夹下面直接写代码。因为使用IDEA重新打开Android项目, 工具会优化项目的目录结构。

1.2.5.1 kotlin 实现代码

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() 
    //定义一个约定的渠道名称val: 声明一个只读变量
    private val channel_name = "gy.com/battery";

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) 
        //1.创建MethodChannel对象
        val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel_name);

        //2.添加方法的调用
        methodChannel.setMethodCallHandler  call, result ->
            //判断调用的方法是否是getBatteryInfo
            if (call.method == "getBatteryInfo") 
                //调用方法获取电量
                val batteryLevel = getBatteryLevel()
                if (batteryLevel != -1) 
                    //获取到返回结果
                    result.success(batteryLevel)
                 else 
                    //获取不到抛出异常
                    result.error("UNAVAILABLE","获取不到电量信息",null)
                
             else 
                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
    


1.2.5.2 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 = "coderwhy.com/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 以上是关于Flutter学习之混合开发的主要内容,如果未能解决你的问题,请参考以下文章

flutter学习之widget的显示和隐藏

flutter学习之widget的显示和隐藏

flutter学习之widget的显示和隐藏

做混合的话Uniapp和Flutter我应该学哪个啊?

Flutter学习之动画实现原理浅析

FlutterFlutter 混合开发 ( 简介 | Flutter 混合开发集成步骤 | 创建 Flutter Module )