关于前端:在原生项目中集成Flutter

9次阅读

共计 25334 个字符,预计需要花费 64 分钟才能阅读完成。

概述

应用 Flutter 从零开始开发 App 是一件轻松惬意的事件,但对于一些成熟的产品来说,齐全摒弃原有 App 的历史积淀,全面转向 Flutter 是不事实的。因而应用 Flutter 去对立 Android、iOS 技术栈,把它作为已有原生 App 的扩大能力,通过有序推动来晋升挪动终端的开发效率。
目前,想要在已有的原生 App 里嵌入一些 Flutter 页面次要有两种计划。一种是将原生工程作为 Flutter 工程的子工程,由 Flutter 进行对立治理,这种模式称为对立管理模式。另一种是将 Flutter 工程作为原生工程的子模块,维持原有的原生工程治理形式不变,这种模式被称为三端拆散模式,如下图所示。

三端代码拆散模式的原理是把 Flutter 模块作为原生工程的子模块,从而疾速地接入 Flutter 模块,升高原生工程的革新老本。在 Flutter 1.1x 时代,在原生已有 app 中接入 Flutter 的步骤比拟繁琐,具体能够能够参考:Flutter 与原生混合开发
不过,从 Flutter 1.20.x 版本开始,Flutter 对原生 app 接入 Flutter 进行了优化和降级,上面是具体介绍。

原生 Android 集成 Flutter

反对的个性

  • 在 Gradle 脚本中增加一个主动构建并引入 Flutter 模块的 Flutter SDK 钩子。
  • 将 Flutter 模块构建为通用的 Android Archive (AAR) 以便集成到您本人的构建零碎中,并进步 Jetifier 与 AndroidX 的互操作性;
  • FlutterEngine API 用于启动并继续地为挂载 FlutterActivity 或 FlutterFragment 提供独立的 Flutter 环境;
  • Android Studio 的 Android 与 Flutter 同时编辑,以及 Flutter module 创立与导入向导;
  • 反对 Java 和 Kotlin 为宿主的应用程序;

集成 Flutter

首先,咱们来看一下最终的成果,如下图所示。

集成 Flutter 次要有两种形式,一种是应用 Android Studio 工具的形式,另一种是应用手动的形式。

应用 Android Studio 形式

间接应用 Android Studio 是在现有利用中主动集成 Flutter 模块比拟便捷的办法。在 Android Studio 关上现有的 Android 原生我的项目,而后顺次点击菜单按钮 File > New > New Module…创立出一个能够集成的新 Flutter 模块,或者抉择导入已有的 Flutter 模块,如下图所示。

抉择 Module 的类型为 Flutter Module,而后在向导窗口中填写模块名称、门路等信息,如下图所示。

此时,Android Studio 插件就会主动为这个 Android 我的项目配置增加 Flutter 模块作为依赖项,这时集成利用就已筹备好进行下一步的构建。

手动集成

如果想要在不应用 Flutter 的 Android Studio 插件的状况下手动将 Flutter 模块与现有的 Android 利用集成,能够应用上面的步骤。

假如咱们的原生利用在 some/path/MyApp 门路下,那么在 Flutter 我的项目的同级目录下新建一个 Flutter 模块,命令如下。

cd some/path/
flutter create -t module --org com.example my_flutter

实现下面的命令后,会在 some/path/my_flutter/ 目录下创立一个 Flutter 模块我的项目。该模块我的项目会蕴含一些 Dart 代码和一些一个暗藏的子文件夹 .android/,.android 文件夹蕴含一个 Android 我的项目,该我的项目不仅能够帮忙你通过 flutter run 运行这个 Flutter 模块的独立利用,而且还能够作为封装程序来帮忙疏导 Flutter 模块作为可嵌入的 Android 库。

同时,因为 Flutter Android 引擎须要应用到 Java 8 中的新个性。因而,须要在宿主 Android 利用的 build.gradle 文件的 android {} 块中申明了以下源兼容性代码。

android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}

