Flutter Andriod打包发布和更新

前言

软件发布和更新是肯定要做的。Flutter在安卓和IOS上面发布是不一样的。而且我也没有IOS的手机,只有一个破小米。所以我们这里以Android 为例。

相关链接

flutter Android 打包和发布:https://zhuanlan.zhihu.com/p/602250391

构建和发布为 Android 应用:https://docs.flutter.cn/deployment/android

软件推荐

为了方便调试,我这里推荐两个软件

localSend 局域网跨设备互传文件:https://github.com/localsend/localsend
Anlink 免费无广告的电脑手机投屏软件:https://cn.anlinksoft.com/download/

Andriod打包

直接按照这个流程,打包

flutter Android 打包和发布:https://zhuanlan.zhihu.com/p/602250391

更改软件icon

官方推荐使用这个库来设置多平台的图标。

flutter_launcher_icons 0.13.1:https://pub-web.flutter-io.cn/packages/flutter_launcher_icons

dev_dependencies:
  ......
  flutter_lints: ^3.0.0


flutter_launcher_icons:
  android: "launcher_icon"
  ios: false
  image_path: "assets/icon/icon.png"
  min_sdk_android: 21 # android min sdk min:16, default 21
  web:
    generate: false
    image_path: "path/to/image.png"
    background_color: "#hexcode"
    theme_color: "#hexcode"
  windows:
    generate: false
    image_path: "path/to/image.png"
    icon_size: 48 # min:48, max:256, default: 48
  macos:
    generate: false
    image_path: "path/to/image.png"

命令行运行:

flutter pub get
flutter pub run flutter_launcher_icons

软件更新

flutter_xupdate实现Android版本一键更新:https://juejin.cn/post/7106452814141849607

如果Build运行很慢,可以试试以下方式

测试运行

测试代码

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_xupdate/flutter_xupdate.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  ///初始化
  void initXUpdate() {
    debugPrint("初始化项目");
    if (Platform.isAndroid) {
      FlutterXUpdate.init(

              ///是否输出日志
              debug: true,

              ///是否使用post请求
              isPost: false,

              ///post请求是否是上传json
              isPostJson: false,

              ///请求响应超时时间
              timeout: 25000,

              ///是否开启自动模式
              isWifiOnly: false,

              ///是否开启自动模式
              isAutoMode: false,

              ///需要设置的公共参数
              supportSilentInstall: false,

              ///在下载过程中,如果点击了取消的话,是否弹出切换下载方式的重试提示弹窗
              enableRetry: false)
          .then((value) {
        debugPrint("初始化成功: $value");
      }).catchError((error) {
        debugPrint(error);
      });

      // FlutterXUpdate.setErrorHandler(
      //     onUpdateError: (Map<String, dynamic> message) async {
      //       print(message);
      //       setState(() {
      //         _message = "$message";
      //       });
      //     });
    } else {
      debugPrint("ios暂不支持XUpdate更新");
    }

    FlutterXUpdate.setErrorHandler(onUpdateError: (message)async{
      debugPrint(message.toString());
      if(message?["code"] == 400){

      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              '版本1.0.1',
            ),
            ElevatedButton(onPressed: initXUpdate, child: const Text("初始化")),
            ElevatedButton(
                onPressed: () {
                  FlutterXUpdate.checkUpdate(
                      url: "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json");
                },
                child: const Text("软件更新")),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

其实核心代码就是这个

https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json

打开了之后就长这样

{
    "Code": 0,
    "Msg": "",
    "UpdateStatus": 1,
    "VersionCode": 3,
    "VersionName": "1.0.2",
    "UploadTime": "2018-07-10 17:28:41",
    "ModifyContent": "\r\n1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。",
    "DownloadUrl": "https://xuexiangjys.oss-cn-shanghai.aliyuncs.com/apk/xupdate_demo_1.0.2.apk",
    "ApkSize": 2048,
    "ApkMd5": "E4B79A36EFB9F17DF7E3BB161F9BCFD8"
}

C# 后端模拟

你后端也可以用别的,比如Node.js,Springboot都可以

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using StaticFiles.Models;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace StaticFiles.Controllers
{
    /// <summary>
    /// 软件更新
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class VersionController : ControllerBase
    {

        /// <summary>
        /// Json获取
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public IActionResult GetJson()
        {

            var filePath = "MyStaticFiles/app-release.apk";
            long size = 2048;

            if (System.IO.File.Exists(filePath))
            {
                //定义一个FileInfo对象,使之与filePath所指向的文件向关联,
                //以获取其大小
                FileInfo fileInfo = new FileInfo(filePath);
                size =  fileInfo.Length/1024;
            }

            var res = new UpdateJson()
            {
                Code = 0, //0代表请求成功,非0代表失败
                Msg = "更新失败!", //请求出错的信息
                UpdateStatus = 2, //0代表不更新,1代表有版本更新,不需要强制升级,2代表有版本更新,需要强制升级
                VersionCode = 30,
                VersionName = "2.6.20",
                ModifyContent = "1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。",
                DownloadUrl = "http://183.247.176.143:9600/api/Version/DownFile",
                ApkSize = size,
                UploadTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
            };
            return new JsonResult(res, new JsonSerializerOptions
            {
                WriteIndented = true,
            });
        }
        /// <summary>
        /// 文件下载
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public IActionResult DownFile()
        {
            FileStream fs = new FileStream("MyStaticFiles/app-release.apk", FileMode.Open, FileAccess.Read);

            var res = new FileStreamResult(fs, "application/octet-stream") { FileDownloadName = "app-release.apk" };

            return res;
        }
    }
}

namespace StaticFiles.Models
{
    public class UpdateJson
    {

        public int Code { get; set; }
        public string Msg { get; set; }
        public int UpdateStatus { get; set; }
        public int VersionCode { get; set; }
        public string VersionName { get; set; }
        public string ModifyContent { get; set; }
        public string DownloadUrl { get; set; }
        public long ApkSize { get; set; }

        public string UploadTime { get; set; }

    }
}

问题

后面测试了很久,出现了两个问题

  • 请求必须是https,要有ssl认证,不然会报这个错误

I/flutter (20463): {code: 2000, detailMsg: Code:2000, msg:查询失败:网络请求错误!(CLEARTEXT communication to 183.247.176.143 not permitted by network security policy), message: 查询失败:网络请求错误!(CLEARTEXT communication to 183.247.176.143 not permitted by network security policy)}

其实最重要的问题就是缺少SSL认证

解决方案

解决安卓9.0版本不能访问http请求问题:https://blog.csdn.net/MrMyGod/article/details/105032262?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-1-105032262-blog-137159952.235v43pc_blog_bottom_relevance_base9&spm=1001.2101.3001.4242.2&utm_relevant_index=4

Android Studio 国内镜像代理设置(如果设置之后还是远程仓库下载失败,请仔细阅读其内容就可以解决了):https://blog.csdn.net/NakajimaFN/article/details/126434254

如果是第一次使用,配置远程地址为:

mirrors.neusoft.edu.cn

运行成功!但是会提示MD5加密失败

我们把MD5去掉即可

更新失败

常见问题汇总:https://github.com/xuexiangjys/flutter_xupdate/wiki/常见问题

VesionCode,VersionName

首先我们要知道什么是VesionCode,VersionName

  • VersionCode,给程序员看的,都是整形。只有Code增加之后才会更新。比如21->22才会更新,21->21,21->19不会更新
  • VersionName,给用户看的,比如1.2.1,1.3.4。不影响更新逻辑

签名

如果一个程序要更新,老版本和新版本的签名必须一致。而我们Debug和Release的签名是不一致的。Debug用的是测试签名,是Flutter官方给我们的。Release用的是我们自己的签名。所以Debug版本是无法更新的,必须要用Release版本安装之后才能更新程序

总结

详细的我就不展开说明了。幸好有人已经帮我解决的更新的大部分问题,不如安卓的更新还得自己去安卓那里处理部分的代码,使用Channel去调用,会非常的麻烦

posted @ 2024-05-24 17:13  gclove2000  阅读(11)  评论(0编辑  收藏  举报