Flutter 混合(1)

https://github.com/fengxing1234/Flutter

常用命令

  • 創(chuàng)建module
    flutter create -t module my_flutter

創(chuàng)建項(xiàng)目

使用Android Studio 在 flutter目錄中創(chuàng)建5個(gè)項(xiàng)目:

  1. Native 項(xiàng)目
    Android 原生項(xiàng)目

  2. flutter_app項(xiàng)目
    標(biāo)準(zhǔn)的Flutter App工程,包含標(biāo)準(zhǔn)的Dart層與Native平臺(tái)層

  3. flutter_module項(xiàng)目
    Flutter組件工程,僅包含Dart層實(shí)現(xiàn)啃匿,Native平臺(tái)層子工程為通過Flutter自動(dòng)生成的隱藏工程

  4. flutter_package項(xiàng)目
    Flutter純Dart插件工程,僅包含Dart層的實(shí)現(xiàn)冀值,往往定義一些公共Widget

  5. flutter_plugin項(xiàng)目
    Flutter平臺(tái)插件工程也物,包含Dart層與Native平臺(tái)層的實(shí)現(xiàn)

native集成flutter_module

官網(wǎng)方式集成

https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps

官方建議使用flutter_module形式集成native宫屠,這里做個(gè)實(shí)驗(yàn),看看flutter_app形式能否成功滑蚯。

native項(xiàng)目中:

  • build.gradle
compileOptions {
  sourceCompatibility 1.8
  targetCompatibility 1.8
}

不添加會(huì)報(bào)錯(cuò)的浪蹂。

  • 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);
            }
        });

運(yùn)行,查看結(jié)果告材,完畢坤次。

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);

同步代碼 運(yùn)行。

首先打出aar包

在flutter_module目錄下斥赋,執(zhí)行:
flutter build apk
會(huì)在flutter_module/.android/Flutter/build/outputs/aar/生成aar文件
如果代碼沒有改變缰猴,在此使用命令不會(huì)打出新的aar。

使用
./gradlew assembleDebug
./gradlew assembleRelease
這兩個(gè)命令也可以打出aar包疤剑。

使用aar集成方式滑绒,適用flutter_app和flutter_module開發(fā)闷堡。

  • Native中:

把這個(gè)aar放在libs目錄

implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) 

自動(dòng)依賴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 導(dǎo)報(bào)時(shí)候注意 別倒錯(cuò)了疑故。

現(xiàn)在同步下代碼杠览,然后運(yùn)行,完事纵势。

目前這樣做會(huì)有問題踱阿,如果flutter-module集成了native插件,會(huì)報(bào)錯(cuò)钦铁。

稍等在寫一個(gè)native插件模擬報(bào)錯(cuò)软舌。

flutter-app 集成進(jìn)native

純flutter-app項(xiàng)目,需要做一些修改才能打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 {
                //設(shè)置支持的SO庫(kù)架構(gòu)
                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'
            }
        }
    }