接下来,须要将 Flutter module 增加到原生 Android 工程的依赖中。将 Flutter 模块增加到原生 Android 应用程序中次要有两种办法实现。应用 AAR 包形式和间接应用 module 源码的形式。应用 AAR 包形式须要先将 Flutter 模块打包成 AAR 包。假如,你的 Flutter 模块在 some/path/my_flutter 目录下,那么打包 AAR 包的命令如下。

cd some/path/my_flutter
flutter build aar

而后,依据屏幕上的提醒实现集成操作,如下图所示,当然也能够在 Android 原生工程中进行手动增加依赖代码。

事实上,该命令次要用于创立(默认状况下创立 debug/profile/release 所有模式)本地存储库,次要蕴含以下文件,如下所示。

build/host/outputs/repo
└── com
    └── example
        └── my_flutter
            ├── flutter_release
            │   ├── 1.0
            │   │   ├── flutter_release-1.0.aar
            │   │   ├── flutter_release-1.0.aar.md5
            │   │   ├── flutter_release-1.0.aar.sha1
            │   │   ├── flutter_release-1.0.pom
            │   │   ├── flutter_release-1.0.pom.md5
            │   │   └── flutter_release-1.0.pom.sha1
            │   ├── maven-metadata.xml
            │   ├── maven-metadata.xml.md5
            │   └── maven-metadata.xml.sha1
            ├── flutter_profile
            │   ├── ...
            └── flutter_debug
                └── ...

能够发现,应用下面的命令编译的 AAR 包次要分为 debug、profile 和 release 三个版本,应用哪个版本的 AAR 须要依据原生的环境进行抉择。找到 AAR 包,而后再 Android 宿主应用程序中批改 app/build.gradle 文件,使其蕴含本地存储库和上述依赖项,如下所示。

android {// ...}

repositories {
  maven {
    url 'some/path/my_flutter/build/host/outputs/repo'
    // This is relative to the location of the build.gradle file
    // if using a relative path.
  }
  maven {url 'https://storage.googleapis.com/download.flutter.io'}
}

dependencies {
  // ...
  debugImplementation 'com.example.flutter_module:flutter_debug:1.0'
  profileImplementation 'com.example.flutter_module:flutter_profile:1.0'
  releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}

当然,除了命令形式外,还能够应用 Android Studio 来构建 AAR 包。顺次点击 Android Studio 菜单中的 Build > Flutter > Build AAR 即可构建 Flutter 模块的 AAR 包,如下图所示。

除了 AAR 包形式外,另一种形式就是应用源码的形式进行依赖,行将 flutter_module 模块作为一个模块增加到 Android 原生工程中。首先,将 Flutter 模块作为子项目增加到宿主利用的 settings.gradle 中,如下所示。

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

binding 和 evaluation 脚本能够使 Flutter 模块将其本身(如 :flutter)和该模块应用的所有 Flutter 插件(如 :package_info,:video_player 等)都蕴含在 settings.gradle 上下文中,而后在原生 Android 工程的 app 目录下的 build.gradle 文件下增加如下依赖代码。

dependencies {implementation project(':flutter')
}

到此,在原生 Android 工程中集成 Flutter 环境就实现了,接下来编写代码即可。

增加 Flutter 页面

失常跳转

1,增加 FlutterActivity
Flutter 提供了一个 FlutterActivity 来作为 Flutter 的容器页面,FlutterActivity 和 Android 原生的 Activity 没有任何区别,能够认为它是 Flutter 的父容器组件,但在原生 Android 程序中,它就是一个一般的 Activity,这个 Activity 必须在 AndroidManifest.xml 中进行注册,如下所示。

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize" />

对于 theme 属性,咱们能够应用 Android 的其余款式进行替换,此主题款式会决定了利用的零碎款式。

2,关上 FlutterActivity

在 AndroidManifest.xml 中注册 FlutterActivity 后,而后咱们能够在任何中央启动这个 FlutterActivity,如下所示。

myButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(FlutterActivity.createDefaultIntent(MainActivity.this)
    );
  }
});

