Flutter混合开发

一. 调用原生功能

1.1. Camera

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

1.1.1. 添加依赖

添加对image_picker的依赖:

dependencies:
  image_picker: ^0.6.5

1.1.2. 平台配置

对iOS平台,想要访问相册或者相机,需要获取用户的允许,依然是修改info.plist文件:/ios/Runner/Info.plist。

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

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

1.1.3. 代码实现

image_picker的核心代码是pickImage方法:

  • 可以传入数据源、图片的大小、质量、前置后置摄像头等
  • 数据源是必传参数:ImageSource枚举类型
    camera:相机
    gallery:相册
static Future<File> pickImage(
    {@required ImageSource source,
    double maxWidth,
    double maxHeight,
    int imageQuality,
    CameraDevice preferredCameraDevice = CameraDevice.rear}) async

案例演练:

import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';

class HYCameraScreen extends StatefulWidget {
  static const String routeName = "/camera";

  @override
  _HYCameraScreenState createState() => _HYCameraScreenState();
}

class _HYCameraScreenState extends State<HYCameraScreen> {
  File _image;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Camera"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _image == null ? Text("未选择图片"): Image.file(_image),
            RaisedButton(
              child: Text("选择照片"),
              onPressed: _pickImage,
            )
          ],
        ),
      ),
    );
  }

  void _pickImage() async {
    File image = await ImagePicker.pickImage(source: ImageSource.gallery);
    setState(() {
      _image = image;
    });
  }
}

1.2. 电池信息

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

1.2.1. 平台通过介绍

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

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

调用过程大致如下:

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

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

1.2.2. 创建测试项目

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

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

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

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

  • 如果我们希望指定编程语言,比如iOS编程语言为Objective-C,Android的编程语言为Java
flutter create -i objc -a java batterylevel2

1.2.3. 编写Dart代码

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

  • 创建该对象时,需要传入一个name,该name是区分多个通信的名称
  • 可以通过调用该对象的invokeMethod来给对应的平台发送消息进行通信
    该调用是异步操作,需要通过await获取then回调来获取结果
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(
      title: 'Flutter Demo',
      theme: ThemeData(
          primarySwatch: Colors.blue, splashColor: Colors.transparent),
      home: HYBatteryScreen(),
    );
  }
}

class HYBatteryScreen extends StatefulWidget {
  static const String routeName = "/battery";

  @override
  _HYBatteryScreenState createState() => _HYBatteryScreenState();
}

class _HYBatteryScreenState extends State<HYBatteryScreen> {
  // 1.创建平台通道
  // 通道的名字是唯一的,最好是:域名+功能
  static const platform = const MethodChannel("coderwhy.com/battery");
  int _result = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Battery"),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text("当前电池信息: $_result"),
            RaisedButton(
              child: Text("获取电池信息"),
              onPressed: getBatteryInfo,
            )
          ],
        ),
      ),
    );
  }

  void getBatteryInfo() async {
    // 2.唤起对应的原生方法
    final int result = await platform.invokeMethod("getBatteryInfo");
    setState(() {
      _result = result;
    });
  }
}

当我们通过 platform.invokeMethod 调用对应平台方法时,需要在对应的平台实现原生代码:

  • iOS中可以通过Objective-C或Swift来实现
  • Android中可以通过Java或者Kotlin来实现

1.2.4. 编写iOS代码

1.2.4.1. Swift代码实现

首先进入ios目录,使用Xcode打开ios项目。

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

  1. 获取FlutterViewController(是应用程序的默认Controller)
  2. 获取MethodChannel(方法通道),注意:这里需要根据我们创建时的名称来获取
  3. 监听方法调用(会调用传入的回调函数)
  4. 实现原生代码来获取电池信息,具体详细步骤看代码:
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    // 1.获取FlutterViewController(是应用程序的默认Controller)
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    
    // 2.获取MethodChannel(方法通道)
    let batteryChannel = FlutterMethodChannel(name: "coderwhy.com/battery",
                                              binaryMessenger: controller.binaryMessenger)
    
    // 3.监听方法调用(会调用传入的回调函数)
    // dart里面执行`platform.invokeMethod("getBatteryInfo")`的时候,下面的闭包就会被调用
    batteryChannel.setMethodCallHandler({
      [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
      // 3.1.判断是否是getBatteryInfo的调用,如果不是,则告知Flutter端没有实现对应的方法
      guard call.method == "getBatteryInfo" else {
        result(FlutterMethodNotImplemented)
        return
      }
      // 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调
      self?.receiveBatteryLevel(result: result)
    })
    
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    
  // 4.这个方法实现获取电池信息
  private func receiveBatteryLevel(result: FlutterResult) {
    // 4.1.iOS中获取信息的方式
    let device = UIDevice.current
    device.isBatteryMonitoringEnabled = true
    
    // 4.2.如果没有获取到,那么返回给Flutter端一个异常
    if device.batteryState == UIDevice.BatteryState.unknown {
      result(FlutterError(code: "UNAVAILABLE",
                          message: "Battery info unavailable",
                          details: nil))
    } else {
      // 4.3.通过result将结果回调给Flutter端
      result(Int(device.batteryLevel * 100))
    }
  }
}
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:@"coderwhy.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]) {
      // iOS中获取电池信息的方式
      int batteryLevel = [weakSelf getBatteryLevel];
      // 如果没有获取到,那么返回给Flutter端一个异常
      if (batteryLevel == -1) {
        result([FlutterError errorWithCode:@"UNAVAILABLE"
                                   message:@"Battery info unavailable"
                                   details:nil]);
      } else {
        // 3.通过result将结果回调给Flutter端
        result(@(batteryLevel));
      }
    } else {
      // 3.2.如果没实现,告知Flutter端没有实现对应的方法
      result(FlutterMethodNotImplemented);
    }
  }];

  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

