Rxjava2+Retrofit2+IntentService实现app自动更新

[TOC]

效果图

网速较慢,只录制了最后几秒,效果还是看得出来。

Rxjava2+Retrofit2+IntentService实现app自动更新_第1张图片
appUpdate.gif

需求

实现app的版本自动更新。

思路

其实Google官方专门提供了一个DownloadManager类来实现app的自动更新功能,因为项目的网络框架用的Retrofit2配合Rxjava2,因此想试一下自己去实现,顺便练练手。
  此处下载大文件,肯定是要放在后台操作,放在服务中比较合适,而这个服务唯一的任务就是下载,因此我们用IntentService,然后在服务中通过发送Notification实现下载进度的实时监控,那么问题来了,下载过程中如何得到当前的下载进度?这个应该就是难点所在了,本来是想在写入数据到文件的时候将写入的长度记录下来,后来意外在网上看到一种更优雅的方式,通过自定义OkHttp网络拦截器的方式来获取到当前已经下载的数据长度,并通过Rxbus发布数据(参考链接),在此做个记录。

实现

自定义ResponseBody

/**
 * 下载文件
 * Created by lxf on 2017/3/3.
 */
public class FileResponseBody extends ResponseBody {

    private Response originalResponse;//原结果

    public FileResponseBody(Response originalResponse) {
        this.originalResponse = originalResponse;
    }

    //返回内容类型
    @Override
    public MediaType contentType() {
        return originalResponse.body().contentType();
    }

    //返回内容长度,没有则返回-1
    @Override
    public long contentLength() {
        return originalResponse.body().contentLength();
    }

    //返回缓存源,类似于io中的BufferedReader
    @Override
    public BufferedSource source() {
        return Okio.buffer(new ForwardingSource(originalResponse.body().source()) {
            long bytesReaded = 0;

            //返回读取到的长度
            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                bytesReaded += bytesRead == -1 ? 0 : bytesRead;
                // 通过RxBus发布进度信息
                RxBus.getDefault().send(new DownloadBean(contentLength(), bytesReaded));
                return bytesRead;
            }
        });
    }
}

上面的代码已经加了注解,如果对Okio不了解的同学可以参考下这里。

配置网络接口

由于是通过Retrofit2下载文件,返回的Obserable泛型直接使用ResponseBody。

interface Api {
    ...
    
    @Streaming//注明为流文件,防止retrofit将大文件读入内存
    @GET
    Observable down(@Url String url);//通过@Url覆盖baseurl

}

实现网络接口:

public Observable down(String url) {
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(NetConfig.CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
                .addNetworkInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Response originalResponse = chain.proceed(chain.request());//对结果重新处理
                        return originalResponse
                                .newBuilder()
                                .body(new FileResponseBody(originalResponse))//将自定义的ResposeBody设置给它
                                .build();
                    }
                })
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
        Api api = retrofit.create(Api.class);
        return api.down(url);
}

封装UpdateManager

public class UpdateManager {

    /**
     * 是否需要更新,需要则下载
     *
     * @param context     上下文
     * @param url         新版本地址
     * @param apkPath     本地apk保存路径
     * @param cd          订阅关系集合,在数据传输完毕时解除订阅
     */
    public static void downloadApk(final Context context, final String url, final String apkPath, final CompositeDisposable cd) {
        NetWork.getInstance().down(url)
                .map(new Function() {
                    @Override
                    public BufferedSource apply(ResponseBody responseBody) throws Exception {
                        return responseBody.source();//获取数据缓冲源
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(new Observer() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        cd.add(d);
                    }

                    @Override
                    public void onNext(BufferedSource bufferedSource) {
                        try {
                            writeFile(bufferedSource, new File(apkPath));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        unSubscribe(cd);
                    }

                    @Override
                    public void onComplete() {
                        //安装apk
                        Intent intent = new Intent(Intent.ACTION_VIEW);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        intent.setDataAndType(Uri.fromFile(new File(apkPath)), "application/vnd.android.package-archive");
                        context.startActivity(intent);

                        unSubscribe(cd);
                    }
                });
    }


    /**
     * 写入文件
     */
    private static void writeFile(BufferedSource source, File file) throws IOException {
        if (!file.getParentFile().exists())
            file.getParentFile().mkdirs();

        if (file.exists())
            file.delete();

        BufferedSink bufferedSink = Okio.buffer(Okio.sink(file));
        bufferedSink.writeAll(source);

        bufferedSink.close();
        source.close();
    }

    /**
     * 解除订阅
     *
     * @param cd 订阅关系集合
     */
    private static void unSubscribe(CompositeDisposable cd) {
        if (cd != null && !cd.isDisposed())
            cd.dispose();
    }
}

通过IntentService下载apk文件

public class MyIntentService extends IntentService {
    private static final String ACTION_DOWNLOAD = "intentservice.action.download";

    private static final String DOWNLOAD_URL = "downloadUrl";
    private static final String APK_PATH = "apkPath";

    private CompositeDisposable cd = new CompositeDisposable();
    private NotificationCompat.Builder builder;
    private NotificationManager notificationManager;

    public MyIntentService() {
        super("MyIntentService");
    }

    public static void startUpdateService(Context context, String url, String apkPath) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_DOWNLOAD);
        intent.putExtra(DOWNLOAD_URL, url);
        intent.putExtra(APK_PATH, apkPath);
        context.startService(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            String action = intent.getAction();
            if (ACTION_DOWNLOAD.equals(action)) {
                notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                builder = new NotificationCompat.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle("开始下载")
                        .setAutoCancel(true)
                        .setContentText("版本更新");

                notificationManager.notify(0, builder.build());

                String url = intent.getStringExtra(DOWNLOAD_URL);
                String apkPath = intent.getStringExtra(APK_PATH);
                handleUpdate(url, apkPath);
            }
        }
    }

    private void handleUpdate(String url, String apkPath) {
        subscribeEvent();//订阅下载进度
        UpdateManager.downloadApk(this, url, apkPath, cd);
    }

    private void subscribeEvent() {
        RxBus.getDefault().toObservable(DownloadBean.class)
                .subscribe(new Observer() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        cd.add(d);
                    }

                    @Override
                    public void onNext(DownloadBean downloadBean) {
                        int progress = (int) Math.round(downloadBean.getBytesReaded() / (double) downloadBean.getTotal() * 100);
                        builder.setContentInfo(String.valueOf(progress) + "%").setProgress(100, progress, false);
                        notificationManager.notify(0, builder.build());

                        if (progress == 100)
                            notificationManager.cancel(0);
                    }

                    @Override
                    public void onError(Throwable e) {
                        subscribeEvent();
                    }

                    @Override
                    public void onComplete() {
                        subscribeEvent();
                    }
                });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("onDestory____MyIntentService");
    }
}

调用方式

public class UpdateActivity extends BaseActivity {

    ...
    
    public void startDownload(View view){
        //要下载的文件地址
        String url = "http://www.izis.cn/mygoedu/yztv_1.apk";
        //下载后的保存路径
        String apkPath = Environment.getExternalStorageDirectory().getPath() + "/yzkj.apk";
        MyIntentService.startUpdateService(this,url,apkPath);
    }
}

Demo地址

传送门

你可能感兴趣的:(Rxjava2+Retrofit2+IntentService实现app自动更新)