基于Retrofit2+RxJava2实现Android App自动更新
作者:Ever69 发布时间:2021-09-04 20:19:29
本文实例为大家分享了Retrofit2 RxJava2实现Android App自动更新,具体内容如下
功能解析
自动更新可以说已经是App的标配了,很多第三方平台也都支持这个功能,最近手头上的项目需要加入这个App自动更新,考虑到项目里有用到Retrofit2和RxJava2,于是打算使用它俩自己实现这个功能。
分析App自动更新,可以分为以下三个功能点:
1.APK文件的下载
2.下载进度的实时更新显示
3.下载完成后的自动安装
其中比较难的一点是下载进度的实时更新显示,更难的是如何优雅的进行下载进度的更新显示,这也是为什么我用Retrofit2和RxJva2实现的原因。
用过Retrofit的人都知道他的内部是基于OkHttp实现的,OkHttp大家可能都不陌生,本次解决如何优雅的进行下载进度的更新显示的关键就在OkHttp的 * 中, * 可谓是OKHttp的一大精髓,通过 * 我们可以拿到Http的请求和响应信息,拿到了这些,你想干什么都行了。本次解决问题的核心就是在 * 中拿到下载内容的长度并通过自定义的RxBus发送事件将下载信息发送出去,然后在合适的地方拿到这些下载信息,通过Notification实时展示下载进度。
先上一张App自动更新的流程图
功能实现
首先根据功能需求我创建了七个类:
1.ApiManager(Retrofit初始化和Api接口定义)
2.ApkLoadingBean(下载长度和文件总长度的数据类)
3.ApkResponseBody(自定义继承OKHttp的ResponseBody的类)重点
4.RxBus(使用RxJava实现的‘EventBus')重点
5.UpdateApkService(更新服务,在这里开启下载和订阅下载进度)重点
6.UpdateHelper(检查更新、弹出更新对话框)
7.UpdateManager(调用ApiManager接口进行下载)
先讲一下OKHttp里对 * 的操作,我们在 * 里拿到请求到的响应,对响应信息进行一些封装并通过RxBus发送出去。接下来看重点代码。
ApkResponseBody:
public class ApkResponseBody extends ResponseBody {
private Response originalResponse;//原responsebody
public ApkResponseBody(Response originalResponse) {
this.originalResponse = originalResponse;
}
/**
* 返回内容类型
*
* @return
*/
@Override
public MediaType contentType() {
return originalResponse.body().contentType();
}
/**
* 内容总长度
* @return
*/
@Override
public long contentLength() {
return originalResponse.body().contentLength();
}
/**
* 返回缓存源,类似于io中的BufferedReader
*
* @return
*/
@Override
public BufferedSource source() {
return Okio.buffer(new ForwardingSource(originalResponse.body().source()) {
long totalRead = 0;
//返回读取的长度
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
totalRead += bytesRead == -1 ? 0 : bytesRead;
Log.i("test", "本次下载:" + bytesRead);
Log.i("test", "总共下载:" + totalRead);
RxBus.getDefault().post(new ApkLoadingBean(contentLength(), totalRead));
return bytesRead;
}
@Override
public Timeout timeout() {
return super.timeout();
}
@Override
public void close() throws IOException {
super.close();
}
@Override
public String toString() {
return super.toString();
}
});
}
}
在source()方法中拿到下载长度和文件总长度,封装成Bean通过RxBus发送出去。并在ApiManager中初始化Retrofit的时候设置给OKHttp。
OkHttpClient client = new OkHttpClient().newBuilder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse
.newBuilder()
.body(new ApkResponseBody(originalResponse))
.build();
}
}).build();
RxBus:
public class RxBus {
private static volatile RxBus mInstance;
private final Subject<Object> mBus;
private RxBus() {
this.mBus = PublishSubject.create().toSerialized();
}
public static RxBus getDefault() {
if (mInstance == null) {
synchronized (RxBus.class) {
if (mInstance == null) {
mInstance = Holder.BUS;
}
}
}
return mInstance;
}
/**
* 发送一个事件
*
* @param obj
*/
public void post(Object obj) {
mBus.onNext(obj);
}
/**
* 暴露出RxBus的Observable供我们订阅事件
*
* @param tClass
* @param <T>
* @return
*/
public <T> Observable<T> toObservable(Class<T> tClass) {
return mBus.ofType(tClass);
}
private static class Holder {
private static final RxBus BUS = new RxBus();
}
}
UpdateService:
public class UpdateApkService extends IntentService {
private static Context mContext;
public static final String ACTION_DOWNLOAD = "intentservice.ACTION_DOWNLOAD";
public static final String DOWNLOAD_URL = "DOWNLOAD_URL";
public static final String APK_PATH = "APK_PATH";
private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
private NotificationCompat.Builder mBuilder;
private NotificationManager mNotificationManager;
public UpdateApkService() {
super("UpdateApkService");
}
public static void startUpdateService(Context context, String url, String apkPath) {
mContext = context;
Intent intent = new Intent(context, UpdateApkService.class);
intent.setPackage(context.getPackageName());
intent.setAction(ACTION_DOWNLOAD);
intent.putExtra(DOWNLOAD_URL, url);
intent.putExtra(APK_PATH, apkPath);
context.startService(intent);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent != null) {
String action = intent.getAction();
if (ACTION_DOWNLOAD.equals(action)) {
T.showShort(mContext,"开始下载...");
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("下载最新版中")
.setProgress(100, 0, false)
.setAutoCancel(true);
mNotificationManager.notify(0, mBuilder.build());
String url = intent.getStringExtra(DOWNLOAD_URL);
String apkPath = intent.getStringExtra(APK_PATH);
subscribeEvent();//订阅下载进度
UpdateManager.downLoadApk(this, url, apkPath, mCompositeDisposable);
}
}
}
private void subscribeEvent() {
RxBus.getDefault().toObservable(ApkLoadingBean.class)
.subscribe(new Observer<ApkLoadingBean>() {
@Override
public void onSubscribe(Disposable d) {
mCompositeDisposable.add(d);
}
@Override
public void onNext(ApkLoadingBean bean) {
int progress = (int) Math.round(bean.getProgress() / (double) bean.getTotal() * 100);
mBuilder.setProgress(100, progress, false);
mNotificationManager.notify(0, mBuilder.build());
if (progress==100)
mNotificationManager.cancel(0);
}
@Override
public void onError(Throwable e) {
subscribeEvent();
}
@Override
public void onComplete() {
subscribeEvent();
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("test", "UpdateService is destory");
}
}
在Service中订阅下载进度,拿到 * 里发送的封装好的下载信息Bean,通过计算出进度显示在Notification上,这样就可以实现我们实时更新下载进度的需求了。
贴一张以上几个类的关联图,提大家梳理一下。
总结
通过Retrofit2+RxJava2实现了App自动更新,加深了我对这两个框架的理解和使用技巧,也扩展了自己的思路,记得以前自己写自动更新的时候,思绪混乱,代码不堪入目。。这次不仅实现了自动更新,还使用了相当优雅的解决方式。
来源:https://blog.csdn.net/Ever69/article/details/80222543


猜你喜欢
- using System;using System.Collections.Generic;using System.Data;using
- 如何下载并配置JDK 15进入官网下载JDK 15。官网地址:https://www.oracle.com/index.html脚本之家下载
- 本文实例为大家分享了Unity Shader实现黑幕过场效果的具体代码,供大家参考,具体内容如下一、效果演示二、实现Shader:黑幕过场着
- 自定义缓存 - ehcacheEhcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器1.导包&l
- 本文实例为大家分享了C#实现QQ聊天窗口的具体代码,供大家参考,具体内容如下分析需要两个TextBox,一个用于显示消息,一个用于编辑消息需
- 首先不可否认,这些在面试上会经常被面试官问起,但是你回答的让面试官满意吗?当然如果你知道了这些原理,或许你就不怕了。既然说到了原理,我们还是
- 最近看了一些淘宝购物车的demo,于是也写了一个。效果图如下:主要代码如下: actvity中的代码:public class Shoppi
- 前言二进制文件读写两个重要的函数 , fread 和 fwrite , fread 用于读取文件 , fwrite 用于写出文件 ;frea
- package com.jiucool.www.struts.action; import java.io.B
- 在安卓开发中,经常会使用到一些动画,那么在开发中,如何使用这些动画呢?帧动画:不是针对View做出一些形状上的变化,而是用于播放一张张的图片
- 本项目使用的环境:开发工具:Intellij IDEA 2017.1.3springboot: 1.5.6jdk:1.8.0_161mave
- springboot集成mybatis plus和dynamic-datasource注意事项环境spring-boot-starter-p
- 打印Java程序的线程栈信息jstack可以得知当前线程的运行情况安装jstack等命令集,jstack是开发版本jdk的一部分,不是开发版
- 众所周知,当你点击一个超链接进行跳转时,WebView会自动将当前地址作为Referer(引荐)发给服务器,因此很多服务器端程序通过是否包含
- volatile变量volatile是Java的关键词,我们可以用它来修饰变量或者方法。为什么要使用volatilevolatile的典型用
- 在Android开发中,我们经常使用列表控件,而有时候列表控件条目中又会是多条目数据,这时候,我们无法确定每个条目的数据多少,而为了美观,我
- • 创建目录和文件1、通过Path类的Combine方法可以合并路径。string activeDir = @"C:\myDir&
- 介绍众所周知,AOP(面向切面编程)是Spring框架的特色功能之一。通过设置横切关注点(cross cutting concerns),A
- 先来看看效果:一、添加依赖库的步骤1.项目的gradle文件内的做以下改动allprojects { repositories
- 1.java后台(1)使用BigDecimal类方式一:String str=new BigDecimal(num+""