背景
最近遇到一个需求,需要实现app的热更新,了解了一下热更新方案时间的时间有点久,就做了个app升级的过渡版本,然后遇到问题 真机安装遇到签名不一致的问题
如下
安装过程
版本升级的代码如下
///版本更新检查
static Future checkVersionUpdate() async {
if (isWeb) {
return VersionEntity(need: false);
}
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version;
// String jsonStr = await rootBundle.loadString('assets/json/version.json');
// Map jsonData = json.decode(jsonStr);
Map jsonData = await getVersion();
VersionEntity versionEntity = VersionEntity.fromJson(jsonData);
Version latestVersion = Version.parse(versionEntity.version ?? version);
Version currentVersion = Version.parse(version);
print(1);
if (latestVersion > currentVersion) {
_toUpdate(versionEntity);
} else {
return VersionEntity(need: false);
}
/// 非强制更新
// if (versionEntity.data!.need! && !(versionEntity.data!.necessary!)) {
// _toUpdate(versionEntity);
// } // 强制更新
// else if (versionEntity.data!.need! && versionEntity.data!.necessary!) {
// _toUpdate(versionEntity);
// } else {
// // KLogUtil.d('无需更新');
// }
return versionEntity;
}
///版本更新弹窗
static _toUpdate(VersionEntity versionEntity) {
// 进度
String progress = '';
double downloadProgress = 0.0;
bool downloadStart = false;
// 禁止返回
bool disBack = versionEntity.necessary!;
Get.bottomSheet(
isDismissible: !(versionEntity.necessary!),
enableDrag: !(versionEntity.necessary!),
StatefulBuilder(
builder: (context, state) {
String fileAddress = '';
return WillPopScope(
onWillPop: () => _onBackPressed(disBack),
child: AppToast.bottomSheetContainer(
padding: EdgeInsets.fromLTRB(20.w, 5.h, 20.w, 20.h),
height: 429.h,
bgColor: const Color(0xFF25272B),
child: Column(
children: [
Container(
width: 36.w,
height: 4.h,
decoration: BoxDecoration(
color: versionEntity.necessary!
? Colors.transparent
: const Color(0xFF3B3D40),
borderRadius: BorderRadius.circular(3.r),
),
),
10.verticalSpace,
Assets.images.versionUpdate
.image(width: 100.r, height: 100.r),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.h),
child: Text(
"Update to ${versionEntity.version}",
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.h),
child: Row(
children: [
Text(
"更新内容:",
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: versionEntity.contents?.length ?? 0,
itemBuilder: (context, index) {
return Text(
versionEntity!.contents![index],
style: TextStyle(
color: Colors.grey,
fontSize: 13.sp,
),
);
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
versionEntity.necessary == false && disBack == false
? Expanded(
child: SizedBox(
height: 56.h,
child: ElevatedButton(
onPressed: () {
/// 每天弹一次更新
Get.back();
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color(0xFF25272B)),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16.r),
),
),
),
child: Text(
"Not Now ",
style: TextStyle(
color: const Color(0xFF25272B),
fontSize: 14.sp,
fontWeight: FontWeight.bold,
),
),
),
),
)
: Container(),
versionEntity.necessary == false && disBack == false
? SizedBox(width: 15.w)
: Container(),
Visibility(
visible: progress == '',
child: Expanded(
child: SizedBox(
height: 56.h,
child: ElevatedButton(
onPressed: () async {
if (Platform.isIOS) {
InstallPlugin.install(iosAppStoreUrl);
// AppInstaller.goStore(
// iosAppStoreUrl, "id1375433239",
// review: true);
return;
}
if (progress == '') {
state(() {
downloadStart = true;
});
disBack = true; //开始下载后禁止退出弹窗
final filePath =
await getExternalStorageDirectory();
fileAddress = '${filePath!.path}/app-LH.apk';
try {
Dio dio = Dio(
BaseOptions(
connectTimeout:
const Duration(milliseconds: 10000),
receiveTimeout: const Duration(
milliseconds: 100000),
sendTimeout:
const Duration(milliseconds: 10000),
),
);
await dio.download(
versionEntity.url!,
fileAddress,
onReceiveProgress: (received, total) {
if (total != -1) {
state(() {
progress =
"${(received / total * 100).toStringAsFixed(2)}%";
downloadProgress = received / total;
});
}
},
).then(
(response) async {
if (response.statusMessage == 'OK') {
// AppInstaller.installApk(fileAddress);
InstallPlugin.install(fileAddress);
// await AppInstaller.installApk(
// fileAddress);
} else {
AppToast.toast(
stateType: StateType.error,
tips: "Failed to download");
disBack = false;
}
},
);
} catch (e) {
print(e);
}
}
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color(0xFF4677FF)),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16.r)),
),
),
child: downloadStart
? CircularProgressIndicator(
valueColor: const AlwaysStoppedAnimation(
Colors.white),
backgroundColor:
Colors.white.withOpacity(.1),
)
: Text("Update",
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
)),
),
),
),
),
Visibility(
visible: progress != '',
child: Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(.1),
borderRadius: BorderRadius.circular(16.r),
),
clipBehavior: Clip.hardEdge,
child: Stack(
alignment: AlignmentDirectional.center,
children: [
InkWell(
child: SizedBox(
height: 56.h,
width: double.infinity,
child: LinearProgressIndicator(
value: downloadProgress,
backgroundColor: Colors.transparent,
valueColor: const AlwaysStoppedAnimation(
Color(0xFF4677FF)),
),
),
onTap: () async {
if (progress == "100.00%") {
await InstallPlugin.install(fileAddress);
}
},
),
Text(
progress == ''
? "Update"
: (progress == "100.00%"
? "安装app"
: progress),
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
))
],
),
),
),
),
],
),
],
),
),
);
},
),
);
}
static Future _onBackPressed(bool necessary) async {
// 强更 禁止退出弹窗
if (necessary) {
return false;
} else {
return true;
}
}
class VersionEntity {
bool? necessary;
bool? need;
String? version;
String? url;
List? contents;
VersionEntity({this.necessary, this.need, this.version, this.url});
VersionEntity.fromJson(Map json) {
necessary = json['necessary'];
need = json['need'];
version = json['version'];
url = json['url'];
contents = json['contents'].cast();
}
Map toJson() {
final Map data = {};
data['necessary'] = necessary;
data['need'] = need;
data['version'] = version;
data['url'] = url;
data['contents'] = contents;
return data;
}
}
下面是解决签名不一致的问题 解决方案
keytool -genkey -v -keystore ./key.jks -keyalg RSA -keysize 2048 -validity 20000 -alias HL
很多会遇到 原因是keytool 是java的库 需要配置java环境 或者 在java目录下进行操作
bash: keytool: command not found
在java环境目录 打开cmd 执行后 复制key.jks 到你的安卓目录下 (android/)
在安卓目录下(android/) 新增key.properties 文件
写入 密码是你自己设置的密码
storePassword=789asd
keyPassword=789asd
keyAlias=LH
storeFile=../key.jks
最后在你 (android/app) 下的build.gradle 配置 buildTypes
// 最上面
def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
//签名信息
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
debuggable false
minifyEnabled true
// signingConfig signingConfigs.debug
signingConfig signingConfigs.release
ndk{ // 必须加入这部分,否则可能导致编译成功的release包在真机中会闪退
abiFilters "armeabi-v7a"
}
}
debug {
ndk {
//这里要加上,否则debug包会出问题,后面三个为可选,x86建议加上不然部分模拟器回报错
abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86"
}
}
}
flutter build apk --release
这样就解决了升级遇到的签名版本不一致的问题