运行下面的代码,发现并不会跳转到 Flutter 页面,因为咱们并没有提供跳转的地址。上面的示例将演示如何应用自定义路由跳转到 Flutter 模块页面中,如下所示。

myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withNewEngine()
        .initialRoute("/my_route")
        .build(currentActivity)
      );
  }
});

其中,my_route 为 Flutter 模块的初始路由,对于 Flutter 的路由常识,能够看上面的文章:Flutter 开发之路由与导航

咱们应用 withNewEngine()工厂办法配置,创立一个的 FlutterEngine 实例。当运行下面的代码时,利用就会由原生页面跳转到 Flutter 模块页面。

3,应用带有缓存的 FlutterEngine

每个 FlutterActivity 在默认状况下都会创立本人的 FlutterEngine,并且每个 FlutterEngine 在启动时都须要有肯定的预热工夫。这意味着在原生页面跳转到 Flutter 模块页面之前会肯定的时间延迟。为了尽量减少这个提早,你能够在启动 Flutter 页面之前先预热的 FlutterEngine。即在应用程序中运行过程中找一个正当的工夫实例化一个 FlutterEngine,如在 Application 中进行初始化,如下所示。

public class MyApplication extends Application {
  @Override
  public void onCreate() {super.onCreate();
    flutterEngine = new FlutterEngine(this);

    flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault()
    );

    FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);
  }
}

其中,FlutterEngineCache 的 ID 能够是任意的字符串,应用时请确保传递给任何应用缓存的 FlutterEngine 的 FlutterFragment 或 FlutterActivity 应用的是雷同的 ID。实现下面的自定义 Application 后,咱们还须要在原生 Android 工程的 AndroidManifest.xml 中应用自定义的 Application,如下所示。

<application
        android:name="MyApplication"
        android:theme="@style/AppTheme">
</application>

上面咱们来看一下如何在 FlutterActivity 页面中应用缓存的 FlutterEngine,当初应用 FlutterActivity 跳转到 Flutter 模块时须要应用下面的 ID,如下所示。

myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withCachedEngine("my_engine_id")
        .build(currentActivity)
      );
  }
});

能够发现,在应用 withCachedEngine()工厂办法后,关上 Flutter 模块的延迟时间大大降低了。

4,应用缓存引擎的初始路由
当应用带有 FlutterEngine 配置的 FlutterActivity 或者 FlutterFragment 时,会有初始路由的概念,咱们能够在代码中增加跳转到 Flutter 模块的初始路由。然而,当咱们应用带有缓存的 FlutterEngine 时,FlutterActivity 和 FlutterFragment 并没有提供初始路由的概念。如果开发人员心愿应用带有缓存的 FlutterEngine 时也能自定义初始路由,那么能够在执行 Dart 入口点之前配置他们的缓存 FlutterEngine 以应用自定义初始路由,如下所示。

public class MyApplication extends Application {
  @Override
  public void onCreate() {super.onCreate();
    flutterEngine = new FlutterEngine(this);
    flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
    flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault()
    );
  
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  }
}

带有背景款式的跳转

如果要批改跳转的款式,那么能够在原生 Android 端自定义一个主题款式出现一个半透明的背景。首先关上 res/values/styles.xml 文件,而后增加自定义的主题,如下所示。

<style name="MyTheme" parent="@style/AppTheme">
        <item name="android:windowIsTranslucent">true</item>
    </style>

而后,将 FlutterActivity 的主题改为咱们自定义的主题,如下所示。

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/MyTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

而后,就能够应用通明背景启动 FlutterActivity,如下所示。

// Using a new FlutterEngine.
startActivity(FlutterActivity.withNewEngine()
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);

// Using a cached FlutterEngine.
startActivity(FlutterActivity.withCachedEngine("my_engine_id")
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);

增加 FlutterFragment