創(chuàng)建`module/AndroidManifest.xml,上git上找去。

現(xiàn)在開始打包

cd flutter_app
cd android
./gradlew assembleDebug

flutter_app/build/app/outputs/aar/目錄在會(huì)生辰aar文件书妻。

現(xiàn)在包有了船响,和上面module集成方式就一樣了。
我這里直接替換aar包躲履,就可以運(yùn)行了见间。
小試了一下, 因?yàn)槲掖虻陌莇ebug工猜,在native進(jìn)入flutter頁(yè)面時(shí)米诉,會(huì)出現(xiàn)黑屏,改成release就好了篷帅。

現(xiàn)在 純flutter 和 module 模式 都可以使用 aar方式集成了
當(dāng)然還有很多坑沒踩史侣,現(xiàn)在就來(lái)踩坑完。
編寫flutter-plugin 讓現(xiàn)在的集成方式報(bào)錯(cuò)魏身。

flutter_plugin

創(chuàng)建插件時(shí)惊橱,自動(dòng)生成一個(gè)native方法,我們還是在創(chuàng)建一個(gè)方法把箭昵,就做個(gè)toast吧税朴。

嗯。。正林。 不寫了 茧跋。費(fèi)事。就用自動(dòng)帶的方法吧卓囚,如果調(diào)用成功瘾杭,會(huì)顯示手機(jī)版本,如果失敗哪亿,Unknown粥烁。

flutter_app依賴flutter-plugin

首先更改開發(fā)模式

def isLib = false
  • 依賴插件
    這里使用本地依賴
    pubspec.yaml文件
flutter_plugin:
    path: ../flutter_plugin/
  • 修改代碼
    按照plugin-示例代碼使用

運(yùn)行 ,可以 蝇棉,完畢讨阻。

修改模式

def isLib = true

打包aar。

  • native
    替換aar
    運(yùn)行篡殷,報(bào)錯(cuò)钝吮。
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)

找不到插件,看來(lái)打包aar板辽,只能打包源碼奇瘦,不能打包依賴。

Flutter 帶有原生代碼的插件劲弦,在插件安裝后耳标,也是會(huì)以本地 Module Project 的形式引入 。

在插件安裝之后邑跪,所有帶原生代碼的插件次坡,都會(huì)以路徑和插件名的key=value 形式 存在 .flutter-plugins 文件中。

而在 android 工程的 settings.gradle 里画畅,會(huì)通過讀取該文件將 .flutter-plugins 文件中的項(xiàng)目一個(gè)個(gè) 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" 腳本的引入了,這個(gè)腳本一般在于 flutterSDK/packages/flutter_tools/gradle/目錄下轴踱,其中最關(guān)鍵的部分同樣是 讀取.flutter-plugins 文件中的項(xiàng)目症脂,然后一個(gè)一個(gè)再implementation 到主工程里完成依賴。

自此所有原生代碼的 Flutter 插件寇僧,都被作為本地 Module Project 的形式引入主工程了 摊腋,最后腳本會(huì)自動(dòng)生成一個(gè) GeneratedPluginRegistrant.java 文件沸版,實(shí)現(xiàn)原生代碼的引用注冊(cè)嘁傀, 而這個(gè)過程對(duì)你完全是無(wú)感的。

解決辦法 一 使用 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"
 ///為庫(kù)的方式才添加本地倉(cāng)庫(kù)依賴视粮,這個(gè)本地倉(cāng)庫(kù)目前是從 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來(lái)做,一人一個(gè)茴肥,誰(shuí)也不偏向坚踩。

flutter_module 依賴和app一樣的。
lib代碼一樣的瓤狐。
修改一下標(biāo)題區(qū)分module還是app瞬铸。

編譯,生成aar础锐,替換aar嗓节,運(yùn)行,報(bào)錯(cuò)皆警。

還是一樣的錯(cuò)誤拦宣,我們來(lái)解決一下。

  • flutter-plugin 項(xiàng)目
    生成aar包

或者直接在flutter-module中找到plugin的aar


image.png

找到aar后信姓,直接把a(bǔ)ar文件放在libs目錄下鸵隧。

運(yùn)行,完畢意推,可用掰派。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市左痢,隨后出現(xiàn)的幾起案子靡羡,更是在濱河造成了極大的恐慌,老刑警劉巖俊性,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件略步,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡定页,警方通過查閱死者的電腦和手機(jī)趟薄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)典徊,“玉大人杭煎,你說(shuō)我怎么就攤上這事∽渎洌” “怎么了羡铲?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)儡毕。 經(jīng)常有香客問我也切,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任雷恃,我火速辦了婚禮疆股,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘倒槐。我一直安慰自己旬痹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布讨越。 她就那樣靜靜地躺著唱凯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谎痢。 梳的紋絲不亂的頭發(fā)上磕昼,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音节猿,去河邊找鬼票从。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滨嘱,可吹牛的內(nèi)容都是我干的峰鄙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼太雨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吟榴!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起囊扳,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吩翻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后锥咸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狭瞎,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年搏予,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了熊锭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雪侥,死狀恐怖碗殷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情速缨,我是刑警寧澤锌妻,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站鸟廓,受9級(jí)特大地震影響从祝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜引谜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一牍陌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧员咽,春花似錦毒涧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至滑频,卻和暖如春捡偏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峡迷。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工银伟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绘搞。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓彤避,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親夯辖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琉预,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355