使用Retrofit+RxJava下載檔案並實現APP更新
阿新 • • 發佈:2018-12-20
後臺介面
這次就不能再像上一年那樣通過一個txt檔案來儲存apk資訊了,我們要做的就是請後臺吃頓飯,寫一下以下介面
- 上傳介面putApk
這個介面用於方便我們上傳新版本,可暫時配合postman使用
- 獲取apk介面 getApk
我們通過當前版本號和version的對比判斷是否需要更新
Gradle配置
//retrofit implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'io.reactivex:rxandroid:1.1.0'//處理網路請求在android中執行緒排程問題 implementation 'com.squareup.retrofit2:converter-gson:2.4.0'//gson轉換 implementation 'com.squareup.retrofit2:adapter-rxjava:2.4.0' implementation 'com.trello.rxlifecycle2:rxlifecycle:2.2.1'//解決RxJava記憶體洩漏 implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1' implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'//使用攔截器
許可權設定
- 新增讀寫,網路許可權
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
- 在application內新增
<provider android:name="android.support.v4.content.FileProvider" android:authorities="專案包名.fileprovider" android:grantUriPermissions="true" android:exported="false" > <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
- 在res中新建xml資原始檔夾並建立file_paths檔案
<?xml version="1.0" encoding="utf-8"?> <paths> <!--外部儲存路徑--> <external-path path="Android/data/com.nongyan.xinzhihouse/" name="files_root" /> <!--內部儲存路徑--> <files-path name="Android/data/com.nongyan.xinzhihouse/" path="files_root"> </files-path> </paths>
這兩步是因為Android 7.0 以上google引入私有目錄被限制訪問和StrictMode API,也就是說在 /Android /data我們是有許可權訪問的,但接下的檔案我們就需要授權申請了
Retrofit和RxJava類與方法 該模組內容參考https://blog.csdn.net/jiashuai94/article/details/78775314
service 介面定義
public interface Service {
@Streaming
@GET
Observable<ResponseBody> download(@Url String url);
}
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>() {
@Override
public InputStream call(ResponseBody responseBody) {
return responseBody.byteStream();
}
})
.observeOn(Schedulers.computation()) // 用於計算任務
.doOnNext(new Action1<InputStream>() {
@Override
public 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;
}
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
return response.newBuilder().body(
new JsResponseBody(response.body(), downloadListener)).build();
}
}
下載監聽回撥
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庫中的輸入流,這裡就當作inputStream來使用。
private BufferedSource bufferedSource;
public JsResponseBody(ResponseBody responseBody, JsDownloadListener downloadListener) {
this.responseBody = responseBody;
this.downloadListener = downloadListener;
downloadListener.onStartDownload(responseBody.contentLength());
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public 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;
@Override
public 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請求
private void updateApk() {
if (Build.VERSION.SDK_INT >= 23) {//如果是6.0以上的
int REQUEST_CODE_CONTACT = 101;
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
//驗證是否許可許可權
for (String str : permissions) {
if (MainActivity.this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
//申請許可權
MainActivity.this.requestPermissions(permissions, REQUEST_CODE_CONTACT);
return;
}
}
}
presenter.getApkInfo();
}
處理版本資訊,決定是否更新
@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, "已經是最新版本", 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.e("MainActivity",String.valueOf(Environment.MEDIA_MOUNTED));
downFile(updateInfo.getUrl());
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create().show();
}
}
開始更新,設定進度條
/下載apk操作
public void downFile(final String url) {
progressDialog = new ProgressDialog(MainActivity.this); //進度條,在下載的時候實時更新進度,提高使用者友好度
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{//沒外部儲存就使用內部儲存
directoryPath=getFilesDir()+File.separator+"apk";
}
File file = new File(directoryPath);
Log.e("測試路徑",directoryPath);
if(!file.exists()){//判斷檔案目錄是否存在
file.mkdirs();
}
return directoryPath;
}
- 設定進度條大小
/**
* 進度條實時更新
* @param i
*/
@Override
public void downLoading(final int i) {
progressDialog.setProgress(i);
}
更新完成,喚起安裝介面
/**
* 下載成功
*/
@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() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //android N的許可權問題
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//授權讀許可權
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() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create().show();
}
presenter
- 獲取最新apk資訊 這裡我使用的model和下載的model不是同一個,需要自己編寫,所用介面就是上面的下載apk資訊介面getApk, 需要這部分的資料可以看基於OkHttp3的Retrofit使用實踐,裡面的例子足以完成Retrofit的網路請求
@Override
public void getApkInfo() {
RetrofitModel retrofitModel = new RetrofitModel();
retrofitModel.getApkInfo(new MainListener<UpdateInfo>() {
@Override
public void onSuccess(UpdateInfo updateInfo) {
view.showUpdate(updateInfo);
}
@Override
public void onfail(String s) {
view.showError(s);
}
});
}
- 下載檔案
@Override
public void downFile(String url) {
final DownloadUtils downloadUtils = new DownloadUtils(Api.BASE_URL, new JsDownloadListener() {
@Override
public void onStartDownload(long length) {
view.setMax(length);
}
@Override
public void onProgress(int progress) {
view.downLoading(progress);
}
@Override
public void onFinishDownload() {
view.downSuccess();
}
@Override
public void onFail(String errorInfo) {
view.showError(errorInfo);
}
});
File file = new File(view.getApkPath(),"ZhouzhiHouse.apk");
downloadUtils.download(url, file, new Subscriber() {
@Override
public void onCompleted() {
view.downSuccess();
}
@Override
public void onError(Throwable e) {
view.showError("onError:"+e);
}
@Override
public void onNext(Object o) {
}
});
}