Flutter 1.12后 上传aar至maven私服

前言

本篇写一个将 flutter 打包为 aar 置入已有项目的方案

与前篇不同的是: 本篇使用新版本的 flutter 环境, 使用 build aar 命令构建 aar,并上传至 maven 私服

开发环境

$ flutter doctor -v
[] Flutter (Channel stable, v1.12.13+hotfix.7, on Mac OS X 10.15 19A602, locale zh-Hans-CN)
    • Flutter version 1.12.13+hotfix.7 at /Users/caijinglong/Library/Flutter/flutter_dev
    • Framework revision 9f5ff2306b (9 天前), 2020-01-26 22:38:26 -0800
    • Engine revision a67792536c
    • Dart version 2.7.0

准备步骤

创建宿主工程

这个是模拟你本来的项目

作为原生开发者自行使用 Android Studio 创建即可

Flutter 1.12后 上传aar至maven私服_第1张图片

一个标准的 android 项目, 除了 gradle 版本使用 6.1.1, 和 maven 仓库使用阿里云镜像, 其他并没有什么修改

创建 flutter 项目

这个是模拟你的 flutter 项目

使用 module type

$ flutter create -t module flutter_module_example

Flutter 1.12后 上传aar至maven私服_第2张图片

如果你不是这种类型创建的项目, 理论上也可以用 flutter build aar, 但是不保证完全一样

本机启动一个 maven 私服

这里使用 docker-compose 启动, 根据你自己的情况来安装, 如果你有公司的 maven 私服或你不喜欢用 docker, 则根据你自己的实际情况跳过这步或使用别的 maven 私服

Flutter 1.12后 上传aar至maven私服_第3张图片

version: "3"

services:
  nexus:
    image: sonatype/nexus3
    ports:
      - 9000:8081
    volumes:
      - /Volumes/Samsung-T5/docker/nexus/data:/nexus-data

语法是 宿主机:docker 镜像

端口是将 docker 的 8081 映射到本机的 9000 端口

volumes 是映射 nexus 的数据到本机的文件夹内

$ docker-compose up 启动 docker

Flutter 1.12后 上传aar至maven私服_第4张图片

我这里不是初次安装, 所以没有, 你如果是初次安装可以从 data 文件夹内找到 admin.password 查看初始密码

一般初次登陆会让你设置一个, 记住它后面要用到

Flutter 1.12后 上传aar至maven私服_第5张图片

这个就是私服的 maven url 地址

flutter 项目

修改项目

本篇修改一下 flutter 项目, 引入一个path_provider

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  path_provider: any

实际开发时别用这个 any 版本号, 我只是懒得看最新版是多少而已

构建 aar

$ flutter build aar 来打包

Flutter 1.12后 上传aar至maven私服_第6张图片

这里 build 完, console 日志有提示你怎么配置你的 host 项目, 如果你是自己开发, 直接用本地的 repo 目录作为仓库就可以了, 将配置复制到项目里即可

repositories 闭包配置可以写在在 host 项目, 也可以配置在 app module 中, 你如果不明白项目和 module 的差别, 可以请教你们安卓原生同事, 也可以自行百度

其他的配置请配置到 app module 里


// 添加 maven 仓库 url
repositories {
    maven {
        url '/Volumes/Samsung-T5/code/flutter/exists_blog/flutter_module_example/build/host/outputs/repo' // 这个是我本地的, 你根据你自己的情况肯定不一样, 不要直接复制粘贴我的, 粘贴命令行里的
    }
    maven {
        url 'http://download.flutter.io' // 这个是flutter的maven仓库地址
    }
}

// 添加依赖
dependencies {
    debugImplementation 'com.example.flutter_module_example:flutter_debug:1.0'
    profileImplementation 'com.example.flutter_module_example:flutter_profile:1.0'
    releaseImplementation 'com.example.flutter_module_example:flutter_release:1.0'
}

// 因为android项目默认是没有profile类型的, 这里需要你自己加一个, 然后配置参考debug
android {
    buildTypes {
        profile {
            initWith debug
        }
    }
}

当然, 实际上不太可能是自己开发, 即便是自己开发, 也不可能家里和公司连目录结构都一模一样, 然后这里就是 maven 私服的作用了, 让依赖相同, 以便于合作开发

