使用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自动更新,加深了我对这两个框架的理解和使用技巧,也扩展了自己的思路,记得以前自己写自动更新的时候,思绪混乱,代码不堪入目。。这次不仅实现了自动更新,还使用了相当优雅的解决方式。不敢说很牛逼,中等牛逼吧。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值