在 Android 开发中,除了 Activity 之外,还能够应用 Fragment 来加载页面,Fragment 比 Activity 的粒度更小,有碎片化的意思。如果有碎片化加载的场景,那么能够应用 FlutterFragment。FlutterFragment 容许开发者管制以下操作:

  • 初始化 Flutter 的路由;
  • Dart 的初始页面的飞入款式;
  • 设置不通明和半透明背景;
  • FlutterFragment 是否能够管制 Activity;
  • FlutterEngine 或者带有缓存的 FlutterEngine 是否能应用;

1,将 FlutterFragment 增加到 Activity
应用 FlutterFragment 要做的第一件事就是将其增加到宿主 Activity 中。为了给宿主 Activity 增加一个 FlutterFragment,须要在 Activity 的 onCreate()中实例化并附加一个 FlutterFragment 的实例,这和原生 Android 的 Fragment 应用办法是一样的,代码如下:

public class MyActivity extends FragmentActivity {
   
    private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
    private FlutterFragment flutterFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity_layout);
        FragmentManager fragmentManager = getSupportFragmentManager();
        flutterFragment = (FlutterFragment) fragmentManager
            .findFragmentByTag(TAG_FLUTTER_FRAGMENT);

        if (flutterFragment == null) {flutterFragment = FlutterFragment.createDefault();
            fragmentManager
                .beginTransaction()
                .add(R.id.fragment_container, flutterFragment, TAG_FLUTTER_FRAGMENT)
                .commit();}
    }
}

其中,代码中用到的原生 Fragment 的布局代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
        
</androidx.constraintlayout.widget.ConstraintLayout>

而后,将原生 Android 的启动页面改为咱们的 MyActivity 即可。除此之外,咱们还能够借助 FlutterFragment 来获取原生代码的生命周期,并作出相干的逻辑操作,如下所示。

public class MyActivity extends FragmentActivity {
    @Override
    public void onPostResume() {super.onPostResume();
        flutterFragment.onPostResume();}

    @Override
    protected void onNewIntent(@NonNull Intent intent) {flutterFragment.onNewIntent(intent);
    }

    @Override
    public void onBackPressed() {flutterFragment.onBackPressed();
    }

    @Override
    public void onRequestPermissionsResult(
        int requestCode,
        @NonNull String[] permissions,
        @NonNull int[] grantResults) {
        flutterFragment.onRequestPermissionsResult(
            requestCode,
            permissions,
            grantResults
        );
    }

    @Override
    public void onUserLeaveHint() {flutterFragment.onUserLeaveHint();
    }

    @Override
    public void onTrimMemory(int level) {super.onTrimMemory(level);
        flutterFragment.onTrimMemory(level);
    }
}

不过,下面的示例启动时应用了一个新的 FlutterEngine,因而启动后会须要肯定的初始化工夫,导致利用启动后会有一个空白的 UI,直到 FlutterEngine 初始化胜利后 Flutter 模块的首页渲染实现。对于这种景象,咱们同样能够在提前初始化 FlutterEngine,即在应用程序的 Application 中初始化 FlutterFragment,如下所示。

public class MyApplication extends Application {

    FlutterEngine flutterEngine=null;
    
    @Override
    public void onCreate() {super.onCreate();
        flutterEngine = new FlutterEngine(this);
        flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
        flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault()
        );
        FlutterEngineCache
                .getInstance()
                .put("my_engine_id", flutterEngine);
    }
}

在下面的代码中,通过设置导航通道的初始路由,而后关联的 FlutterEngine 在初始执行 runApp(),在初始执行 runApp()后再扭转导航通道的初始路由属性是没有成果的。而后,咱们批改 MyFlutterFragmentActivity 类的代码,并应用 FlutterFragment.withNewEngine()应用缓存的 FlutterEngine,如下所示。

public class MyFlutterFragmentActivity extends FragmentActivity {

    private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
    private FlutterFragment flutterFragment = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.flutter_fragment_activity);
        FragmentManager fragmentManager = getSupportFragmentManager();
        if (flutterFragment == null) {flutterFragment=FlutterFragment.withNewEngine()
                    .initialRoute("/")
                    .build();

            fragmentManager
                    .beginTransaction()
                    .add(R.id.fragment_container, flutterFragment,TAG_FLUTTER_FRAGMENT)
                    .commit();}
    }
}