分析下构建产物

这里我们注意到, 我们依赖只有一个 flutter_module_example, 没有 path_provider, 这个就是 maven 帮我们处理的了

Flutter 1.12后 上传aar至maven私服_第7张图片

看目录结构, path_provider 是有的, 还分为 release profile debug

Flutter 1.12后 上传aar至maven私服_第8张图片

这里是 flutter 项目对应的 pom 文件, 依赖了三部分代码, 这样 maven 就会自动帮你把这三部分代码都下载并依赖

上传依赖

使用界面上传

image

Flutter 1.12后 上传aar至maven私服_第9张图片

这里分别上传就看见了

Flutter 1.12后 上传aar至maven私服_第10张图片

因为是演示, 我只上传 release 的, 然后在 host 项目里引用

项目里, 根目录的build.gradle

repositories{
    maven{
        url 'http://localhost:9000/repository/maven-releases/' // 你自己的maven私服地址
    }
    maven{
        url 'http://download.flutter.io'
    }
}

app 的build.gradle

dependencies{
    implementation 'com.example.flutter_module_example:flutter_release:1.0'
}

这样就可以了

当然肯定还有问题, 但是基本的思路已经通了, 接着就是怎么自动化的问题了

自动化的探索

命令行上传

自动化的话, 肯定需要有工具或者命令行上传

我这里选择的是命令行的方案

但在这之前, 我需要先把旧的包删除掉, 以便于保证我命令行的包可以上传成功

image

在 browser 视图中统统删掉

接着进入刚刚的 repo 文件夹

$ cd flutter_module_example/build/host/outputs/repo/com/example/flutter_module_example/flutter_release/1.0

根据maven 官网说明
可以使用 mvn deploy:deploy-file 命令来上传

$ mvn deploy:deploy-file -DpomFile=flutter_release-1.0.pom -Dfile=flutter_release-1.0.aar -Durl=http://localhost:9000/repository/maven-releases/

Flutter 1.12后 上传aar至maven私服_第11张图片

这里我得到了一个错误, 错误原因呢是认证失败, 也就是说没权限, 这是肯定的, 因为一个 maven 私服你能看就不错了, 没权限还想上传怕是想太多, 万一你给人挂个马怎么办?

所以我们需要配置权限

眼尖的同学可能发现了, 有一个 Help 1 标签告诉我们如何配置权限

点开一看, 有 3 种方式:

  1. 配置 http 代理, 然后在代理里帮我们加上 basic 的请求头(这里就引申出一个, 可以用反代的方式, 你请求自己的某个 nginx/caddy, 然后由 nginx 帮你加 http 的 authentication 请求头, 但是这样做太复杂没必要)
  2. settings.xml 中针对仓库配置密码或私钥选项
  3. 配置 ssl 来连接

这里 ssl 比较复杂, 不适合 demo 演示, 如果你们公司有要求则根据你们公司的来

我这里选用 settings.xml 来做, 并且为了演示方便, 我将 settings 放在项目文件夹下, 然后用mvn -s 参数引用配置文件, 这样的好处是对于系统的 mvn 配置没有侵入性, 当然你也可以按照官方的说法修改 m2 文件夹下的配置文件,这个请自行搜索如何做

我的 maven 是 brew 安装的, 可以通过brew info maven来查看

然后进入到/libexec/conf文件夹下, 复制 settings.xml 出来

然后找到 servers 标签修改如下:

<servers>
    <server>
      <id>nexusid>
      <username>adminusername>
      <password>adminpassword>
    server>
<servers>

用户名,密码换成你的

mvn deploy:deploy-file \
-DpomFile="build/host/outputs/repo/com/example/flutter_module_example/flutter_release/1.0/flutter_release-1.0.pom" \
-DgeneratePom=false \
-Dfile="build/host/outputs/repo/com/example/flutter_module_example/flutter_release/1.0/flutter_release-1.0.aar" \
-Durl="http://localhost:9000/repository/maven-releases" \
-DrepositoryId="nexus" \
-Dpackaging=aar \
-s="mvn-settings.xml"

这样就可以提交成功了

但是这样会发现一个问题, flutter 打包出来的版本号永远是 1.0, 而 flutter 在稍后的版本提供了参数可以在 build aar 的时候修改版本号, 而对于目前应用广泛的 stable 版(1.12.13+hotfix.7)来说, 并没有这个功能

所以, 我们需要在上传前修改 pom 文件, 将版本号根据某个规律提升或指定, 而这个是可以自动化完成的事情, 因为 pom 实质上就是 xml 文件, 而 xml 的解析对于大部分语言来说都是有三方库可以完成的

整理思路

自动化部署需要完成如下的步骤

  1. flutter build aar
  2. 找到所有的 aar 文件对应的 pom 文件, 使用 xml 解析并将版本号修改, 同时修改依赖对应的版本号
  3. 上传 aar 和 pom
  4. 修改 android 端的依赖到最新的 flutter 版本号

编写脚本

思路有了, 我们编写工具完成这个步骤就可以了, 这里我使用 dart 来完成, 因为 dart 是 flutter 的开发语言, 对于 flutter 开发来说上手难度较低, 当然也可以用 python/go 等任何你熟悉的语言来完成

import 'dart:convert';
import 'dart:io';
import 'package:xml/xml.dart' as xml;

const targetVersion = "1.0.4";

class DeployObject {
  File pomFile;
  File aarFile;
}

void main() {
  List aarFiles = [];
  List needChangeList = [];
  List deploys = [];

  final dir = Directory("build/host/outputs/repo");

  // 扫描aar
  for (final file in dir.listSync(recursive: true)) {
    if (file.path.endsWith(".aar")) {
      aarFiles.add(file); // aar文件
      needChangeList.add(
        file.uri.pathSegments[file.uri.pathSegments.length - 3],
      ); // 库的名称, 为了简单, 我只扫描了项目名称, 没有扫描组名, 如果你不幸有重名, 需要自己增加对于包名的处理
    }
  }

  for (final aar in aarFiles) {
    final pomFile = handlePom(needChangeList, aar); // 处理pom文件
    deploys.add(DeployObject()
      ..aarFile = aar
      ..pomFile = pomFile);
  }

  for (final deploy in deploys) {
    deployPkt(deploy);
  }
}

File handlePom(List needChangeVersionList, File aarFile) {
  final pomPath = aarFile.path.substring(0, aarFile.path.length - 3) + 'pom';

  final file = File(pomPath);

  final doc = xml.parse(file.readAsStringSync());

  {
    // 修改自身的版本号
    final xml.XmlText versionNode =
        doc.findAllElements("version").first.firstChild;
    versionNode.text = targetVersion;

    // 这里添加大括号只是为了看的清楚, 实际可不加
  }

  final elements = doc.findAllElements("dependency");
  for (final element in elements) {
    final artifactId = element.findElements("artifactId").first.text;
    if (needChangeVersionList.contains(artifactId)) {
      final xml.XmlText versionNode =
          element.findElements("version").first.firstChild;
      versionNode.text = targetVersion; // 修改依赖的版本号
    }
  }

  final buffer = StringBuffer();

  doc.writePrettyTo(buffer, 0, "  ");
  print(buffer);

  return file..writeAsStringSync(buffer.toString());
}

Future deployPkt(DeployObject deploy) async {
  final configPath = File('mvn-settings.xml').absolute.path;
  List args = [
    'deploy:deploy-file',
    '-DpomFile="${deploy.pomFile.absolute.path}"',
    '-DgeneratePom=false',
    '-Dfile="${deploy.aarFile.absolute.path}"',
    '-Durl="http://localhost:9000/repository/maven-releases"',
    '-DrepositoryId="nexus"',
    '-Dpackaging=aar',
    '-s="$configPath"',
  ];
  final shell = "mvn ${args.join(' \\\n    ')}";
  final f = File(
      "${Directory.systemTemp.path}/${DateTime.now().millisecondsSinceEpoch}.sh");
  f.writeAsStringSync(shell);
  final process = await Process.start('bash', [f.path]);
  final output = await utf8.decodeStream(process.stdout);
  print(output);
  final exitCode = await process.exitCode;
  if (exitCode != 0) {
    exit(exitCode);
  }
}

完整版的脚本在这里
只保证有 bash 环境可用, 因为是用 bash 来执行的, window 可能需要根据你的环境有所修改

然后, 每次都会创建 n 个临时的 sh 文件用于上传

我这里执行一下: $ dart bin/main.dart

Flutter 1.12后 上传aar至maven私服_第12张图片

能看到一堆的执行成功

Flutter 1.12后 上传aar至maven私服_第13张图片

然后我们查看 nexus 的 web 端, 我们能看到 1.0.4 在 maven 私服上已经有了

点右边的 pom 文件的连接看看内容

Flutter 1.12后 上传aar至maven私服_第14张图片

自身是 1.0.4 版本, 依赖的 path_provider 也是, 但 io.flutter 的引擎体系还是应该的版本号

宿主项目的修改

接着就是修改宿主项目的依赖了, 我们最早的时候用的是 1.0 版本

我们添加依赖

implementation 'com.example.flutter_module_example:flutter_release:1.0.4'

然后 gradle sync, 等待一会儿就能同步成功了

这样以后我们 flutter 开发人员就可以直接对安卓组说, 我更新了一个开发版, 1.0.5-dev1, 你试试能不能跑, 交互有没有问题之类的

修改代码打开 flutter 的页面

这里就是修改 MainActivity 了

修改布局文件: activity_main.xml

Flutter 1.12后 上传aar至maven私服_第15张图片

修改下右边的 text 属性

修改 java 文件

package top.kikt.flutterhost;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;

import io.flutter.app.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.view.FlutterMain;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 添加下面那段
    findViewById(R.id.bt_to_flutter).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        FlutterMain.startInitialization(getApplicationContext());
        FlutterMain.ensureInitializationComplete(getApplicationContext(), null);
        Intent intent = new Intent(MainActivity.this, FlutterActivity.class);
        startActivity(intent);
      }
    });
  }
}

开启新页面的代码, 一点, 嗯… crash 了

没事, 我们查查日志

Flutter 1.12后 上传aar至maven私服_第16张图片

哦, 原来是没添加 Activity 到 manifest 里, 好久没写安卓都忘了

打开AndroidManifest.xml

<application>
 <activity android:name="io.flutter.app.FlutterActivity" />
application>

然后运行就可以跑起来了, 点下 Open flutter 按钮

Flutter 1.12后 上传aar至maven私服_第17张图片

Flutter 1.12后 上传aar至maven私服_第18张图片

开启 flutter 页面的时候会黑屏一下, 这是因为这里需要加载 flutter 引擎, 提前加载引擎可以减少这种情况, 但无法根治, 请自行百度搜索解决方案

这里是提前初始化的代码

package top.kikt.flutterhost;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;

import io.flutter.app.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.view.FlutterMain;

public class MainActivity extends AppCompatActivity {

  boolean flutterInited = false;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    findViewById(R.id.bt_to_flutter).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        if (!flutterInited) {
          FlutterMain.startInitialization(getApplicationContext());
          FlutterMain.ensureInitializationComplete(getApplicationContext(), null);
        }
        Intent intent = new Intent(MainActivity.this, FlutterActivity.class);
        startActivity(intent);
      }
    });

    initFlutterEngine();
  }

  void initFlutterEngine() {
    FlutterMain.startInitialization(getApplicationContext());
    FlutterMain.ensureInitializationCompleteAsync(getApplicationContext(), null, new Handler(), new Runnable() {
      @Override
      public void run() {
        flutterInited = true;
      }
    });
  }
}

这里如果有用模拟器的朋友可能会遇到没找到 so 的 crash, 这是因为 x86 引擎没有 release 的 so 的原因, 而我们是单独引用 release 的原因

我们按照官方的方案添加如下配置在 app 的 build.gradle 里

android{
    buildTypes {
        profile {
            initWith debug
        }
    }
}

dependencies{
    def flutterModuleVersion = '1.0.4'
    debugImplementation "com.example.flutter_module_example:flutter_debug:$flutterModuleVersion"
    profileImplementation "com.example.flutter_module_example:flutter_profile:$flutterModuleVersion"
    releaseImplementation "com.example.flutter_module_example:flutter_release:$flutterModuleVersion"
}

这样就可以了

后记

本篇完成了打包 aar 并上传 maven 的全过程

仓库地址

有问题请在博客下留言, 其他地方的留言不保证能看到

以上

你可能感兴趣的:(flutter)