Flutter圈子程序员iOS开发进阶

iOS老项目集成Flutter(iOS混编Flutter)

2018-11-02  本文已影响13人  GA_
问题

Flutter已经玩了些日子,但是老项目怎么集成Flutter呢?
集成了怎么热重载?
Flutter和原生安卓、iOS是怎么关联的呢?
Flutter和原生是怎么交互?

iOS老项目集成Flutter流程:

1、flutter环境安装 flutter中文网,特别详细,跟着来就行。
2、确保环境配置正确,全部都是小对号
终端执行

flutter doctor -v

如下是打印结果:

houjianan:FlutterMixed> flutter doctor -v
[✓] Flutter (Channel master, v0.10.2-pre.119, on Mac OS X 10.13.4 17E202, locale zh-Hans-CN)
    • Flutter version 0.10.2-pre.119 at /Users/houjianan/flutter
    • Framework revision 50098f149d (2 days ago), 2018-10-30 22:54:49 -0400
    • Engine revision 3a67757300
    • Dart version 2.1.0 (build 2.1.0-dev.8.0 bf26f760b1)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
    • Android SDK at /Users/houjianan/Library/Android/sdk
    • Android NDK at /Users/houjianan/Library/Android/sdk/ndk-bundle
    • Platform android-28, build-tools 28.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
    • All Android licenses accepted.
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 9.4.1, Build version 9F2000
    • ios-deploy 1.9.2
    • CocoaPods version 1.5.3
