flutter_xupdate实现Android版本一键更新
XUpdate可以实现一键更新Flutter应用,是一款国产的开源作品,从作者github的记录上看,在app更新方面已经开发了有3、4年的时间了,不但有flutter的产品,还有android版本的更新组件(我想flutter应该也是从android的版本升级过来的吧)并且提供了在线更新的后端服务系统,有兴趣的朋友可以去他的github账号了解一下。(后台版本更新管理服务,后台版本更新管理系统)
在了解flutter在线更新插件时,在pub上还发现一个国外的产品in_app_update,这个产品的likes、popularity都高于xupdate,看了看文档,发现这个东西在国内可能会水土不服啊,还是来支持国产软件吧。
这个插件只支持android,今天就使用android给大家演示一下。
pub地址:pub.dev/packages/fl…(如果你觉得好用,请给作者一个likes)
github地址:github.com/xuexiangjys…(如果你觉得好用,请给作者一个star)
安装依赖
flutter pub add flutter_xupdate
or
dependencies:
flutter_xupdate: ^2.0.2
配置需要的周边环境
xupdate需要我们将android主题修改为AppCompat,打开文件:android/app/src/main/res/values/styles.xml
,将LaunchTheme
主题修改为Theme.AppCompat.Light.NoActionBar
。
<resources>
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
如果不配置会报这个错误
E/ThemeUtils(11695): View class androidx.appcompat.widget.AppCompatImageView is an AppCompat widget that can only be used with a Theme.AppCompat theme (or descendant).
W/System.err(11695): java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
W/System.err(11695): at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:846)
W/System.err(11695): at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:809)
W/System.err(11695): at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:687)
W/System.err(11695): at androidx.appcompat.app.AppCompatDialog.setContentView(AppCompatDialog.java:100)
W/System.err(11695): at com.xuexiang.xupdate.widget.BaseDialog.init(BaseDialog.java:78)
W/System.err(11695): at com.xuexiang.xupdate.widget.BaseDialog.init(BaseDialog.java:74)
W/System.err(11695): at com.xuexiang.xupdate.widget.BaseDialog.<init>(BaseDialog.java:63)
W/System.err(11695): at com.xuexiang.xupdate.widget.BaseDialog.<init>(BaseDialog.java:50)
W/System.err(11695): at com.xuexiang.xupdate.widget.UpdateDialog.<init>(UpdateDialog.java:129)
W/System.err(11695): at com.xuexiang.xupdate.widget.UpdateDialog.newInstance(UpdateDialog.java:120)
W/System.err(11695): at com.xuexiang.xupdate.proxy.impl.DefaultUpdatePrompter.showPrompt(DefaultUpdatePrompter.java:62)
W/System.err(11695): at com.xuexiang.xupdate.UpdateManager.findNewVersion(UpdateManager.java:351)
W/System.err(11695): at com.xuexiang.xupdate.utils.UpdateUtils.processUpdateEntity(UpdateUtils.java:91)
W/System.err(11695): at com.xuexiang.xupdate.proxy.impl.DefaultUpdateChecker.processCheckResult(DefaultUpdateChecker.java:143)
W/System.err(11695): at com.xuexiang.xupdate.proxy.impl.DefaultUpdateChecker.onCheckSuccess(DefaultUpdateChecker.java:106)
W/System.err(11695): at com.xuexiang.xupdate.proxy.impl.DefaultUpdateChecker.access$000(DefaultUpdateChecker.java:46)
W/System.err(11695): at com.xuexiang.xupdate.proxy.impl.DefaultUpdateChecker$1.onSuccess(DefaultUpdateChecker.java:67)
W/System.err(11695): at com.xuexiang.flutter_xupdate.OKHttpUpdateHttpService$1.onResponse(OKHttpUpdateHttpService.java:83)
W/System.err(11695): at com.xuexiang.flutter_xupdate.OKHttpUpdateHttpService$1.onResponse(OKHttpUpdateHttpService.java:75)
W/System.err(11695): at com.zhy.http.okhttp.OkHttpUtils$3.run(OkHttpUtils.java:186)
W/System.err(11695): at android.os.Handler.handleCallback(Handler.java:883)
W/System.err(11695): at android.os.Handler.dispatchMessage(Handler.java:100)
W/System.err(11695): at android.os.Looper.loop(Looper.java:214)
W/System.err(11695): at android.app.ActivityThread.main(ActivityThread.java:7386)
W/System.err(11695): at java.lang.reflect.Method.invoke(Native Method)
W/System.err(11695): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
W/System.err(11695): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:980)
I/flutter (11695): {code: 2006, detailMsg: Code:2006, msg:查询失败:解析Json错误!(You need to use a Theme.AppCompat theme (or descendant) with this activity.), message: 查询失败:解析Json错误!(You need to use a Theme.AppCompat theme (or descendant) with this activity.)}
初始化插件
作者的Example对初始化的注释也是很丰富。
//初始化XUpdate
void initXUpdate() {
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) {
print("初始化成功: $value");
}).catchError((error) {
print(error);
});
FlutterXUpdate.setErrorHandler(
onUpdateError: (Map<String, dynamic>? message) async {
print(message);
});
} else {
debugPrint("ios暂不支持XUpdate更新");
}
}
更新应用
xupdate最主要的方法就是checkUpdate,在做好初始化和服务端的处理后,只需要一行代码即可完成在线更新,支持后台更新,静默安装需要有root权限,静默安装就算了,现在Android 的权限控制越来越多,很难做到静默安装了。我们有一些应用和一些设备的小厂合作,在出厂时就内置,这个时候用这个静默安装就很舒服了。
///默认App更新
void checkUpdateDefault() {
FlutterXUpdate.checkUpdate(url: _updateUrl);
}
///默认App更新 + 支持后台更新
void checkUpdateSupportBackground() {
FlutterXUpdate.checkUpdate(url: _updateUrl, supportBackgroundUpdate: true);
}
///自动模式, 如果需要完全无人干预,自动更新,需要root权限【静默安装需要】
void checkUpdateAutoMode() {
FlutterXUpdate.checkUpdate(url: _updateUrl, isAutoMode: true);
}
准备数据
对于在线更新的插件基本流程都是一致的,客户端在向服务器请求获取更新数据,服务端将最新的版本信息返回给客户端(一般都是json格式),客户端分析json文件,查看本地版本是不是已经过时了,如果过时则下载apk并进行安装,如果没有过时则放弃。这里能够扩展的业务有更新内容,是否强制更新等。xupdate也是这样一个过程,作者默认提供了一个json文件的格式包括对应的model,如果有特殊需求也支持自定义json格式。一般的业务默认的json就够用了。
默认的json内容
{
"Code": 0, //0代表请求成功,非0代表失败
"Msg": "", //请求出错的信息
"UpdateStatus": 1, //0代表不更新,1代表有版本更新,不需要强制升级,2代表有版本更新,需要强制升级
"VersionCode": 3,
"VersionName": "1.0.2",
"ModifyContent": "1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。",
"DownloadUrl": "https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk",
"ApkSize": 2048
"ApkMd5": "..." //md5值没有的话,就无法保证apk是否完整,每次都会重新下载。框架默认使用的是md5加密。
}
这里有一个apk的md5,作者对md5值的描述是这样的:
这里需要说明的是,这里填写的MD5值是APK文件进行MD5加密后的值,并不是对APK签名的MD5。框架默认使用的是MD5加密,如果你觉得不够安全,也可以使用其他加密方式,不过这可能涉及到原生的编码,详情参见:自定义文件加密校验器.
如果不想使用MD5的话就不需要配置这个字段,不过这样每次检查的话都会去重新下载APK,建议配置。
原来只是文件的md5,并不是apk签名的md5,那我们获取这个md5值就很方便了,我准备了3个办法来获取md5值。我放到文章的后边了,大家翻一下目录去看吧。
这里我我们准备了这样的一个版本描述文件,我随意编译了一个apk的版本,一起放到服务器上了。
{
"Code": 0,
"Msg": "",
//0代表不更新,1代表有版本更新,不需要强制升级,2代表有版本更新,需要强制升级
"UpdateStatus": 1,
"VersionCode": 2,
"VersionName": "2.0.0",
"ModifyContent": "1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。",
"DownloadUrl": "https://static-9852f929-d814-4bc5-942a-db0005fdf887.bspapp.com/app-release-1.apk",
"ApkSize": 40448,
//md5值没有的话,就无法保证apk是否完整,每次都会重新下载。框架默认使用的是md5加密。
"ApkMd5": "e2eedae8d50b4454dbf219ea426548a3"
}
开始更新
final _updateUrl = "http://192.168.2.114:15806/xupdate-2.json";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('XUpdate演示'),
),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () {
FlutterXUpdate.checkUpdate(url: _updateUrl);
},
child: const Text('默认更新'),
),
ElevatedButton(
onPressed: () {
FlutterXUpdate.checkUpdate(
url: _updateUrl,
supportBackgroundUpdate: true,
);
},
child: const Text('支持后台更新'),
),
],
),
),
);
}
\
默认APP更新
\
支持后台更新
启用后台更新
下载完成后,显示安装界面
一切ok,作者在pub主页中的demo还演示了更多的功能,包括调整宽高比,强制更新,切换下载方式,使用自定义json解析,有需要的小伙伴可以自己跑一些demo试试。
文件MD5值计算
java实现(1)
参考xupdate的校验部分,可以自己写一个获取apk文件md5值的方法。
package cn.iocoder.yudao;
import cn.hutool.core.io.FileUtil;
import cn.smallbun.screw.core.util.FileUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
/**
* @author Radium
* @date 2022/6/7 3:41 PM
*/
public class Md5Test {
public static void main(String[] args) {
File file = new File("<apk文件地址>");
String fileMD5 = getFileMD5(file);
System.out.println(fileMD5);
}
/**
* 获取文件的MD5值
*
* @param file
* @return
*/
public static String getFileMD5(File file) {
if (!FileUtil.exist(file)) {
return "";
}
try (InputStream fis = new FileInputStream(file)){
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
digest.update(buffer, 0, len);
}
return bytes2Hex(digest.digest());
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 一个byte转为2个hex字符
*
* @param src byte数组
* @return 16进制大写字符串
*/
private static String bytes2Hex(byte[] src) {
char[] res = new char[src.length << 1];
final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
for (int i = 0, j = 0; i < src.length; i++) {
res[j++] = hexDigits[src[i] >>> 4 & 0x0F];
res[j++] = hexDigits[src[i] & 0x0F];
}
return new String(res);
}
}
java实现(2)
如果有Hutool工具会更方便一点,一行代码就搞定。
String fileMD5 = MD5.create().digestHex(new File(<apk文件地址>));
System.out.println(fileMD5);
Dart实现
使用Dart获取MD5也很简单。
import 'dart:io';
import 'package:crypto/crypto.dart';
void main() async {
var file = File(<Apk文件地址>);
print(md5.convert(file.readAsBytesSync()));
}
\
无法访问http地址
错误提示:CLEARTEXT communication to " " not permitted by network security policy
。这个主要是因为Android P以后的网络访问安全策略升级,限制了非加密的流量请求。有3个办法处理。
- 减低目标版本,把targetSdkVersion设置到27一下,没有特殊需求的用这个办法就行了。
- http请求改成https,对于真正要上线的项目,修改一下https还是很有必要的。
- 禁止Android的这个限制。在res/xml/目录下新建个network_security_config.xml的文件,文件名随意只要能和Androidmanifest.xml中配置的文件名一致就行。。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
在Androidmanifest.xml中设置networkSecurityConfig只想到上述文件。
查看版本信息
引入插件
$ flutter pub add package_info_plus
or
dependencies:
package_info_plus: ^1.4.2
编写获取信息的方法。
void initVersionInfo() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
appName = packageInfo.appName;
packageName = packageInfo.packageName;
version = packageInfo.version;
buildNumber = packageInfo.buildNumber;
}
初始化数据
@override
void initState() {
super.initState();
Future.delayed(
Duration.zero,
() => setState(
() {
initVersionInfo();
},
),
);
initXUpdate();
}
展示数据
Text("appName: $appName"),
Text("packageName: $packageName"),
Text("version: $version"),
Text("buildNumber: $buildNumber"),
版本名称对应关系
packageInfo插件属性 | local.properties文件属性 | pubspec.yaml |
---|---|---|
packageInfo.version | version“+”前边的内容 | flutter.versionName |
packageInfo.buildNumber | version“+”后边的内容 | flutter.versionCode |
转载自:https://juejin.cn/post/7106452814141849607