Day22 - Flutter - 混合開(kāi)發(fā)

概述

  • 調(diào)用原生功能
  • 嵌入原有項(xiàng)目
  • Flutter模塊調(diào)試
一、調(diào)用原生功能
  • 1.1、Camera
    某些應(yīng)用程序可能需要使用移動(dòng)設(shè)備進(jìn)行拍照或者選擇相冊(cè)中的照片,F(xiàn)lutter官方提供了插件:image_picker

    • 1.1.1常拓、添加依賴
      添加對(duì)image_picker的依賴:https://pub.dev/packages/image_picker掐禁,在項(xiàng)目的 pubspec.ymal 里面添加下面的依賴即可怜械,然后執(zhí)行右上角的 Pub get

      dependencies:
           image_picker: ^0.6.7+1
      
    • 1.1.2、平臺(tái)配置
      對(duì)iOS平臺(tái)傅事,想要訪問(wèn)相冊(cè)或者相機(jī)缕允,需要獲取用戶的允許:
      依然是修改 info.plist 文件:/ios/Runner/Info.plist
      添加對(duì)相冊(cè)的訪問(wèn)權(quán)限:Privacy - Photo Library Usage Description
      添加對(duì)相機(jī)的訪問(wèn)權(quán)限:Privacy - Camera Usage Description

      • 拓展:其他的權(quán)限
      • 相機(jī)權(quán)限:Privacy - Camera Usage Description 是否允許此App使用你的相機(jī)?
      • 相冊(cè)權(quán)限:Privacy - Photo Library Usage Description 是否允許此App訪問(wèn)你的媒體資料庫(kù)蹭越?
      • 通訊錄權(quán)限:Privacy - Contacts Usage Description 是否允許此App訪問(wèn)你的通訊錄障本?
      • 藍(lán)牙權(quán)限:Privacy - Bluetooth Peripheral Usage Description 是否許允此App使用藍(lán)牙?
      • 使用期間定位權(quán)限:Privacy - Location When In Use Usage Description 是否允許此App使用定位服務(wù)响鹃?
      • 始終定位權(quán)限:Privacy - Location Always Usage Description 是否允許此App始終使用定位服務(wù)驾霜?
      • 語(yǔ)音轉(zhuǎn)文字權(quán)限:Privacy - Speech Recognition Usage Description 是否允許此App使用語(yǔ)音識(shí)別?
      • 日歷權(quán)限:Privacy - Calendars Usage Description 是否允許此App使用日歷买置?
      • 健康—讀取數(shù)據(jù): Privacy - Health Share Usage Description 是否允許此App讀取健康數(shù)據(jù)粪糙?
      • 健康—寫入數(shù)據(jù): Privacy - Health Share Usage Description 是否允許此App寫入健康數(shù)據(jù)?
      • 讀取HomeKit: Privacy - HomeKit Usage Description 是否允許此App訪問(wèn)HomeKit忿项?
      • 麥克風(fēng):Privacy - Microphone Usage Description 是否允許此App訪問(wèn)麥克風(fēng)蓉冈?
      • 提醒事項(xiàng): Privacy - Reminders Usage Description 是否允許此App訪問(wèn)提醒事項(xiàng)城舞?
      • 運(yùn)動(dòng)與健身: Privacy - Motion Usage Description 是否允許此App訪問(wèn)運(yùn)動(dòng)與健身?
      • 面部ID權(quán)限: Privacy - Face ID Usage Description 是否允許此App訪問(wèn)Face ID寞酿?

      之后選擇相冊(cè)或者訪問(wèn)相機(jī)時(shí)家夺,會(huì)彈出如下的提示框:


    • 1.1.3、代碼實(shí)現(xiàn)
      image_picker 的核心代碼是 getImage 方法:
      可以傳入數(shù)據(jù)源伐弹、圖片的大小拉馋、質(zhì)量、前置后置攝像頭等
      數(shù)據(jù)源是必傳參數(shù):ImageSource 枚舉類型: camera:相機(jī) 惨好、gallery:相冊(cè)

      Future<PickedFile> getImage({
         @required ImageSource source,
         double maxWidth,
         double maxHeight,
         int imageQuality,
         // 默認(rèn)后置攝像頭
         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('選擇一個(gè)相冊(cè)'),
                          onPressed: _pickImage
                       ),
                       _imageFile == null ? Text('請(qǐng)選擇一張照片') : Image.file(File(_imageFile.path))
                    ],
                 ),
              );
         }
      
         void _pickImage() async {
              print('選擇相冊(cè)');
              PickedFile pickedFile = await _picker.getImage(source: ImageSource.gallery);
              setState(() {
                  _imageFile = pickedFile;
              });
         }
      }
      
      案例效果
  • 1.2煌茴、電池信息
    某些原生的信息,如果沒(méi)有很好的插件昧狮,我們可以通過(guò)景馁、platform channels(平臺(tái)通道) 來(lái)獲取信息。

    • 1.2.1逗鸣、平臺(tái)通過(guò)介紹
      平臺(tái)通過(guò)是如何工作的呢合住?

      • 消息使用platform channels(平臺(tái)通道)在客戶端(UI)和宿主(平臺(tái))之間傳遞;
      • 消息和響應(yīng)以異步的形式進(jìn)行傳遞撒璧,以確保用戶界面能夠保持響應(yīng)透葛;

      調(diào)用過(guò)程大致如下:

      • 1.客戶端(Flutter端)發(fā)送與方法調(diào)用相對(duì)應(yīng)的消息
      • 2.平臺(tái)端(iOS、Android端)接收方法卿樱,并返回結(jié)果僚害;
        • iOS端通過(guò)FlutterMethodChannel做出響應(yīng);
        • Android端通過(guò)MethodChannel做出響應(yīng)繁调;

      Flutter萨蚕、iOS、Android端數(shù)據(jù)類型的對(duì)應(yīng)關(guān)系:

      Flutter蹄胰、iOS岳遥、Android端數(shù)據(jù)類型的對(duì)應(yīng)關(guān)系
    • 1.2.2、創(chuàng)建測(cè)試項(xiàng)目
      我們這里創(chuàng)建一個(gè)獲取電池電量信息的項(xiàng)目裕寨,分別通過(guò)iOS和Android原生代碼來(lái)獲取對(duì)應(yīng)的信息:

      • 創(chuàng)建方式一:默認(rèn)創(chuàng)建方式浩蓉,目前默認(rèn)創(chuàng)建的Flutter項(xiàng)目,對(duì)應(yīng)iOS的編程語(yǔ)言是Swift宾袜,對(duì)應(yīng)Android的編程語(yǔ)言是kotlin

        flutter create batterylevel
        
      • 創(chuàng)建方式二:指定編程語(yǔ)言捻艳,如果我們希望指定編程語(yǔ)言,比如iOS編程語(yǔ)言為Objective-C庆猫,Android的編程語(yǔ)言為Java

        flutter create -i objc -a java batterylevel2
        
        • 提示: i代表 iOS认轨,a 代表 android
    • 1.2.3、寫Dart代碼
      在Dart代碼中月培,我們需要?jiǎng)?chuàng)建一個(gè)MethodChannel對(duì)象:

      • 創(chuàng)建該對(duì)象時(shí)好渠,需要傳入一個(gè)name昨稼,該name是區(qū)分多個(gè)通信的名稱
      • 可以通過(guò)調(diào)用該對(duì)象的invokeMethod來(lái)給對(duì)應(yīng)的平臺(tái)發(fā)送消息進(jìn)行通信
        • 該調(diào)用是異步操作,需要通過(guò)await獲取then回調(diào)來(lái)獲取結(jié)果

          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(
                      // 啟動(dòng)要顯示的界面
                      home: HomePage(),
                  );
              }
          }
          
          class HomePage extends StatelessWidget {
             @override
             Widget build(BuildContext context) {
                 return Scaffold(
                     appBar: AppBar(
                        title: Text("原生電池的調(diào)用"),
                     ),
                     body: JKBatteryLevel(),
                 );
             }
          }
          
          class JKBatteryLevel extends StatefulWidget {
              @override
              _JKBatteryLevelState createState() => _JKBatteryLevelState();
          }
          
          class _JKBatteryLevelState extends State<JKBatteryLevel> {
               // 定義一個(gè)平臺(tái)通道
               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 {
                   // 調(diào)用原生的電池信息
                   final result = await platform1.invokeMethod('getBatteryInfo');
                    setState(() {
                        _batterylevel = result;
                    });
              }
          }
          

          當(dāng)我們通過(guò) platform.invokeMethod調(diào)用對(duì)應(yīng)平臺(tái)方法時(shí)拳锚,需要在對(duì)應(yīng)的平臺(tái)實(shí)現(xiàn)其操作:
          iOS 中可以通過(guò) Objective-CSwift 來(lái)實(shí)現(xiàn)
          Android 中可以通過(guò) Java 或者 Kotlin 來(lái)實(shí)現(xiàn)

    • 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 {
        
               // 實(shí)現(xiàn)獲取電量信息的功能
               // 1、獲取FlutterViewController
               let flutterController: FlutterViewController = window.rootViewController as! FlutterViewController
        
               // 2拌蜘、創(chuàng)建 FlutterMethodChannel
               /**
                 name:static const platform1 = const MethodChannel('com.jk/battery'); 的 com.jk/battery杆烁,名字自己定義: 域名/名字
                 binaryMessenger:  二進(jìn)制消息
                */
               let channel = FlutterMethodChannel(name: "com.jk/battery", binaryMessenger: flutterController.binaryMessenger);
        
               // 3.監(jiān)聽(tīng)channnel方法
               channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
                   guard call.method == "getBatteryInfo" else {
                        // 找不到該方法
                        result(FlutterMethodNotImplemented)
                        return;
                   }
        
                   let device = UIDevice.current
                   // 電池電量的探測(cè),設(shè)置為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設(shè)備
      • <2>兔魂、OC 代碼

        #import "AppDelegate.h"
        #import "GeneratedPluginRegistrant.h"
        
        @implementation AppDelegate
        
        - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        
            // 1.獲取FlutterViewController(是應(yīng)用程序的默認(rèn)Controller)
            FlutterViewController *flutterController = (FlutterViewController *)self.window.rootViewController;
        
            // 2.獲取MethodChannel(方法通道)
            FlutterMethodChannel *batteryChannel = [FlutterMethodChannel methodChannelWithName:@"com.jk/battery" binaryMessenger:flutterController.binaryMessenger];
        
            // 3.監(jiān)聽(tīng)方法調(diào)用(會(huì)調(diào)用傳入的回調(diào)函數(shù))
            __weak typeof(self) weakSelf = self;
            [batteryChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
                // 3.1.判斷是否是getBatteryInfo的調(diào)用
                if ([@"getBatteryInfo" isEqualToString:call.method]) {
                    // 1.iOS中獲取信息的方式
                    int batteryLevel = [weakSelf getBatteryLevel];
                    // 2.如果沒(méi)有獲取到,那么返回給Flutter端一個(gè)異常
                    if (batteryLevel == -1) {
                        result([FlutterError errorWithCode:@"UNAVAILABLE"
                                message:@"Battery info unavailable"
                                details:nil]);
                    } else {
                        // 3.通過(guò)result將結(jié)果回調(diào)給Flutter端
                        result(@(batteryLevel));
                    }
                } else {
                     // 3.2.如果調(diào)用的是getBatteryInfo的方法, 那么通過(guò)封裝的另外一個(gè)方法實(shí)現(xiàn)回調(diào)
                    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.創(chuàng)建MethodChannel對(duì)象
               val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        
               // 2.添加調(diào)用方法的回調(diào)
               methodChannel.setMethodCallHandler { call, result ->
                   if (call.method == "getBatteryInfo") {
                        // 2.1.1.調(diào)用另外一個(gè)自定義方法回去電量信息
                        val batteryLevel = getBatteryLevel()
        
                        // 2.1.2. 判斷是否正常獲取到
                        if (batteryLevel != -1) {
        
                             // 獲取到返回結(jié)果
                             result.success(batteryLevel)
                        } else {
                             // 獲取不到拋出異常
                             result.error("UNAVAILABLE", "Battery level not available.", null)
                        }
                   } else {
                        // 2.2.如果調(diào)用的方法是getBatteryInfo,那么正常執(zhí)行
                        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設(shè)備
      • <2>析校、Java 代碼
        實(shí)現(xiàn)思路和上面是一致的,只是使用了Java來(lái)實(shí)現(xiàn):

        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.創(chuàng)建MethodChannel對(duì)象
                  MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
        
                  // 2.添加調(diào)用方法的回調(diào)
                  methodChannel.setMethodCallHandler(
                      (call, result) -> {
        
                      // 2.1.如果調(diào)用的方法是getBatteryInfo,那么正常執(zhí)行
                      if (call.method.equals("getBatteryInfo")) {
        
                          // 2.1.1.調(diào)用另外一個(gè)自定義方法回去電量信息
                          int batteryLevel = getBatteryLevel();
        
                          // 2.1.2. 判斷是否正常獲取到
                          if (batteryLevel != -1) {
                              // 獲取到返回結(jié)果
                             result.success(batteryLevel);
                          } else {
                             // 獲取不到拋出異常
                            result.error("UNAVAILABLE", "Battery level not available.", null);
                          }
                      } else {
                          // 2.2.如果調(diào)用的方法是getBatteryInfo,那么正常執(zhí)行
                          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;
             }
        }
        
二铜涉、嵌入原有項(xiàng)目

首先智玻,我們先明確一點(diǎn):Flutter設(shè)計(jì)初衷并不是為了和其它平臺(tái)進(jìn)行混合開(kāi)發(fā),它的目的是為了打造一個(gè)完整的跨平臺(tái)應(yīng)用程序芙代。
但是吊奢,實(shí)際開(kāi)發(fā)中,原有項(xiàng)目完全使用Flutter進(jìn)行重構(gòu)并不現(xiàn)實(shí)纹烹,對(duì)于原有項(xiàng)目我們更多可能采用混合開(kāi)發(fā)的方式页滚。

  • 2.1、創(chuàng)建Flutter模塊

    • 對(duì)于需要進(jìn)行混合開(kāi)發(fā)的原有項(xiàng)目铺呵,F(xiàn)lutter可以作為一個(gè)庫(kù)或者模塊裹驰,繼承進(jìn)現(xiàn)有項(xiàng)目中。

      • 模塊引入到你的Android或iOS應(yīng)用中陪蜻,以使用Flutter渲染一部分的UI邦马,或者共享的Dart代碼。
      • 在Flutter v1.12中宴卖,添加到現(xiàn)有應(yīng)用的基本場(chǎng)景已經(jīng)被支持滋将,每個(gè)應(yīng)用在同一時(shí)間可以集成一個(gè)全屏幕的Flutter實(shí)例。
    • 但是症昏,目前一些場(chǎng)景依然是有限制的:

      • 運(yùn)行多個(gè)Flutter實(shí)例随闽,或在屏幕局部上運(yùn)行Flutter可能會(huì)導(dǎo)致不可以預(yù)測(cè)的行為;
      • 在后臺(tái)模式使用Flutter的能力還在開(kāi)發(fā)中(目前不支持)肝谭;
      • 將Flutter庫(kù)打包到另一個(gè)可共享的庫(kù)或?qū)⒍鄠€(gè)Flutter庫(kù)打包到同一個(gè)應(yīng)用中掘宪,都不支持蛾扇;
      • 添加到應(yīng)用在Android平臺(tái)的實(shí)現(xiàn)基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能會(huì)有不可預(yù)知的行為魏滚。
    • 創(chuàng)建 Flutter Module

      flutter create --template module my_flutter
      

      創(chuàng)建完成后镀首,該模塊和普通的Flutter項(xiàng)目一直,可以通過(guò)Android Studio或VSCode打開(kāi)鼠次、開(kāi)發(fā)更哄、運(yùn)行;

      • 目錄結(jié)構(gòu)如下:
        • 和之前項(xiàng)目不同的iOS和Android項(xiàng)目是一個(gè)隱藏文件腥寇,并且我們通常不會(huì)單獨(dú)打開(kāi)它們?cè)賮?lái)運(yùn)行成翩;

        • 它們的作用是將Flutter Module進(jìn)行編譯,之后繼承到現(xiàn)有的項(xiàng)目中赦役;

          my_flutter/
          ├── .iOS/
          ├── .android/
          ├── lib/
          │   └── main.dart
          ├── test/
          └── pubspec.yaml
          
      Flutter Module
  • 2.2麻敌、嵌入iOS項(xiàng)目

    • 嵌入到現(xiàn)有iOS項(xiàng)目有多種方式:

      • 可以使用 CocoaPods 依賴管理和已安裝的 Flutter SDK ;
      • 也可以通過(guò)手動(dòng)編譯 Flutter engine 掂摔、你的 dart 代碼和所有 Flutter plugin 成 framework 术羔,用 Xcode 手動(dòng)集成到你的應(yīng)用中,并更新編譯設(shè)置棒呛;

      目前iOS項(xiàng)目幾乎都已經(jīng)使用Cocoapods進(jìn)行管理聂示,所以推薦使用第一種CocoaPods方式;

    • 我們按照如下的方式簇秒,搭建一個(gè)需要繼承的iOS項(xiàng)目:我們暫且起名字:testdemoios

      • 1鱼喉、為了進(jìn)行測(cè)試,我們這里創(chuàng)建一個(gè)默認(rèn)的iOS項(xiàng)目:使用Xcode創(chuàng)建即可



      • 2趋观、將項(xiàng)目加入CocoaPods進(jìn)行管理扛禽,電腦上需要已經(jīng)安裝了CocoaPods,直接百度輸入 CocoaPods即可搜到很多的教程皱坛,按著教程來(lái)就好

        初始化CocoaPods:

        cd 進(jìn)入剛才創(chuàng)建的 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' 后面的路徑,我們可以放一個(gè)統(tǒng)一的位置剩辟,方便團(tuán)隊(duì)開(kāi)發(fā)

        myflutter路徑

        testdemoios路徑

        路徑配置

        安裝成功

        安裝CocoaPods的依賴

        pod install
        
    • 2.2.1掐场、Swift代碼里面嵌入 上面 my_flutter 包
      為了在既有的iOS應(yīng)用中展示Flutter頁(yè)面,需要啟動(dòng) Flutter Engine和 FlutterViewController贩猎。
      通常建議為我們的應(yīng)用預(yù)熱一個(gè) 長(zhǎng)時(shí)間存活 的FlutterEngine:
      我們將在應(yīng)用啟動(dòng)的 AppDelegate.swift 中創(chuàng)建一個(gè) 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 {
      
                // 開(kāi)啟引擎
                flutterEngine.run()
              
                return true
            }
      }
      

      在啟動(dòng)的ViewController中吭服,創(chuàng)建一個(gè)UIButton嚷堡,并且點(diǎn)擊這個(gè)Button時(shí),彈出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("進(jìn)入 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)
      
         }
      }
      
      代碼效果
      • 提示:我當(dāng)時(shí)運(yùn)行代碼報(bào)錯(cuò):framework not found FlutterPluginRegistrant艇棕,我進(jìn)行了一下 pod update 就好了\

      • 我們也可以省略預(yù)先創(chuàng)建的 FlutterEngine :不推薦這樣來(lái)做蝌戒,因?yàn)樵诘谝会槇D像渲染完成之前串塑,可能會(huì)出現(xiàn)明顯的延遲。

        func showFlutter() {
            let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
            present(flutterViewController, animated: true, completion: nil)
        }
        
    • 2.2.2北苟、Objective-C代碼
      如果上面的代碼希望使用Objective-C也是可以實(shí)現(xiàn)的:代碼的邏輯是完成一致的

      • 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
        

    擴(kuò)展:在原生項(xiàng)目開(kāi)啟 Flutter的熱更新和熱加載桩匪,也就是flutter代碼修改完,原生項(xiàng)目不用再啟動(dòng)粹淋,就可以直接看到flutter代碼的修改內(nèi)容
    步驟如下:
    1吸祟、關(guān)閉模擬器開(kāi)啟的項(xiàng)目,殺死模擬器的APP
    2桃移、進(jìn)入module項(xiàng)目的根目錄,終端執(zhí)行下面的代碼葛碧,選擇對(duì)用的設(shè)備

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

    3借杰、打開(kāi)app,在flutter修改完項(xiàng)目后进泼,在終端輸入對(duì)應(yīng)的指令蔗衡,app界面跟著變化

  • 2.3.嵌入Android項(xiàng)目
    嵌入到現(xiàn)有Android項(xiàng)目有多種方式:

    • 編譯為AAR文件(Android Archive):通過(guò)Flutter編譯為aar,添加相關(guān)的依賴
    • 依賴模塊的源碼方式乳绕,在gradle進(jìn)行配置

    這里我們采用第二種方式

    • 1>绞惦、創(chuàng)建一個(gè)Android的測(cè)試項(xiàng)目,使用Android Studio創(chuàng)建

      創(chuàng)建一個(gè)Android的測(cè)試項(xiàng)目洋措,使用Android Studio創(chuàng)建
    • 2>济蝉、添加相關(guān)的依賴

      • 修改Android項(xiàng)目中的settings.gradle文件:

        修改Android項(xiàng)目中的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 項(xiàng)目的路徑,我放置的和上面iOS那個(gè)圖一樣

      • 我們需要在Android項(xiàng)目工程的build.gradle中添加依賴:

        dependencies {
           implementation project(':flutter')
        }
        

        編譯代碼菠发,可能會(huì)出現(xiàn)如下錯(cuò)誤: 1王滤、這是因?yàn)閺腏ava8開(kāi)始才支持接口方法;2滓鸠、Flutter Android引擎使用了該Java8的新特性

        報(bào)錯(cuò)

        解決辦法:通過(guò)設(shè)置Android項(xiàng)目工程的build.gradle配置使用Java8編譯:

        通過(guò)設(shè)置Android項(xiàng)目工程的build.gradle配置使用Java8編譯:
        compileOptions {
            sourceCompatibility 1.8
            targetCompatibility 1.8
        }
        

        接下來(lái)雁乡,我們這里嘗試添加一個(gè)Flutter的screen到Android應(yīng)用程序中
        Flutter提供了一個(gè)FlutterActivity來(lái)展示Flutter界面在Android應(yīng)用程序中,我們需要先對(duì)FlutterActivity進(jìn)行注冊(cè):

        • 在AndroidManifest.xml中進(jìn)行注冊(cè)

          <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)
                  );
              }
        }
        

        也可以在創(chuàng)建時(shí)踱稍,傳入默認(rèn)的路由:

        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)
                 )
            }
        }
        

        也可以在創(chuàng)建時(shí)指定路由:

        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模塊調(diào)試