[✓] Android Studio (version 3.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 29.1.1
    • Dart plugin version 181.5656
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
[✓] IntelliJ IDEA Ultimate Edition (version 2018.1.1)
    • IntelliJ at /Applications/IntelliJ IDEA.app
    • Flutter plugin version 27.1.2
    • Dart plugin version 181.4445.29
[✓] Connected device (6 available)
    • “houjianan”的 iPhone • ffba6dc829885833aab8cf53b275216ee9949c05 • ios • iOS 12.0.1
• No issues found!

环境配置好了 就可以创建一个iOS项目或者在老项目上操作

3、iOS工程Enable Bitcode 需要关闭,因为Flutter混合开发不支持Bitcode

E9666FF3-7A49-478D-AB60-C4984D9FBE51.png

4、flutter module创建

a、切换分支flutter channel master
houjianan:Desktop> cd /Users/houjianan/flutter 
houjianan:flutter> flutter channel master
 ╔════════════════════════════════════════════════════════════════════════════╗
  ║ A new version of Flutter is available!                                     ║
  ║                                                                            ║
  ║ To update to the latest version, run "flutter upgrade".                    ║
  ╚════════════════════════════════════════════════════════════════════════════╝
Switching to flutter channel 'master'...
git: Already on 'master'
git: Your branch is behind 'origin/master' by 19 commits, and can be fast-forwarded.
git:   (use "git pull" to update your local branch)
houjianan:flutter> 

拉新代码

houjianan:flutter> git pull

b、创建flutter create -t module flutter_module

houjianan:fluttermixed> cd /Users/houjianan/Desktop/fluttermixed 
houjianan:fluttermixed> ls
FlutterMixed
houjianan:fluttermixed> flutter create -t module flutter_module
Creating project flutter_module...
  flutter_module/.gitignore (created)
  flutter_module/.idea/libraries/Dart_SDK.xml (created)
  flutter_module/.idea/libraries/Flutter_for_Android.xml (created)
  flutter_module/.idea/modules.xml (created)
  flutter_module/.idea/workspace.xml (created)
  flutter_module/.metadata (created)
  flutter_module/lib/main.dart (created)
  flutter_module/flutter_module.iml (created)
  flutter_module/flutter_module_android.iml (created)
  flutter_module/pubspec.yaml (created)
  flutter_module/README.md (created)
  flutter_module/test/widget_test.dart (created)
Running "flutter packages get" in flutter_module...         21.9s
Wrote 12 files.
All done!
Your module code is in flutter_module/lib/main.dart.
houjianan:fluttermixed> ls
FlutterMixed  flutter_module
houjianan:fluttermixed> cd flutter_module/
houjianan:flutter_module> ls -la
total 88
drwxr-xr-x  15 houjianan  staff   510 11  2 10:38 .
drwxr-xr-x   5 houjianan  staff   170 11  2 10:37 ..
drwxr-xr-x  12 houjianan  staff   408 11  2 10:38 .android
-rw-r--r--   1 houjianan  staff   339 11  2 10:37 .gitignore
drwxr-xr-x   5 houjianan  staff   170 11  2 10:37 .idea
drwxr-xr-x   7 houjianan  staff   238 11  2 10:38 .ios
-rw-r--r--   1 houjianan  staff   308 11  2 10:37 .metadata
-rw-r--r--   1 houjianan  staff  4983 11  2 10:38 .packages
-rw-r--r--   1 houjianan  staff   162 11  2 10:37 [README.md](http://readme.md/)
-rw-r--r--   1 houjianan  staff   896 11  2 10:37 flutter_module.iml
-rw-r--r--   1 houjianan  staff  1465 11  2 10:37 flutter_module_android.iml
drwxr-xr-x   3 houjianan  staff   102 11  2 10:37 lib
-rw-r--r--   1 houjianan  staff  8556 11  2 10:38 pubspec.lock
-rw-r--r--   1 houjianan  staff   370 11  2 10:37 pubspec.yaml
drwxr-xr-x   3 houjianan  staff   102 11  2 10:37 test
houjianan:flutter_module> 

创建完之后 如下图
注意:查看隐藏文件快捷键 shift+command+。

B24B53FD-3D23-4004-A08C-2737D130672E.png

创建iOS项目的Config文件(管理Xcode工程的配置衔接文件) 里面包含分别创建 Flutter.xcconfig、Debug.xcconfig、Release.xcconfig 三个配置文件;其中Flutter.xcconfig 是指向外目录flutter module的Generated.xcconfig 文件路径引用文件,其他两个代表Xcode的环境配置文件。

10FEC9A7-44D1-423E-B808-82F69E0DE56C.png
E88EBAF3-AE9A-422D-899F-B0D98933C0CF.png

使用了cocoapods 在Debug和Release里面添加

#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.release.xcconfig"

Flutter.xcconfig

//
//  Flutter.xcconfig
//  FlutterMixed
//
//  Created by 侯佳男 on 2018/11/2.
//  Copyright © 2018年 侯佳男. All rights reserved.
//
#include "../../flutter_module/.ios/Flutter/Generated.xcconfig"
ENABLE_BITCODE=NO

Debug.xcconfig

//
//  Debug.xcconfig
//  FlutterMixed
//
//  Created by 侯佳男 on 2018/11/2.
//  Copyright © 2018年 侯佳男. All rights reserved.
//
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"

Release.xcconfig

//
//  Release.xcconfig
//  FlutterMixed
//
//  Created by 侯佳男 on 2018/11/2.
//  Copyright © 2018年 侯佳男. All rights reserved.
//
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
FLUTTER_BUILD_MODE=release

Xcode project环境配置选择

EEE2A73B-AB3E-4C1B-890A-8C4208822188.png

添加脚本

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh"build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh”embed
52EB1B8C-406A-4C1D-9250-3DD0A3445B2C.png 4D47862C-91B6-42A8-9F5D-A70FA5FD6001.png

注意:
Run Script 在Target Dependencies或者[CP]Check pods Manifest.lock后面

添加好了之后run下项目,就会执行脚本,iOS工程文件下会有一个Flutter文件夹(第一次搞是这么回事儿,有时候就是不会生成,具体为啥不清楚,有知道的请留言告诉我,感谢!)。
run之后如果没有Flutter这个文件夹,手动创建一个,然后把flutter_module->.ios->Fluter里面的App.framework、engine、flutter_assets添加进刚才手动创建的Flutter文件夹内

A47D9226-51D0-487B-A95E-20FE342AEAA5.png

项目里面文件夹的颜色不同。


012931A0-DF96-4FF2-A46A-2E4332EB716D.png

蓝色文件夹是选择了Create folder references
黄色文件夹是选择了Create groups
flutter_assets一定要蓝色的 不然flutter界面啥都看不见。

用Android Studio运行项目

main.dart文件里面的内容全部替换为如下代码

import 'package:flutter/material.dart';
import 'package:flutter/services.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',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the
        // counter didn't reset back to zero; the application is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {

  // 创建一个给native的channel (类似iOS的通知)
  static const methodChannel = const MethodChannel('com.pages.your/native_get');

  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;

      print('flutter的log打印:现在输出count=$_counter');
      // 当个数累积到3的时候给客户端发参数
      if(_counter == 3) {
        _toNativeSomethingAndGetInfo();
      }

      // 当个数累积到5的时候给客户端发参数
      if(_counter == 1002) {
        Map<String, String> map = { "title": "这是一条来自flutter的参数" };
        methodChannel.invokeMethod('toNativePush',map);
      }

      // 当个数累积到8的时候给客户端发参数
      if(_counter == 1005) {
        Map<String, dynamic> map = { "content": "flutterPop回来","data":[1,2,3,4,5]};
        methodChannel.invokeMethod('toNativePop',map);
      }
    });
  }

  // 给客户端发送一些东东 , 并且拿到一些东东
  Future<Null> _toNativeSomethingAndGetInfo() async {
    dynamic result;
    try {
      result = await methodChannel.invokeMethod('toNativeSomething','大佬你点击了$_counter下');
    } on PlatformException {
      result = 100000;
    }
    setState(() {
      // 类型判断
      if (result is int) {
        _counter = result;
      }
    });
  }

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
//      appBar: new AppBar(
//        // Here we take the value from the MyHomePage object that was created by
//        // the App.build method, and use it to set our appbar title.
//        title: new Text(widget.title),
//      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'he button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

