Flutter混编方案
- Flutter 工程作为原生工程共用的子模块,维持原有的原生工程管理方式不变。这种模式,就是三端分离模式。
- 除了实现轻量级接入,还可以快速实现 Flutter 功能的“热插拔”,降低原生工程的改造成本。而 Flutter 工程独自管理,无需打开原生工程,可直接进行 Dart 代码和原生代码的开发调试。
-
三端工程分离模式的关键是抽离 Flutter 工程,将不同平台的构建产物依照标准组件化的形式进行管理,即 Android 使用 aar、iOS 使用 pod。换句话说,将 Flutter 模块打包成 aar 和 pod,这样原生工程就可以像引用其他第三方原生组件库那样快速接入 Flutter 了。
平台通道架构设计
消息使用platform channels(平台通道)在客户端(UI)和宿主(平台)之间传递;
调用过程大致如下:
1.客户端(Flutter端)发送与方法调用相对应的消息;
2.平台端(iOS、Android端)接收方法,并返回结果;
- iOS端通过FlutterMethodChannel做出响应;
- Android端通过MethodChannel做出响应;
集成Flutter
现有的Flutter是一个Application项目,现在想把它嵌入原生应用中。首先需要将现有Flutter工程转成Module工程,在flutter 工程中的pubspec.yaml
文件中flutter节点下添加如下配置,然后pug get
跑一下
module:
androidX: true
androidPackage: com.ganyuan.flutter_module
iosBundleIdentifier: com.ganyuan.flutterModule
.android
和 .ios
文件夹,这两个文件是flutter applicaiton没有的,在 mac上是两个 隐藏文件夹,window 是可见的Android中集成Flutter module
Flutter 可以作为 Gradle 子项目源码或者 AAR 嵌入到现有的 Android 应用程序中。
请注意
你目前现有的 Android 项目可能支持 mips
或 x86
之类的架构,然而,Flutter 当前仅支持 为 x86_64
,armeabi-v7a
和 arm64-v8a
构建预编(AOT)的库。
可以考虑使用 abiFilters
这个 Android Gradle 插件 API 来指定 APK 中支持的架构,从而避免 libflutter.so
无法生成而导致应用运行时崩溃,具体操作如下:
android {
//...
defaultConfig {
ndk {
// Filter for architectures supported by Flutter.
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
}
}
}
Flutter 引擎支持 x86
和 x86_64
的版本,在模拟器以 debug 即时编译 (JIT) 模式运行时, Flutter 模块仍可以正常运行。
首先在本地安装Flutter的SDK及环境配置,FlutterSDK安装教程
安装完成后,将我们的Android工程和Flutter工程(默认已创建好)放到同一目录下,如下图所示
该方式可以使你的 Android 项目和 Flutter 项目能够同步一键式构建。当你需要同时在这两个项目中进行快速迭代时,这种方案非常方便。
- 将 Flutter 模块作为子项目添加到宿主应用的
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
'flutter_module/.android/include_flutter.groovy' // new
)) // new
binding 和 evaluation 脚本可以使 Flutter 模块将其自身(如 :flutter
)和该模块使用的所有 Flutter 插件(如 :package_info
,:video_player
等)都包含在 settings.gradle
的评估的上下文中。
- 在你的应用中引入对 Flutter 模块的依赖:
dependencies {
implementation project(':flutter')
}
- 确保Flutter工程和Android工程编译的SDK版本保持一致,如下图在app级
build.gradle
文件中修改
- (可选)尽量保证两个工程的gradle版本和jdk版本和引用路径保持一致,不然可能编译会报错
/File/News Projects Setup/Default Project Structure
(根据Android Studio版本不同可能路径不同)里设置 - (可选)添加
kotlin
依赖支持,尽量保证Android和Flutter工程支持的kotlin
版本一致
buildscript {
ext.kotlin_version = '1.8.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin-android'
dependencies {
...
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
- 执行
gradle sync
,这一步建议打开科学上网
如果报Failed to apply plugin class ‘FlutterPlugin’
的错误,则需要把settings.gradle
中的(RepositoriesMode.FAIL_ON_PROJECT_REPOS
)改为(RepositoriesMode.PREFER_PROJECT
),如下图:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
repositories {
google()
mavenCentral()
}
}
如果没法科学上网的同学可以在settings.gradle
中添加如下代码
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
repositories {
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
}
}
同时在project的build.gradle
中添加
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
}
}
再次执行gradle sync
,build
成功后,你的应用程序已将 Flutter 模块添加为依赖项。
7.在 Android 应用中添加 Flutter 页面
这里我们以添加一个Flutter Fragment
为例
首先我们在需要展示Flutter页面的地方事先预热一个Flutter Engine
,以提高Flutter页面初始加载速度
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(this);
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
然后在FlutterFragment
中使用预热的 FlutterEngine
,可以使用工厂方法 withCachedEngine()
实例化 FlutterFragment
。
FlutterFragment.withCachedEngine("my_engine_id").build();
FlutterFragment
内部可访问FlutterEngineCache
,并且可以根据传递给 withCachedEngine()
的 ID 获取预热的 FlutterEngine
。
MainActivity.java代码:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(this);
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
BottomNavigationView navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
}
}
HomeFragment.java代码:
public class HomeFragment extends Fragment {
private FragmentHomeBinding binding;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Fragment fragment = FlutterFragment.withCachedEngine("my_engine_id").build();
getParentFragmentManager().beginTransaction().add(this.getId(), fragment).commit();
}
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
HomeViewModel homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);
binding = FragmentHomeBinding.inflate(inflater, container, false);
View root = binding.getRoot();
// final TextView textView = binding.textHome;
// homeViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
demo运行效果: