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
- flutter_xupdate 3.0.0 copy "flutter_xupdate: ^3.0.0":https://pub.dev/packages/flutter_xupdate/install
如果Build运行很慢,可以试试以下方式
-
Flutter配置Gradle镜像,解决国内下载慢问题:https://blog.csdn.net/qianxiamuxin/article/details/134322224
-
Flutter 卡在 "Running Gradle task 'assembleDebug'... ":https://juejin.cn/post/7092175693466828836
测试运行
- xuexiangjys/flutter_xupdate Github仓库地址:https://github.com/xuexiangjys/flutter_xupdate
- Flutter系列|如何在flutter中实现版本更新功能?:https://www.bilibili.com/video/BV1YK4y1S73e/?vd_source=17cf6a5a912b0a39a628030a5814462c
测试代码
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)}
- 版本太旧,其他库不兼容:在Github的Issue上面最新的评论都是这个:https://github.com/xuexiangjys/flutter_xupdate/issues/118
其实最重要的问题就是缺少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去调用,会非常的麻烦