一旦將Flutter模塊繼承到你的項(xiàng)目中珠月,并且使用Flutter平臺(tái)的API運(yùn)行Flutter引擎或UI,那么就可以先普通的Android或者iOS一樣來(lái)構(gòu)建自己的Android或者iOS項(xiàng)目了
但是Flutter的有一個(gè)非常大的優(yōu)勢(shì)是其快速開(kāi)發(fā)锌钮,也就是hot reload桥温。
那么對(duì)應(yīng)Flutter模塊,我們?nèi)绾问褂胔ot reload加速我們的調(diào)試速度呢梁丘?

  • 可以使用 flutter attach

    # --app-id是指定哪一個(gè)應(yīng)用程序
    # -d是指定連接哪一個(gè)設(shè)備
    flutter attach --app-id com.coderwhy.ios-my-test -d 3D7A877C-B0DD-4871-8D6E-0C5263B986CD
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侵浸,一起剝皮案震驚了整個(gè)濱河市旺韭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掏觉,老刑警劉巖区端,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異澳腹,居然都是意外死亡织盼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門酱塔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沥邻,“玉大人,你說(shuō)我怎么就攤上這事羊娃√迫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蕊玷,是天一觀的道長(zhǎng)邮利。 經(jīng)常有香客問(wèn)我,道長(zhǎng)垃帅,這世上最難降的妖魔是什么延届? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮贸诚,結(jié)果婚禮上方庭,老公的妹妹穿的比我還像新娘。我一直安慰自己赦颇,他們只是感情好二鳄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著媒怯,像睡著了一般订讼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扇苞,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天欺殿,我揣著相機(jī)與錄音,去河邊找鬼鳖敷。 笑死脖苏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的定踱。 我是一名探鬼主播棍潘,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了亦歉?” 一聲冷哼從身側(cè)響起恤浪,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肴楷,沒(méi)想到半個(gè)月后水由,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赛蔫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年砂客,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呵恢。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鞠值,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渗钉,到底是詐尸還是另有隱情齿诉,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布晌姚,位于F島的核電站,受9級(jí)特大地震影響歇竟,放射性物質(zhì)發(fā)生泄漏挥唠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一焕议、第九天 我趴在偏房一處隱蔽的房頂上張望宝磨。 院中可真熱鬧,春花似錦盅安、人聲如沸唤锉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窿祥。三九已至,卻和暖如春蝙寨,著一層夾襖步出監(jiān)牢的瞬間晒衩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工墙歪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留听系,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓虹菲,卻偏偏與公主長(zhǎng)得像靠胜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353