使用Retrofit+RxJava下载文件并实现APP更新
后臺接口
這次就不能再像上一年那樣通過一個txt文件來存儲apk信息了,我們要做的就是請后臺吃頓飯,寫一下以下接口
- 上傳接口putApk
這個接口用于方便我們上傳新版本,可暫時配合postman使用
- 獲取apk接口 getApk
我們通過當(dāng)前版本號和version的對比判斷是否需要更新
Gradle配置
//retrofitimplementation 'com.squareup.retrofit2:retrofit:2.4.0'implementation 'io.reactivex:rxandroid:1.1.0'//處理網(wǎng)絡(luò)請求在android中線程調(diào)度問題implementation 'com.squareup.retrofit2:converter-gson:2.4.0'//gson轉(zhuǎn)換implementation 'com.squareup.retrofit2:adapter-rxjava:2.4.0'implementation 'com.trello.rxlifecycle2:rxlifecycle:2.2.1'//解決RxJava內(nèi)存泄漏implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1'implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'//使用攔截器在配置的時候要注意使用攔截器的版本要和retrofit使用的okhttp3的版本保持一致,否則容易出現(xiàn)java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.Worker thread異常
權(quán)限設(shè)置
- 添加讀寫,網(wǎng)絡(luò)權(quán)限
- 在application內(nèi)添加
- 在res中新建xml資源文件夾并創(chuàng)建file_paths文件
這兩步是因為Android 7.0 以上google引入私有目錄被限制訪問和StrictMode API,也就是說在 /Android?
/data我們是有權(quán)限訪問的,但接下的文件我們就需要授權(quán)申請了
Retrofit和RxJava類與方法
該模塊內(nèi)容參考https://blog.csdn.net/jiashuai94/article/details/78775314
service 接口定義
DownloadUtils
public class DownloadUtils{private static final String TAG = "DownloadUtils";private static final int DEFAULT_TIMEOUT = 15;private Retrofit retrofit;private JsDownloadListener listener;private String baseUrl;private String downloadUrl;private RetrofitHelper retrofitHelper ;public DownloadUtils(String baseUrl, JsDownloadListener listener) {this.baseUrl = baseUrl;this.listener = listener;JsDownloadInterceptor mInterceptor = new JsDownloadInterceptor(listener);OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(mInterceptor).retryOnConnectionFailure(true).connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS).build();retrofit = new Retrofit.Builder().baseUrl(baseUrl).client(httpClient).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();}/*** 開始下載* @param url* @param file* @param subscriber*/public void download(@NonNull String url, final File file, Subscriber subscriber) {retrofit.create(Service.class).download(url).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).map(new Func1<ResponseBody, InputStream>() {@Overridepublic InputStream call(ResponseBody responseBody) {return responseBody.byteStream();}}).observeOn(Schedulers.computation()) // 用于計算任務(wù).doOnNext(new Action1<InputStream>() {@Overridepublic void call(InputStream inputStream) {writeFile(inputStream, file);}}).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);}/*** 將輸入流寫入文件* @param inputString* @param file*/private void writeFile(InputStream inputString, File file) {if (file.exists()) {file.delete();}FileOutputStream fos = null;try {fos = new FileOutputStream(file);byte[] b = new byte[1024];int len;while ((len = inputString.read(b)) != -1) {fos.write(b,0,len);}inputString.close();fos.close();} catch (FileNotFoundException e) {listener.onFail("FileNotFoundException");} catch (IOException e) {listener.onFail("IOException");}} }攔截器
public class JsDownloadInterceptor implements Interceptor {private JsDownloadListener downloadListener;public JsDownloadInterceptor(JsDownloadListener downloadListener) {this.downloadListener = downloadListener;}@Overridepublic Response intercept(Chain chain) throws IOException {Response response = chain.proceed(chain.request());return response.newBuilder().body(new JsResponseBody(response.body(), downloadListener)).build();} }下載監(jiān)聽回調(diào)
public interface JsDownloadListener {void onStartDownload(long length);void onProgress(int progress);void onFail(String errorInfo); }下載請求體
public class JsResponseBody extends ResponseBody {private ResponseBody responseBody;private JsDownloadListener downloadListener;// BufferedSource 是okio庫中的輸入流,這里就當(dāng)作inputStream來使用。private BufferedSource bufferedSource;public JsResponseBody(ResponseBody responseBody, JsDownloadListener downloadListener) {this.responseBody = responseBody;this.downloadListener = downloadListener;downloadListener.onStartDownload(responseBody.contentLength());}@Overridepublic MediaType contentType() {return responseBody.contentType();}@Overridepublic long contentLength() {return responseBody.contentLength();}@Overridepublic BufferedSource source() {if (bufferedSource == null) {bufferedSource = Okio.buffer(source(responseBody.source()));}return bufferedSource;}private Source source(Source source) {return new ForwardingSource(source) {long totalBytesRead = 0L;@Overridepublic long read(Buffer sink, long byteCount) throws IOException {long bytesRead = super.read(sink, byteCount);totalBytesRead += bytesRead != -1 ? bytesRead : 0;Log.e("download", "read: "+ (int) (totalBytesRead * 100 / responseBody.contentLength()));if (null != downloadListener) {if (bytesRead != -1) {downloadListener.onProgress((int) (totalBytesRead));}}return bytesRead;}};} }MVP下的使用邏輯
我使用的Demo是采用mvp模式寫的,所以以下邏輯需要用mvp模式視角來處理
Contract
public interface Contract {interface View{void showError(String s);void showUpdate(UpdateInfo updateInfo);void downLoading(int i);void downSuccess();void downFial();void setMax(long l);}interface Presenter{void getApkInfo();void downFile(String url);} }Activty
在用戶activity中需要處理一下操作
- 喚起更新apk請求
處理版本信息,決定是否更新
@Override public void showUpdate(final UpdateInfo updateInfo) {try {PackageManager packageManager = this.getPackageManager();PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(),0);now_version = packageInfo.versionCode;//獲取原版本號} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}if(now_version== updateInfo.getVersion()){Toast.makeText(this, "已經(jīng)是最新版本", Toast.LENGTH_SHORT).show();Log.d("版本號是", "onResponse: "+now_version);}else{AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);builder.setIcon(android.R.drawable.ic_dialog_info);builder.setTitle("請升級APP至版本" + updateInfo.getVersion());builder.setMessage(updateInfo.getDescription());builder.setCancelable(false);builder.setPositiveButton("確定", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {Log.e("MainActivity",String.valueOf(Environment.MEDIA_MOUNTED));downFile(updateInfo.getUrl());}});builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {}});builder.create().show();} }開始更新,設(shè)置進度條
/下載apk操作 public void downFile(final String url) {progressDialog = new ProgressDialog(MainActivity.this); //進度條,在下載的時候?qū)崟r更新進度,提高用戶友好度progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);progressDialog.setTitle("正在下載");progressDialog.setMessage("請稍候...");progressDialog.setProgress(0);progressDialog.show();File file = new File(getApkPath(),"ZhouzhiHouse.apk"); //獲取文件路徑presenter.downFile(url,file);Log.d("SettingActivity", "downFile: "); } //文件路徑 public String getApkPath() {String directoryPath="";if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ) {//判斷外部存儲是否可用directoryPath =getExternalFilesDir("apk").getAbsolutePath();}else{//沒外部存儲就使用內(nèi)部存儲directoryPath=getFilesDir()+File.separator+"apk";}File file = new File(directoryPath);Log.e("測試路徑",directoryPath);if(!file.exists()){//判斷文件目錄是否存在file.mkdirs();}return directoryPath; }- 設(shè)置進度條大小
更新完成,喚起安裝界面
/*** 下載成功*/ @Override public void downSuccess() {if (progressDialog != null && progressDialog.isShowing()){progressDialog.dismiss();}AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);builder.setIcon(android.R.drawable.ic_dialog_info);builder.setTitle("下載完成");builder.setMessage("是否安裝");builder.setCancelable(false);builder.setPositiveButton("確定", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {Intent intent = new Intent(Intent.ACTION_VIEW);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //android N的權(quán)限問題intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//授權(quán)讀權(quán)限Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.nongyan.xinzhihouse.fileprovider", new File(getApkPath(), "ZhouzhiHouse.apk"));//注意修改intent.setDataAndType(contentUri, "application/vnd.android.package-archive");} else {intent.setDataAndType(Uri.fromFile(new File(getApkPath(), "ZhouzhiHouse.apk")), "application/vnd.android.package-archive");intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}startActivity(intent);}});builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {}});builder.create().show(); }presenter
- 獲取最新apk信息?
這里我使用的model和下載的model不是同一個,需要自己編寫,所用接口就是上面的下載apk信息接口getApk,?
需要這部分的資料可以看基于OkHttp3的Retrofit使用實踐,里面的例子足以完成Retrofit的網(wǎng)絡(luò)請求
- 下載文件
注意
引入依賴版本的是否一致
android 不同版本的處理
文件的路徑
在build.gradle中versionCode面向開發(fā)者,versionName面向用戶
參考資料
使用Retrofit+RxJava實現(xiàn)帶進度下載文件
Android7.0應(yīng)用程序自助更新跳轉(zhuǎn)安裝界面出現(xiàn)解析包出錯
徹底搞懂Android文件存儲—內(nèi)部存儲,外部存儲以及各種存儲路徑解惑
?
總結(jié)
以上是生活随笔為你收集整理的使用Retrofit+RxJava下载文件并实现APP更新的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海纳云智慧城市白皮书 附下载
- 下一篇: Vue3 + Ant Design Vu