flutter混合开发(Android视角)

flutter 混合开发(Android 视角)

注: 已默认集成开发环境

集成方式 (开发方式采取 Android Studio)

flutter 的创建方式

打开 AS -> File -> New ->New Flutter Project
可以看到有 4 种创建方式
分别对应为:

flutter创建方式 {
  1.Flutter Application
  2.Flutter Module
  3.Flutter Plugin
  4.Flutter Package
}

本文仅讨论:
Flutter Application(产物集成)以及 Flutter Module(官方集成)的方式

项目结构

  1. Flutter Application

创建完整的 Flutter 项目,包含:

Flutter Application包含{
  1.android项目
  2.ios项目
  3.lib文件夹(flutter项目)
  ...
  pubspect.yaml
}

  • android/ios 文件夹

作为一个完整的项目,则需要直接可以运行于 android/ios 设备上
android/ios 项目,则作为不同设备上运行的启动项

android 中
app.build.gradle 与单独 android 项目不同,下面集成流程会具体介绍

  • lib 文件夹

真正存放 dart 文件的位置

  • pubspect.yaml

flutter 的配置项,包含版本号,sdk,依赖,资源管理等

  1. Flutter Module

与 Flutter Application 结构类似,区别在于 android/ios 文件夹是以.android/.ios 文件夹的形式存在的

.android 文件夹下
(1) 多了 include_flutter.groovy 文件 (用于 flutter 打包)
(2) setting 文件夹下多了(注: Binding 会提示导包,不要导入任何包,保留提示即可)
rootProject.name = 'android_generated'
setBinding(new Binding([gradle: this]))
evaluate(new File('include_flutter.groovy'))

官方集成流程

1.进入单独android项目所在文件夹
执行:flutter create -t module flutter_module (flutter_module可自行换名字)
即:此module与android项目是同级的

2.android项目中:
在打包时,会采用单独android项目中的setting.gradle,而并不会使用.android中的setting.gradle来完成有引入模块的配置,因此需要我们手动进行更改
(1) setting.gradle配置:
rootProject.name = 'android_generated'
setBinding(new Binding([gradle: this]))
//引入多出的include_flutter.groovy文件
evaluate(new File('include_flutter.groovy'))
与.android的setting.gradle相同

(2) app.build.gradle
android{}节点下,配置
//jdk1.8支持
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    //添加依赖
    implementation project(':flutter')
}

集成之后,native 如何启动 flutter,因与 aar 集成之后的启动相同,所以在下面会详细讲解

aar 集成流程

aar集成,就是我们常说的产物集成,与官方集成相比,集成过程会稍微复杂,优点在于,并不需要使用者安装flutter环境,只需可以调用即可,侵入性较低

注:官方已经对此种方式进行了优化,如果在此刻集成的话,并不需要最初flutter_shared文件的copy

本文采取的方式为:aar文件放置于一个android的module下,之后,将android的module作为依赖整体引入(用于模块化或组件化开发)

(1) 创建单独的Flutter Application项目(此项目用于aar打包)
(2) 找到android文件夹,引入config.gradle与application.build.gradle同级(组件化方式)
application.config.gradle
ext {
    //true 以lib方式运行,用于打aar包
    //false 以单独业务开发
    isModule = true
    
    //android版本号自行决定
    android = [
            compileSdkVersion: 27,
            minSdkVersion    : 16,
            targetSdkVersion : 27,
    ]
}
设置isModule的boolean值,通过更改此值,用于决定我们这一回是进行单独运行的开发,还是打包aar

(3)application.build.gradle引入这个配置文件
apply from: "config.gradle"

(4)app.build.gradle
//这一部分都是默认存在的,不需要进行更改,我们要做的,只是将在module的情况下,打包采用library的方式
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

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.")
}