OC-ViewController.m

//
//  ViewController.m
//  FlutterMixed
//
//  Created by 侯佳男 on 2018/11/2.
//  Copyright © 2018年 侯佳男. All rights reserved.
//
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interfaceViewController()
@end
@implementationViewController
- (void)viewDidLoad {
    [superviewDidLoad];
}
- (void)didReceiveMemoryWarning {
    [superdidReceiveMemoryWarning];
}
- (void)pushFlutterViewController {
    FlutterViewController* flutterViewController = [[FlutterViewControlleralloc] initWithProject:nilnibName:nilbundle:nil];
    flutterViewController.navigationItem.title= @"Flutter Demo";
    __weak__typeof(self) weakSelf = self;
    // 要与main.dart中一致
    NSString*channelName = @"com.pages.your/native_get";
    FlutterMethodChannel*messageChannel = [FlutterMethodChannelmethodChannelWithName:channelName binaryMessenger:flutterViewController];
    [messageChannel setMethodCallHandler:^(FlutterMethodCall* _Nonnullcall, FlutterResult  _Nonnullresult) {
        // call.method 获取 flutter 给回到的方法名,要匹配到 channelName 对应的多个 发送方法名,一般需要判断区分
        // call.arguments 获取到 flutter 给到的参数,(比如跳转到另一个页面所需要参数)
        // result 是给flutter的回调, 该回调只能使用一次
        NSLog(@"flutter 给到我:\nmethod=%@ \narguments = %@",call.method,call.arguments);
        if([call.methodisEqualToString:@"toNativeSomething"]) {
            UIAlertView*alertView = [[UIAlertViewalloc] initWithTitle:@"flutter回调"message:[NSStringstringWithFormat:@"%@",call.arguments] delegate:selfcancelButtonTitle:@"确定"otherButtonTitles:nil];
            [alertView show];
            // 回调给flutter
            if(result) {
                result(@1000);
            }
        } elseif([call.methodisEqualToString:@"toNativePush"]) {
//            ThirdViewController *testVC = [[ThirdViewController alloc] init];
//            testVC.parames = call.arguments;
//            [weakSelf.navigationController pushViewController:testVC animated:YES];
        } elseif([call.methodisEqualToString:@"toNativePop"]) {
            [weakSelf.navigationControllerpopViewControllerAnimated:YES];
        }
    }];
//    [self.navigationController pushViewController:flutterViewController animated:YES];
    [selfpresentViewController:flutterViewController animated:YEScompletion:nil];
}
-(void)touchesBegan:(NSSet<UITouch*> *)touches withEvent:(UIEvent*)event{
    [selfpushFlutterViewController];
}
@end