管制 FlutterFragment 的渲染模式

FlutterFragment 默认应用 SurfaceView 来渲染它的 Flutter 内容,除此之外,还能够应用 TextureView 来渲染界面,不过 SurfaceView 的性能比 TextureView 好得多。然而,SurfaceView 不能交织在 Android 视图层次结构中应用。此外,在 Android N 之前的 Android 版本中,SurfaceViews 不能动画化,因为它们的布局和渲染不能与其余视图层次结构同步,此时,你须要应用 TextureView 而不是 SurfaceView,应用 TextureView 来渲染 FlutterFragment 的代码如下。

// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .renderMode(FlutterView.RenderMode.texture)
    .build();

// With a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .renderMode(FlutterView.RenderMode.texture)
    .build();

如果要给跳转增加一个转场的通明成果,要启用 FlutterFragment 的通明属性,能够应用上面的配置,如下所示。

// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .transparencyMode(TransparencyMode.transparent)
    .build();

// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .transparencyMode(TransparencyMode.transparent)
    .build();

FlutterFragment 与 Activity

有时候,一些利用应用 Fragment 来作为 Flutter 页面的承载对象时,状态栏、导航栏和屏幕方向依然应用的是 Activity,Fragment 只是作为 Activity 的一部分。在这些应用程序中,用一个 Fragment 是正当的,如下图所示。
在其余应用程序中,Fragment 仅仅作为 UI 的一部分,此时一个 FlutterFragment 可能被用来实现一个抽屉的外部,一个视频播放器,或一个繁多的卡片。在这些状况下,FlutterFragment 不须要全屏线上,因为在同一个屏幕中还有其余 UI 片段,如下图所示。


FlutterFragment 提供了一个概念,用来实现 FlutterFragment 是否可能管制它的宿主 Activity。为了避免一个 FlutterFragment 将它的 Activity 裸露给 Flutter 插件,也为了避免 Flutter 管制 Activity 的零碎 UI,FlutterFragment 提供了一个 shouldAttachEngineToActivity()办法,如下所示。

// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .shouldAttachEngineToActivity(false)
    .build();

// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .shouldAttachEngineToActivity(false)
    .build();

原生 iOS 集成 Flutter

创立 Flutter 模块

为了将 Flutter 集成到原生 iOS 利用里,第一步要创立一个 Flutter module,创立 Flutter module 的命令如下所示。

cd some/path/
flutter create --template module my_flutter

执行完下面的命令后,会在 some/path/my_flutter/ 目录下创立一个 Flutter module 库。在这个目录中,你能够像在其它 Flutter 我的项目中一样,执行 flutter 命令,比方 flutter run –debug 或者 flutter build ios。关上 my_flutter 模块,能够发现,目录构造和一般 的 Flutter 利用的目录别无二至,如下所示。

my_flutter/
├── .ios/
│   ├── Runner.xcworkspace
│   └── Flutter/podhelper.rb
├── lib/
│   └── main.dart
├── test/
└── pubspec.yaml

默认状况下,my_flutter 的 Android 工程和 iOS 工程是暗藏的,咱们能够通过显示暗藏的我的项目来看到 Android 工程和 iOS 工程。

集成到已有 iOS 利用

在原生 iOS 开发中,有两种形式能够将 Flutter 集成到你的既有利用中。
1,应用 CocoaPods 依赖治理和已装置的 Flutter SDK。(举荐)
2,把 Flutter engine、Dart 代码和所有 Flutter plugin 编译成 framework,而后用 Xcode 手动集成到你的利用中,并更新编译设置。

1,应用 CocoaPods 和 Flutter SDK 集成

应用此办法集成 Flutter,须要在本地装置了 Flutter SDK。而后,只须要在 Xcode 中编译利用,就能够主动运行脚本来集成 Dart 代码和 plugin。这个办法容许你应用 Flutter module 中的最新代码疾速迭代开发,而无需在 Xcode 以外执行额定的命令。

