Flutter 混合(1)

https://github.com/fengxing1234/Flutter

常用命令

  • 创建module
    flutter create -t module my_flutter

创建项目

使用Android Studio 在 flutter目录中创建5个项目:

  1. Native 项目
    Android 原生项目

  2. flutter_app项目
    标准的Flutter App工程,包含标准的Dart层与Native平台层

  3. flutter_module项目
    Flutter组件工程,仅包含Dart层实现,Native平台层子工程为通过Flutter自动生成的隐藏工程

  4. flutter_package项目
    Flutter纯Dart插件工程,仅包含Dart层的实现,往往定义一些公共Widget

  5. flutter_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


image.png

找到aar后,直接把aar文件放在libs目录下。

运行,完毕,可用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350