Flutter打包aar而且集成现有Android项目

1、前言

Flutter如今已经很火了,可是若是咱们要想使用Flutter开发,在新的Flutter项目上集成之前的全部代码确定是不现实的,同时又不想将Flutter直接侵入到咱们的项目结构中去,因而最优解就是将开发的Flutter项目单独编译成aar,而后以组件的形式被主工程依赖。java

这样作的好处是显而易见的:对Flutter进行探索开发的同窗能够在本身的Flutter工程内编写dart代码,独立运行调试,完成的时候打包成aar集成到主工程中供写native代码的同窗接入,两方协同工做,不会产生冲突。android

2、打包apk并分析

首先建立flutter工程,会获得以下目录git

在这里插入图片描述

在命令行输入打包命令 flutter build apkgithub

会编译生成apk文件 位于 build/app/outputs/apk/release/app-release.apkweb

打开apk能够看到,里面目录为shell

在这里插入图片描述

能够看到多出来不少东西,这些产物都来自于flutter的构建代码,bash

android/app/build.gradle中依赖了了flutter.gradle架构

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

经过阅读flutter.gradle的构建源码,能够发如今构建apk的过程当中,会将须要的文件构建到apk中。app

注意:flutter新版本sdk打出来的apk已经不包括flutter_assets文件了svg

一、assets文件夹

assets文件夹下面有flutter_assets文件夹、flutter_shared文件夹、isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr文件

  • flutter_assets里是flutter工程产生的assets文件
  • flutter_shared里是封装在flutter.jar里面的处理字符编码的ICU库 (新版本已经不须要了)
  • isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr为特定平台的数据和指令

debug模式下编译出来的apk没有vm/isolate_snapshot_instr文件,而是kernel_blob.bin,这是由于编译模式不一样形成的。

二、lib文件夹

lib文件夹下是特定平台(arm或者x86)的so文件,flutter在Android平台下会默认生成arm-v7架构的的so库,debug模式下会同时生成x86的so文件。

固然有的项目可能配置了

ndk {
      abiFilters 'armeabi'
 }

因此为了解决so对齐问题,你须要在你的flutter项目中手动添加armeabi的so文件,这样的话打包出来的aar就包含了armeabi的so文件,这个armeabi的so文件能够拷贝armeabi-v7a下面的,通常状况下他们两个是没什么区别的,这里拷贝的话亲测可用。
在app目录下建立libs/armeabi,而后将libflutter.so拷贝到armeabi的目录下,而后在gradle中配置

android{
	sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

可是因为flutter sdk版本升级比较快,每一个版本打出的so可能稍有不一样,因此只要升级sdk可能就须要拷贝so,比较麻烦,那么我经过监听打包aar的任务来进行自动拷贝,在gradle文件中配置如下代码

//如下任务为了拷贝so  由于Flutter默认只生成v7的so
task copyFlutterSo(dependsOn: 'transformNativeLibsWithSyncJniLibsForRelease', type: Copy) {
    //${buildDir} =  /Users/xueshanshan/project/flutter/flutter_debug_library/build/app
    def dir = "${buildDir}/intermediates/library_and_local_jars_jni/release"
    from "${dir}/armeabi-v7a/libflutter.so"
    into "${dir}/armeabi/"
}

3、打包aar

上面经过编译命令获得了apk,若是要想打包aar,理论上只须要把
app/build.gradle中的apply plugin: 'com.android.application
改成apply plugin: 'com.android.library,
同时注释掉applicationId "com.flutterappfirst",
而且将清单文件修改成:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.flutterappfirst"/>

而后执行如下命令,就能获得app-release.aar文件

#!/bin/bash

flutter clean
cd android
./gradlew assembleRelease

可是,咱们将aar解压而后看其目录结构,以下图:

在这里插入图片描述

注意:如下内容在flutter新版本sdk上不须要拷贝,由于此文件已经不须要了

咱们发如今aar文件夹下的assets里面缺乏了flutter_shared文件夹,icudtl.dat文件正是在该文件夹里面,也就是说flutter.gradle在编译流程中并无将icudtl.dat文件打进aar包里面,那么咱们的解决办法是将该文件夹手动拷贝到flutter工程中,以下图

在这里插入图片描述

而后再次执行上述打包aar的命令,获得app-release.aar文件,正确的结构以下图:

在这里插入图片描述

能够看到flutter_shared文件夹已经放到assets目录里面了。

4、集成到现有Android项目

上述打包成功的aar就能够做为普通的aar集成到Android项目中了

一、拷贝aar到现有android项目中,拷贝到libs目录下

二、配置build.gradle

repositories {
    flatDir { dirs 'libs' }
}

dependencies {
	compile(name: 'app-release', ext: 'aar')
}

固然也能够将aar发布到jcenter之类的远程仓库中,而后在build.gradle中进行远程导入就OK

三、展现Flutter界面

其实就是模仿新建的flutter项目,在application中初始化

FlutterMain.startInitialization(this);

而后新建一个Activity继承FlutterActivity

public class FlutterMainActivity extends FlutterActivity {

//跳转该页面的时候能够传要跳转的页面,参数名固定为route
    private static final String ROUTE_PAGE = "route";

    public static Intent makeIntent(Context context, String routePage) {
        if (routePage == null || routePage.equals("")) {
            routePage = "/";
        }
        Intent intent = new Intent(context, FlutterMainActivity.class);
        intent.setAction(Intent.ACTION_RUN);
        intent.putExtra(ROUTE_PAGE, routePage);
        return intent;
    }
 
    public void onCreate(@Nullable Bundle savedInstanceState) {
      	//或者在这里初始化Flutter
      	//FlutterMain.startInitialization(this);
    	super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
    }
}

而后就能展现Flutter void main() => runApp(new MyApp());这句话对应的界面了。
能够看到咱们还有传参,那么这个参数就能够在flutter里面接收到,咱们能够根据这个参数的值来跳转对应的界面。

void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return SomeWidget(...);
    case 'route2':
      return SomeOtherWidget(...);
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}

5、第三方依赖问题

假如你的flutter工程依赖了三方的flutter plugin,那么打包aar无法把plugin内容也打进去。
这个时候就可使用fat-aar-android来实现将第三方库的android代码打进aar

还有一个问题,若是你这个aar要上传远程,那么这个库并不会帮你修改pom文件,删除掉相关依赖,因此你须要本身编写任务去删除依赖

当你依赖了flutter第三方库时,会在根目录的.flutter-plugins里面有配置,好比:
shared_preferences=/Users/xueshanshan/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.2/

而后在android目录下的settings.gradle里面有配置,会依赖.flutter-plugins里面配置的库做为module

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
}

因此打包aar时,咱们须要作的遍历全部的依赖,而后remove掉,而后替换成compileOnly和flat-aar库提供的embed