当初如果又一个原生 iOS 工程,并且 Flutter module 和这个 iOS 工程是处在相邻目录的,如下所示。

some/path/
├── my_flutter/
│   └── .ios/
│       └── Flutter/
│         └── podhelper.rb
└── MyApp/
    └── Podfile

1,如果你的利用(MyApp)还没有 Podfile,能够依据 CocoaPods 使用指南 来在我的项目中增加 Podfile。而后,在 Podfile 中增加上面代码:

flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

2,每个须要集成 Flutter 的 [Podfile target][],执行 install_all_flutter_pods(flutter_application_path),如下所示。

target 'MyApp' do
  install_all_flutter_pods(flutter_application_path)
end

3,最初,在 MyApp 原生工程下运行 pod install 命令拉取原生工程须要的插件。

pod install

如果没有任何谬误,界面如下图。

在下面的 Podfile 文件中,podhelper.rb 脚本会把你的 plugins,Flutter.framework,和 App.framework 集成到你的原生 iOS 我的项目中。同时,你利用的 Debug 和 Release 编译配置,将会集成绝对应的 Debug 或 Release 的 编译产物。能够减少一个 Profile 编译配置用于在 profile 模式下测试利用。而后,在 Xcode 中关上 MyApp.xcworkspace,能够应用【⌘B】快捷键编译我的项目,并运行我的项目即可。

应用 frameworks 集成

除了下面的办法,你也能够创立一个 frameworks,手动批改既有 Xcode 我的项目,将他们集成进去。然而每当你在 Flutter module 中扭转了代码,都必须运行 flutter build ios-framework 来编译 framework。上面的示例假如你想在 some/path/MyApp/Flutter/ 目录下创立 frameworks。

flutter build ios-framework --output=some/path/MyApp/Flutter/

此时的文件目录如下所示。

some/path/MyApp/
└── Flutter/
    ├── Debug/
    │   ├── Flutter.framework
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework (only if you have plugins with iOS platform code)
    │   └── example_plugin.framework (each plugin is a separate framework)
    ├── Profile/
    │   ├── Flutter.framework
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework
    │   └── example_plugin.framework
    └── Release/
        ├── Flutter.framework
        ├── App.framework
        ├── FlutterPluginRegistrant.framework
        └── example_plugin.framework

而后,应用 Xcode 关上原生 iOS 工程,并将生成的 frameworks 集成到既有 iOS 利用中。例如,你能够在 some/path/MyApp/Flutter/Release/ 目录拖拽 frameworks 到你的利用 target 编译设置的 General > Frameworks, Libraries, and Embedded Content 下,而后在 Embed 下拉列表中抉择“Embed & Sign”。

1,链接到框架

当然,你也能够将框架从 Finder 的 some/path/MyApp/Flutter/Release/ 拖到你的指标我的项目中,而后点击 build settings > Build Phases > Link Binary With Libraries。而后,在 target 的编译设置中的 Framework Search Paths (FRAMEWORK_SEARCH_PATHS) 减少 $(PROJECT_DIR)/Flutter/Release/,如下图所示。

2,内嵌框架
生成的动静 framework 框架必须嵌入你的利用能力在运行时被加载。须要阐明的是插件会帮忙你生成 动态或动静框架。动态框架应该间接链接而不是嵌入,如果你在利用中嵌入了动态框架,你的利用将不能公布到 App Store 并且会失去一个 Found an unexpected Mach-O header code 的 archive 谬误。

你能够从利用框架组中拖拽框架(除了 FlutterPluginRegistrant 以及其余的动态框架)到你的指标‘build settings > Build Phases > Embed Frameworks,而后从下拉菜单中抉择“Embed & Sign”,如下图所示。

3,应用 CocoaPods 在 Xcode 和 Flutter 框架中内嵌利用

除了应用 Flutter.framework 形式外,你还能够退出一个参数 –cocoapods,而后将 Flutter 框架作为一个 CocoaPods 的 podspec 文件散发。这将会生成一个 Flutter.podspec 文件而不再生成 Flutter.framework 引擎文件,命令如下。