// 获取电池信息的OC代码
- (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.2.5.1. Kotlin代码实现

首先进入android目录,使用Android Studio打开android项目。

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

import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
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

class MainActivity: FlutterActivity() {
    private val CHANNEL = "coderwhy.com/battery"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        // 1.创建MethodChannel对象
        val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) 
        
        // 2.添加调用方法的回调
        methodChannel.setMethodCallHandler {
            // Note: this method is invoked on the main thread.
            call, result ->
            // 2.1.如果调用的方法是getBatteryInfo,那么正常执行
            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 (VERSION.SDK_INT >= 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.1. 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 = "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 = (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项目的命令不一样。

flutter create --template module my_flutter

创建完成后,该模块和普通的Flutter项目一样,可以通过Android Studio或VSCode打开、开发、运行。但是它是个模块,并不是一个真正的Flutter项目。

目录结构如下:

  • 和之前项目不同的是iOS和Android项目是一个隐藏文件,并且我们通常不会单独打开它们再来运行;
  • 它们的作用是将Flutter Module进行编译,之后继承到现有的项目中;
my_flutter/
├── .ios/
├── .android/
├── lib/
│   └── main.dart
├── test/
└── pubspec.yaml

2.2. 嵌入iOS项目

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

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

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

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

  1. 为了进行测试,我们这里使用Xcode创建一个默认的iOS项目
  2. 将项目加入CocoaPods进行管理

首先,电脑上需要安装CocoaPods,不会的自己百度。
初始化CocoaPods:

pod init

安装CocoaPods的依赖:

pod install

编译Podfile文件:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

# 1.添加模块所在路径
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'ios_my_test' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  
  # 2.安装Flutter模块
  install_all_flutter_pods(flutter_application_path)
  
  # Pods for ios_my_test

end

重新执行安装CocoaPods的依赖:

pod install

最后:现在Flutter模块和iOS项目都创建好了,我们需要做的就是将这两者放到同一个目录下,因为,我们引入模块的路径是通过flutter_application_path = '../my_flutter'的方式,所以我们需要创建一个新的文件夹,然后将Flutter模块和iOS项目都放到这个文件夹下,以后我们想写flutter代码就使用Android Studio打开my_flutter项目,想写iOS项目的代码就用Xcode打开iOS项目。

2.2.1. Swift代码

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

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

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

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
   // 1.创建一个FlutterEngine对象
    lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       // 2.启动flutterEngine
        flutterEngine.run()
        return true
    }
}

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

import UIKit
import Flutter

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
       // 1.创建一个按钮
        let button = UIButton(type: UIButton.ButtonType.custom)
        button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
        button.setTitle("Show Flutter", for: .normal)
        button.frame = CGRect(x: 80, y: 210, width: 160, height: 40)
        button.backgroundColor = UIColor.blue
        self.view.addSubview(button)
    }
    
    @objc func showFlutter() {
        // 2.创建FlutterViewController对象(需要先获取flutterEngine)
        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine;
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil);
        navigationController?.pushViewController(flutterViewController, animated: true);
    }
}

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

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];

    // Make a button to call the showFlutter function when pressed.
    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

2.3.嵌入Android项目

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

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

这里我们采用第二种方式。

  1. 使用Android Studio创建一个Android的测试项目
  1. 添加相关的依赖

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

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

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

dependencies {
  implementation project(':flutter')
}

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

这是因为从Java8开始才支持接口方法,Flutter Android引擎使用了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.coderwhy.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.coderwhy.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.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.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嵌入原有项目,在iOS中其实就是一个FlutterViewController,在安卓中就是Activity,所以我们可以在任何界面去推出一个Flutter模块。

三. Flutter模块调试

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

但是Flutter有一个非常大的优势就是热重载,也就是Hot Reload,那么对于Flutter模块,我们如何使用Hot Reload加速我们的调试速度呢?

  • 可以使用flutter attach

首先打开Flutter模块的代码,Flutter模块默认是无法Hot Reload的,这时候我们在终端执行flutter attach命令,这时候会报错,让我们指定哪一个设备和哪一个应用程序,我们根据报错信息选择设备id和应用程序id即可,命令如下:

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

命令执行完毕后,如果我们再修改Flutter模块的主题颜色代码,终端会提示,如果是Hot Reload按r,如果是Hot Restart按R,如下:

结果如下:

这样,虽然你是一个iOS项目+Flutter模块,但是你在Flutter模块中也可以实现Hot Reload功能,只需要修改Flutter模块代码后按一下r,就行了。当然,对于原生iOS项目还是要Command+R才能运行的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容