//重点在于这里
//module的情况下,采用library的形式打包,直接运行的情况下,采取application的方式进行打包
if (rootProject.ext.isModule) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
    //将android的版本号引入
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        //module的情况下,则我们不需要applicaionId了
        if (!rootProject.ext.isModule) {
            applicationId "com.flutter"
        }
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode flutterVersionCode as int
        versionName flutterVersionName
    }
    
    //配置签名文件,现在我采用的是debug下的签名
    buildTypes {
        release {
            signingConfig signingConfigs.debug
        }
    }
    
    //单独运行与module运行采用不同的manifest,单独运行时manifest中需要包含applicaion
    //下面会贴出两种manifest的配置
    sourceSets {
        main {
            if (rootProject.ext.isModule) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    exclude '*module'
                }
            }
        }
    }

    lintOptions {
        disable 'InvalidPackage'
        checkReleaseBuilds false
        abortOnError false
    }
}

flutter {
    source '../..'
}

(5) manifest的配置
单独运行时:
main.manifest
无需更改
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.hyaar_flutter">

    <uses-permission android:name="android.permission.INTERNET" />

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="flutter_demo2"
        android:icon="@mipmap/ic_launcher"
        tools:ignore="GoogleAppIndexingWarning">
        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:windowSoftInputMode="adjustResize"
            tools:targetApi="honeycomb">
            <!-- This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme). -->
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

作为module运行时:
main下方创建module文件夹,和java文件夹同级,创建manifest
main.module.manifest
去掉与application相关的配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hyaar_flutter">

    <!--作为module时的manifest-->
    <uses-permission android:name="android.permission.INTERNET" />

</manifest>

(6)当isModule切换为true的时候
Terminal下执行flutter build apk则会打包成aar文件了
注: 如果引用了其他的插件,从flutter项目的文件夹直接进入,找到build文件夹,需要找到所有生成的aar

Native开启flutter界面

注: 这里说的都是在android项目中做的操作

前提:已经按照官方模式集成或已经引入aar

开启flutter界面的方式有三种

开启方式{
  1.activity
  2.view
  3.fragment
}
  • activity
(1) 进行flutter初始化 建议放在applicaion的onCreate()中
FlutterMain.startInitialization(application());

(2)public class MainActivity extends FlutterActivity
继承FlutterActivity,则这个MainActivity就是flutter界面的承载容器
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  //引入插件注册,否则接收不到通过插件发送的信息
  GeneratedPluginRegistrant.registerWith(this);
}

(3)开启activity的方式,这里我已经写好一个Util,可以直接使用
public class FlutterUtils {
    /**
     * 默认启动flutter键  Do not edit.
     */
    private static final String ROUTE_PAGE = "route";

    /**
     * 获取进入flutter界面的intent (继承FlutterActivity的activity,即可接收此Extra)
     *
     * @param packageContext A Context of the application package implementing
     *                       this class(启动activity)
     * @param cls            The component class that is to be used for the intent.
     *                       (需继承FlutterActivity)
     * @param route          route路径(之后可能会做uri传参)
     * @return intent 启动intent
     */
    public static Intent getIntent(Context packageContext, Class<?> cls, String route) {
        if (route == null || "".equals(route)) {
            route = "/";
        }
        Intent intent = new Intent(packageContext, cls);
        intent.setAction(Intent.ACTION_RUN);
        intent.putExtra(ROUTE_PAGE, route);
        return intent;
    }
}
使用方式:
注:route1,则是对应的flutter中的某一个Widget
Intent intent = FlutterUtils.getIntent(OtherActivity.this,MainActivity.class,"route1");
startActivity(intent);
  • view/fragment

官方已经帮我们写好了启动方式,如果通过官方引入的方式,则可以直接使用,打包aar的方式,需要手动copy
分别是Flutter和FlutterFragment

//将flutterView当做一个view的添加即可
FrameLayout container = findViewById(R.id.container);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(-1, -1);
mFlutterView = Flutter.createView(this, getLifecycle(), "route1");
container.addView(mFlutterView, lp);
  • 对应的flutter

找到route1这个widget

//找到main函数
void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  return RouteHelper.handlerNativeRoute(route);
}

class RouteHelper {
  static handlerNativeRoute(String route) {
    switch (route) {
      case 'route1':
        return Rount1();
      case 'route2':
        return Rount2();
      default:
        //这里建议给出一个主程序入口,aar方式下,当我们不以module开启,
        //则从这里进入
        return DefaultPage();
    }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容