flutter build ios-framework --cocoapods --output=some/path/MyApp/Flutter/

执行命令后,Flutter 模块的目录如下图所示。

some/path/MyApp/
└── Flutter/
    ├── Debug/
    │   ├── Flutter.podspec
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework
    │   └── example_plugin.framework (each plugin with iOS platform code is a separate framework)
    ├── Profile/
    │   ├── Flutter.podspec
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework
    │   └── example_plugin.framework
    └── Release/
        ├── Flutter.podspec
        ├── App.framework
        ├── FlutterPluginRegistrant.framework
        └── example_plugin.framework

而后,在 iOS 应用程序应用 CocoaPods 增加 Flutter 以来文件即可,如下所示。

pod 'Flutter', :podspec => 'some/path/MyApp/Flutter/[build mode]/Flutter.podspec'

增加一个 Flutter 页面

FlutterEngine 和 FlutterViewController

为了在原生 iOS 利用中展现 Flutter 页面,须要应用到 FlutterEngine 和 FlutterViewController。其中,FlutterEngine 充当 Dart VM 和 Flutter 运行时环境;FlutterViewController 依附于 FlutterEngine,给 Flutter 传递 UIKit 的输出事件,并展现被 FlutterEngine 渲染的每一帧画面。

1,创立一个 FlutterEngine
创立 FlutterEngine 的机会由您本人决定。作为示例,咱们将在利用启动的 app delegate 中创立一个 FlutterEngine,并作为属性裸露给外界。首先,在在 AppDelegate.h 文件中增加如下代码。

@import UIKit;
@import Flutter;

@interface AppDelegate : FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

而后,在 AppDelegate.m 文件中增加如下代码。

// Used to connect plugins (only if you have plugins with iOS platform code).
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
  // Runs the default Dart entrypoint with a default Flutter route.
  [self.flutterEngine run];
  // Used to connect plugins (only if you have plugins with iOS platform code).
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

须要阐明的是,GeneratedPluginRegistrant 只有在须要反对的插件能力应用。而后运行我的项目,后果报了一个 framework not found FlutterPluginRegistrant 谬误。

ld: warning: directory not found for option '-F/Users/bilibili/Library/Developer/Xcode/DerivedData/iOSFlutterHybird-advitqdrflrsxldrjkqcsvdzxbop/Build/Products/Debug-iphonesimulator/FlutterPluginRegistrant'
ld: framework not found FlutterPluginRegistrant
clang: error: linker command failed with exit code 1 (use -v to see invocation)

对于这个谬误,须要关上我的项目编译配置,批改 Bitcode。默认状况下,Flutter 是不反对 Bitcode 的,Bitcode 是一种 iOS 编译程序的中间代码,在原生 iOS 工程中集成 Flutter 须要禁用 Bitcode,如下图所示。

2,应用 FlutterEngine 展现 FlutterViewController
在上面的例子中,展现了一个一般的 ViewController,当点击页面中的 UIButton 时就会跳转到 FlutterViewController 的,这个 FlutterViewController 应用在 AppDelegate 中创立的 Flutter 引擎 (FlutterEngine)。

@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

运行下面的代码,如果呈现“symbol(s) not found for architecture x86_64”的谬误,能够应用上面的步骤进行解决。应用 Xcode 关上我的项目,而后顺次抉择 TARGETS->Build Phases,而后找到 Compile Sources 并点击“+”,在搜寻框输出 APPDelegate 找到他的.m 文件。


3,应用隐式 FlutterEngine 创立 FlutterViewController
咱们能够让 FlutterViewController 隐式的创立 FlutterEngine,而不必提前初始化一个 FlutterEngine。不过不倡议这样做,因为按需创立 FlutterEngine 的话,在 FlutterViewController 被 present 进去之后,第一帧图像渲染完之前,将会有显著的提早。不过,当 Flutter 页面很少被展现时,能够应用此形式。

