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
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。