OC-AppDelegate.h

//
//  AppDelegate.h
//  FlutterMixed
//
//  Created by 侯佳男 on 2018/11/2.
//  Copyright © 2018年 侯佳男. All rights reserved.
//
#import <Flutter/Flutter.h>
@interfaceAppDelegate : FlutterAppDelegate<UIApplicationDelegate, FlutterAppLifeCycleProvider>
@end

OC-AppDelegate.m

//
//  AppDelegate.h
//  FlutterMixed
//
//  Created by 侯佳男 on 2018/11/2.
//  Copyright © 2018年 侯佳男. All rights reserved.
//
#import "AppDelegate.h"
@interfaceAppDelegate()
@end
@implementationAppDelegate
{
    FlutterPluginAppLifeCycleDelegate*_lifeCycleDelegate;
}
- (instancetype)init {
    if(self= [superinit]) {
        _lifeCycleDelegate= [[FlutterPluginAppLifeCycleDelegatealloc] init];
    }
    returnself;
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
    return[_lifeCycleDelegateapplication:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
    [_lifeCycleDelegateapplicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication*)application {
    [_lifeCycleDelegateapplicationWillEnterForeground:application];
}
- (void)applicationWillResignActive:(UIApplication*)application {
    [_lifeCycleDelegateapplicationWillResignActive:application];
}
- (void)applicationDidBecomeActive:(UIApplication*)application {
    [_lifeCycleDelegateapplicationDidBecomeActive:application];
}
- (void)applicationWillTerminate:(UIApplication*)application {
    [_lifeCycleDelegateapplicationWillTerminate:application];
}
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
    [_lifeCycleDelegateapplication:application
didRegisterUserNotificationSettings:notificationSettings];
}
- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [_lifeCycleDelegateapplication:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void(^)(UIBackgroundFetchResultresult))completionHandler {
    [_lifeCycleDelegateapplication:application
       didReceiveRemoteNotification:userInfo
             fetchCompletionHandler:completionHandler];
}
- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
    return[_lifeCycleDelegateapplication:application openURL:url options:options];
}
-(BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
    return[_lifeCycleDelegateapplication:application handleOpenURL:url];
}
-(BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
  sourceApplication:(NSString*)sourceApplication
         annotation:(id)annotation {
    return[_lifeCycleDelegateapplication:application
                                   openURL:url
                         sourceApplication:sourceApplication
                                annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
  completionHandler:(void(^)(BOOLsucceeded))completionHandler NS_AVAILABLE_IOS(9_0) {
    [_lifeCycleDelegateapplication:application
       performActionForShortcutItem:shortcutItem
                  completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnullNSString*)identifier
  completionHandler:(nonnullvoid(^)(void))completionHandler {
    [_lifeCycleDelegateapplication:application
handleEventsForBackgroundURLSession:identifier
                  completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void(^)(UIBackgroundFetchResultresult))completionHandler {
    [_lifeCycleDelegateapplication:application performFetchWithCompletionHandler:completionHandler];
}
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
    [_lifeCycleDelegateaddDelegate:delegate];
}
#pragma mark - Flutter
// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
    UIViewController* viewController = [UIApplicationsharedApplication].keyWindow.rootViewController;
    if([viewController isKindOfClass:[FlutterViewControllerclass]]) {
        return(FlutterViewController*)viewController;
    }
    returnnil;
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    [supertouchesBegan:touches withEvent:event];
    // Pass status bar taps to key window Flutter rootViewController.
    if(self.rootFlutterViewController!= nil) {
        [self.rootFlutterViewControllerhandleStatusBarTouches:event];
    }
}
@end

贴完代码运行Xcode 点击屏幕 跳转flutter界面。
更改flutter界面代码 再运行Xcode 点击屏幕 跳转flutter界面,界面被更改。
热重载具体内容看闲鱼相关文章,多人写作开发看闲鱼相关文章,可以关注闲鱼公众号,他们定期更新超优质Flutter相关文章。s

中文文档
混编详细一篇
混编详细一篇
闲鱼一篇
官网
Flutter TIPS
Flutter和iOS交互

上一篇下一篇

猜你喜欢

热点阅读