逼格满满的版本升级效果,一个字,帅!
作者:下位子
https://juejin.im/user/2313028193761389
前言
实现一套完整的应用内更新流程
实现炫酷的更新弹窗动画
正文
APK 打包
在终端使用 flutter build apk --target-platform android-arm64 --split-per-abi 进行打包,任务执行完成后,会在 build/app/outputs/apk/release/app-arm64-v8a-release.apk 下生成 apk 文件。这里根据需要编译平台,我这边只需要arm64下的 apk,所以中添加 android-arm64 配置。
记得更新 pubspec.yaml 中的 version 字段。version: 2.6.0+26 2.6.0 代表版本名称,26 代表版本号,用于判断是否需要提醒更新。
整理距离上一个版本的 change,看一下主要有哪些更新点,为后面更新说明做准备。
前端页面配置
创建表
class OTAData(models.Model):
ota_url = models.CharField("下载地址", max_length=256)
ota_app_code = models.CharField("版本号", max_length=100)
ota_app_desc = models.TextField("更新点", default="")
class Meta:
db_table = 'otadata'
verbose_name = 'OTA数据'
verbose_name_plural = verbose_name
创建后台展示
@admin.register(OTAData)
class OTAAdmin(ImportExportActionModelAdmin):
resource_class = OTAResource
list_display = ("ota_url", "ota_app_code", "ota_app_desc")
配置数据
服务端数据下发
def ota_data(request):
ota_data = OTAData.objects.all()
inner_data = {}
if ota_data.__len__() > 0:
item = ota_data[0]
inner_data = {
"url": item.ota_url,
"appCode": item.ota_app_code,
"desc": item.ota_app_desc,
}
data = {
"status": 0,
"code": 200,
"data": inner_data,
}
return JsonResponse(data, json_dumps_params={'ensure_ascii': False})
path('ota/', ota_data)
{
"status": 0,
"code": 200,
"data": {
"url": "http://xiaweizi.top/SimplicityWeather-2_6.apk",
"appCode": "26",
"desc": "- 新增城市管理动画效果\r\n- 优化搜索结果页展示效果\r\n- 新增炫酷的 demo 入口效果\r\n- 优化背景动画效果"
}
}
客户端
app 启动时请求 ota 接口
如果当前版本小于最新版本,则弹窗提示,并展示配置的更新内容
点击「立即更新」,根据配置的下载地址进行下载,并实时返回下载进度
下载成功,跳转到更新页面,提醒用户覆盖安装
static initOTA() async {
var otaData = await WeatherApi().getOTA();
if (otaData != null && otaData["data"] != null) {
String url = otaData["data"]["url"];
String desc = otaData["data"]["desc"];
String versionName = "";
int appCode = int.parse(otaData["data"]["appCode"]);
var packageInfo = await PackageInfo.fromPlatform();
var number = int.parse(packageInfo.buildNumber);
if (appCode > number) {
showOTADialog(url, desc, versionName);
}
}
}
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
String[] permissions = {
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
ActivityCompat.requestPermissions(registrar.activity(), permissions, 0);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
if (headers != null) {
Iterator<String> jsonKeys = headers.keys();
while (jsonKeys.hasNext()) {
String headerName = jsonKeys.next();
String headerValue = headers.getString(headerName);
request.addRequestHeader(headerName, headerValue);
}
}
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
request.setDestinationUri(fileUri);
final DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
final long downloadId = manager.enqueue(request);
private void trackDownloadProgress(final long downloadId, final DownloadManager manager) {
Log.d(TAG, "OTA UPDATE TRACK DOWNLOAD STARTED " + downloadId);
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "OTA UPDATE TRACK DOWNLOAD THREAD STARTED " + downloadId);
boolean downloading = true;
boolean hasStatus = false;
long downloadStart = System.currentTimeMillis();
while (downloading) {
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(downloadId);
Cursor c = manager.query(q);
if (c.moveToFirst()) {
hasStatus = true;
long bytes_downloaded = c.getLong(c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
long bytes_total = c.getLong(c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
if (progressSink != null && bytes_total > 0) {
Message message = new Message();
Bundle data = new Bundle();
data.putLong(BYTES_DOWNLOADED, bytes_downloaded);
data.putLong(BYTES_TOTAL, bytes_total);
message.setData(data);
handler.sendMessage(message);
}
c.close();
try {
Thread.sleep(250);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
long duration = System.currentTimeMillis() - downloadStart;
if (!hasStatus && duration > MAX_WAIT_FOR_DOWNLOAD_START) {
downloading = false;
Log.d(TAG, "OTA UPDATE FAILURE: DOWNLOAD DID NOT START AFTER 5000ms");
Message message = new Message();
Bundle data = new Bundle();
data.putString(ERROR, "DOWNLOAD DID NOT START AFTER 5000ms");
message.setData(data);
handler.sendMessage(message);
}
}
}
}
}).start();
}
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//AUTHORITY NEEDS TO BE THE SAME ALSO IN MANIFEST
Uri apkUri = FileProvider.getUriForFile(context, androidProviderAuthority, downloadedFile);
intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(apkUri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
每次请求后的 ota 数据可以缓存到本地,下次可以快速的弹出提示弹窗
当用户点击关闭后,可以自定义策略,比如几天内不再提示
在特殊页面,比如关于页面,以红点的形式提醒用户有新版本更新,这种侵入性比较低,弱提醒,对用户感染性很小。
炫酷的更新弹窗
作为天气app,那整体的设计风格当然要保持一致,之前特定花了一点时间,将炫酷的天气动态背景抽成插件,并发布 flutter_weather_bg,项目地址为:
https://github.com/xiaweizi/flutter_weather_bg
progressController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 3000),
);
waveController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 800),
);
progressController.animateTo(widget.progress);
waveController.repeat();
@override
void didUpdateWidget(WaveProgress oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.progress != widget.progress) {
progressController.animateTo(widget.progress / 100.0);
}
}
double progress = _progressAnimation.value;
double frequency = 3.2;
double waveHeight = 4.0;
double currentHeight = (1 - progress) * size.height;
Path path = Path();
path.moveTo(0.0, currentHeight);
for (double i = 0.0; i < size.width; i++) {
path.lineTo(
i,
currentHeight +
sin((i / size.width * 2 * pi * frequency) +
(_waveAnimation.value * 2 * pi) +
pi * 1) *
waveHeight);
}
path.lineTo(size.width, size.height);
path.lineTo(0.0, size.height);
path.close();
canvas.drawPath(path, bottomPaint);
frequency控制周期,越大越密集
waveHeight控制高度,越大越陡
currentHeight根据当前进度,绘制起始高度
for (double i = 0.0; i < size.width; i++) {
path.lineTo(
i,
currentHeight +
sin((i / size.width * 2 * pi * frequency) +
(_waveAnimation.value * 2 * pi) +
pi * 1) *
waveHeight);
}
好了,到此文章结束,虽然不算复杂,但是完整的讲述了一套应用内更新,从前端到服务端,再到客户端的逻辑,感兴趣的可以到SimplicityWeather下载体验。
https://github.com/xiaweizi/SimplicityWeather
推荐阅读
• 耗时2年,Android进阶三部曲第三部《Android进阶指北》出版!
推荐我的技术博客
推荐一下我的独立博客: liuwangshu.cn ,内含Android最强原创知识体系,一直在更新,欢迎体验和收藏!
欢迎加入BATcoder技术交流群
你好,我是刘望舒,百度百科收录的腾讯云TVP专家,著有三本技术畅销书,蝉联四届电子工业出版社年度优秀作者,谷歌开发者社区特邀讲师。
前华为面试官,现大厂技术负责人。
欢迎添加我的微信 henglimogan ,备注:BATcoder,加入BATcoder交流群。
明天见(。・ω・。)