https://github.com/fengxing1234/Flutter
常用命令
- 创建module
flutter create -t module my_flutter
创建项目
使用Android Studio 在 flutter目录中创建5个项目:
Native 项目
Android 原生项目flutter_app项目
标准的Flutter App工程,包含标准的Dart层与Native平台层flutter_module项目
Flutter组件工程,仅包含Dart层实现,Native平台层子工程为通过Flutter自动生成的隐藏工程flutter_package项目
Flutter纯Dart插件工程,仅包含Dart层的实现,往往定义一些公共Widgetflutter_plugin项目
Flutter平台插件工程,包含Dart层与Native平台层的实现
native集成flutter_module
官网方式集成
https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps
官方建议使用flutter_module形式集成native,这里做个实验,看看flutter_app形式能否成功。
native项目中:
-
build.gradle
中
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
不添加会报错的。
-
dependencies
中
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
implementation project(':flutter')
}
-
setting gradle
中
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
))
同步代码
mainActivity 中添加代码
setContentView(R.layout.activity_main);
container = (FrameLayout) findViewById(R.id.flutter_container);
findViewById(R.id.btn_flutter).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
View flutterView = Flutter.createView(
MainActivity.this,
getLifecycle(),
"route1"
);
// FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
// layout.leftMargin = 100;
// layout.topMargin = 200;
// addContentView(flutterView, layout);
container.addView(flutterView);
}
});
运行,查看结果,完毕。
aar 方式集成
flutter_module集成
首先我们注释掉一些代码
- setting 中
include ':app'
//setBinding(new Binding([gradle: this])) // new
//evaluate(new File( // new
// settingsDir.parentFile, // new
// 'flutter_module/.android/include_flutter.groovy' // new
//))
- dependencies 中
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
// implementation project(':flutter')
}
- mainActivity
// View flutterView = Flutter.createView(
// MainActivity.this,
// getLifecycle(),
// "route1"
// );
//// FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
//// layout.leftMargin = 100;
//// layout.topMargin = 200;
//// addContentView(flutterView, layout);
// container.addView(flutterView);
同步代码 运行。
首先打出aar包
在flutter_module目录下,执行:
flutter build apk
会在flutter_module/.android/Flutter/build/outputs/aar/
生成aar文件
如果代码没有改变,在此使用命令不会打出新的aar。
使用
./gradlew assembleDebug
./gradlew assembleRelease
这两个命令也可以打出aar包。
使用aar集成方式,适用flutter_app和flutter_module开发。
- Native中:
把这个aar放在libs目录
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
自动依赖aar
- java代码
在开启Flutter前
FlutterMain.startInitialization(this);
可以放在Application中,这里我就放在了MainActivity 中了
- MainActivity
package com.picc.anative;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.FrameLayout;
import io.flutter.view.FlutterMain;
public class MainActivity extends AppCompatActivity {
private FrameLayout container;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FlutterMain.startInitialization(this);
container = (FrameLayout) findViewById(R.id.flutter_container);
findViewById(R.id.btn_flutter).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, MyFlutterActivity.class));
// View flutterView = Flutter.createView(
// MainActivity.this,
// getLifecycle(),
// "route1"
// );
//// FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
//// layout.leftMargin = 100;
//// layout.topMargin = 200;
//// addContentView(flutterView, layout);
// container.addView(flutterView);
}
});
}
}
- MyFlutterActivity
package com.picc.anative;
import android.os.Bundle;
import android.support.annotation.Nullable;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MyFlutterActivity extends FlutterActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
这里注意:FlutterActivity 导报时候注意 别倒错了。
现在同步下代码,然后运行,完事。
目前这样做会有问题,如果flutter-module集成了native插件,会报错。
稍等在写一个native插件模拟报错。
flutter-app 集成进native
纯flutter-app项目,需要做一些修改才能打aar包。
-
flutter-app/android/app/build.gradle
修改如下
def isLib = true
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
if(isLib) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
if(!isLib) {
applicationId "com.zhyen.flutter_app"
}
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
if(isLib) {
ndk {
//设置支持的SO库架构
abiFilters 'armeabi', 'armeabi-v7a', 'x86'
}
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
sourceSets {
main {
if (isLib) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
如果想要打包aar文件 就使用apply plugin: 'com.android.library'
打包aar,application是打apk包的。
打包aar,不需要 applicationId
。
为了避免AndroidManifest文件冲突,在不同模式下,选择不同文件。
sourceSets {
main {
if (isLib) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
创建`module/AndroidManifest.xml,上git上找去。
现在开始打包
cd flutter_app
cd android
./gradlew assembleDebug
在flutter_app/build/app/outputs/aar/
目录在会生辰aar文件。
现在包有了,和上面module集成方式就一样了。
我这里直接替换aar包,就可以运行了。
小试了一下, 因为我打的包是debug,在native进入flutter页面时,会出现黑屏,改成release就好了。
现在 纯flutter 和 module 模式 都可以使用 aar方式集成了
当然还有很多坑没踩,现在就来踩坑完。
编写flutter-plugin 让现在的集成方式报错。
flutter_plugin
创建插件时,自动生成一个native方法,我们还是在创建一个方法把,就做个toast吧。
嗯。。。 不写了 。费事。就用自动带的方法吧,如果调用成功,会显示手机版本,如果失败,Unknown。
flutter_app依赖flutter-plugin
首先更改开发模式
def isLib = false
- 依赖插件
这里使用本地依赖
pubspec.yaml
文件
flutter_plugin:
path: ../flutter_plugin/
- 修改代码
按照plugin-示例代码使用
运行 ,可以 ,完毕。
修改模式
def isLib = true
打包aar。
- native
替换aar
运行,报错。
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/zhyen/flutter_plugin/FlutterPlugin;
at io.flutter.plugins.GeneratedPluginRegistrant.registerWith(GeneratedPluginRegistrant.java:14)
at com.picc.anative.MyFlutterActivity.onCreate(MyFlutterActivity.java:14)
at android.app.Activity.performCreate(Activity.java:7436)
at android.app.Activity.performCreate(Activity.java:7426)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1286)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3279)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3484)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:86)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2123)
at android.os.Handler.dispatchMessage(Handler.java:109)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:7470)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:958)
找不到插件,看来打包aar,只能打包源码,不能打包依赖。
Flutter 带有原生代码的插件,在插件安装后,也是会以本地 Module Project 的形式引入 。
在插件安装之后,所有带原生代码的插件,都会以路径和插件名的key=value 形式 存在 .flutter-plugins 文件中。
而在 android 工程的 settings.gradle 里,会通过读取该文件将 .flutter-plugins 文件中的项目一个个 include 到主工程里。
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
之后就是主工程里的 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
脚本的引入了,这个脚本一般在于 flutterSDK/packages/flutter_tools/gradle/
目录下,其中最关键的部分同样是 读取.flutter-plugins
文件中的项目,然后一个一个再implementation
到主工程里完成依赖。
自此所有原生代码的 Flutter 插件,都被作为本地 Module Project 的形式引入主工程了 ,最后脚本会自动生成一个 GeneratedPluginRegistrant.java 文件,实现原生代码的引用注册, 而这个过程对你完全是无感的。
解决办法 一 使用 fat-aar
-修改flutter-app端代码
-
android/gradle
文件
添加fat-aar
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.kezong:fat-aar:1.1.10'
}
-
android/app/gradle
文件
if(isLib) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
if(isLib) {
apply plugin: 'com.kezong.fat-aar'
}
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
///为库的方式才添加本地仓库依赖,这个本地仓库目前是从 include 那里读取的。
if(isLib) {
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, _ ->
println name
embed project(path: ":$name", configuration: 'default')
}
}
打aar包。可以使用了,完毕。
方案二
方案二我们使用 flutter_module来做,一人一个,谁也不偏向。
flutter_module 依赖和app一样的。
lib代码一样的。
修改一下标题区分module还是app。
编译,生成aar,替换aar,运行,报错。
还是一样的错误,我们来解决一下。
- flutter-plugin 项目
生成aar包
或者直接在flutter-module中找到plugin的aar
找到aar后,直接把aar文件放在libs目录下。
运行,完毕,可用。