为了不应用曾经存在的 FlutterEngine 来展示 FlutterViewController,省略 FlutterEngine 的创立步骤,并且在创立 FlutterViewController 时,去掉 FlutterEngine 的援用。

// Existing code omitted.
// 省略曾经存在的代码
- (void)showFlutter {
  FlutterViewController *flutterViewController =
      [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
  [self presentViewController:flutterViewController animated:YES completion:nil];
}
@end

应用 FlutterAppDelegate

FlutterAppDelegate 具备如下性能:

  • 传递利用的回调,例如 openURL 到 Flutter 的插件 —— local_auth。
  • 传递状态栏点击(这只能在 AppDelegate 中检测)到 Flutter 的点击置顶行为。

咱们举荐利用的 UIApplicationDelegate 继承 FlutterAppDelegate,但不是必须的,如果你的 App Delegate 不能间接继承 FlutterAppDelegate,那么让你的 App Delegate 实现 FlutterAppLifeCycleProvider 协定,来确保 Flutter plugins 接管到必要的回调。否则,依赖这些事件的 plugins 将会有无奈预估的行为。

@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;

@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

而后,在具体实现中,将 App Delegate 委托给 FlutterPluginAppLifeCycleDelegate,如下所示。

@interface AppDelegate ()
@property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
@end

@implementation AppDelegate

- (instancetype)init {if (self = [super init]) {_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
    }
    return self;
}

- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id>*))launchOptions {self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
    [self.flutterEngine runWithEntrypoint:nil];
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
    return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}

// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    if ([viewController isKindOfClass:[FlutterViewController class]]) {return (FlutterViewController*)viewController;
    }
    return nil;
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {[super touchesBegan:touches withEvent:event];

    // Pass status bar taps to key window Flutter rootViewController.
    if (self.rootFlutterViewController != nil) {[self.rootFlutterViewController handleStatusBarTouches:event];
    }
}

- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
    [_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}

- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application
       didReceiveRemoteNotification:userInfo
             fetchCompletionHandler:completionHandler];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {return [_lifeCycleDelegate application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {return [_lifeCycleDelegate application:application handleOpenURL:url];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
  sourceApplication:(NSString*)sourceApplication
         annotation:(id)annotation {
    return [_lifeCycleDelegate application:application
                                   openURL:url
                         sourceApplication:sourceApplication
                                annotation:annotation];
}

- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
  completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
    [_lifeCycleDelegate application:application
       performActionForShortcutItem:shortcutItem
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
  completionHandler:(nonnull void (^)(void))completionHandler {
    [_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {[_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}

- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {[_lifeCycleDelegate addDelegate:delegate];
}
@end

启动选项

下面例子应用默认配置来启动 Flutter,为了定制化你的 Flutter 运行时,咱们能够指定 Dart 入口、库和路由。

1,指定 Dart 入口

在 FlutterEngine 上调用 run()函数,默认将会调用你的 lib/main.dart 文件里的 main() 函数。不过,咱们能够应用入口办法 runWithEntrypoint()来指定一个 Dart 入口,并且,应用 main() 以外的 Dart 入口函数,必须应用上面的注解,避免被 tree-shaken 优化掉,而没有进行编译。如下所示。

  @pragma('vm:entry-point')
  void myOtherEntrypoint() { ...};

2,指定 Dart 库
同时,Flutter 容许开发者在指定 Dart 函数时指定特定文件。例如应用 lib/other_file.dart 文件的 myOtherEntrypoint() 函数取代 lib/main.dart 的 main() 函数,如下所示。

[flutterEngine runWithEntrypoint:@"myOtherEntrypoint" libraryURI:@"other_file.dart"];

3,指定 Dart 路由

当然,当构建 Flutter Engine 时,还能够为你的 Flutter 利用设置一个初始路由,如下所示。

FlutterEngine *flutterEngine =
    [[FlutterEngine alloc] initWithName:@"my flutter engine"];
[[flutterEngine navigationChannel] invokeMethod:@"setInitialRoute"
                                      arguments:@"/onboarding"];
[flutterEngine run];
正文完
 0