安卓開發實戰之app之版本更新升級(DownloadManager和http下載)完整實現


轉載:  https://blog.csdn.net/u013278099/article/details/52692008

前言

本文將講解app的升級與更新。一般而言用戶使用App的時候升級提醒有兩種方式獲得:

  • 一種是通過應用市場 獲取
  • 一種是打開應用之后提醒用戶更新升級

而更新操作一般是在用戶點擊了升級按鈕之后開始執行的,這里的升級操作也分為兩種形式:

  • 一般升級
  • 強制升級

app升級操作:

  • 應用市場的app升級

在App Store中升級需要為App Store上傳新版App,我們在新版本完成之后都會上傳到App Store中,在審核完成之后就相當於完成了這個應用市場的發布了,也就是發布上線了。這時候如果用戶安裝了這個應用市場,那么就能看到我們的App有新版本的升級提醒了。

  • 應用內升級

除了可以在應用市場升級,我們還可以在應用內升級,在應用內升級主要是通過調用服務器端接口獲取應用的升級信息,然后通過獲取的服務器升級應用信息與本地的App版本比對,若服務器下發的最新的App版本高於本地的版本號,則說明有新版本發布,那么我們就可以執行更新操作了,否則忽略掉即可。

顯然應用市場提醒的升級不是我們的重點,本篇主要是對於app升級的場景來進行不同角度的實現,便於以后開發過程中直接拿去用就ok了。

服務器端:

  • 服務端提供一個接口,或者網址,這里提供一個網址如下:
http://192.168.191.1:8081/update
  • 1

一般作為一個安卓程序員要測試還得寫一個服務端(醉了),這里我就使用nodejs來搞一個本地的服務器來測試下app的版本更新檢驗。

  • 根據請求的結果,我這里就寫一個簡單的json
{"data":{ "appname": "hoolay.apk", "serverVersion": "1.0.2", "serverFlag": "1", "lastForce" : "1", "updateurl": "http://releases.b0.upaiyun.com/hoolay.apk", "upgradeinfo": "V1.0.2版本更新,你想不想要試一下哈!!!" }, "error_code":"200","error_msg" :"蛋疼的認識"}

 

然后我電腦上是裝了webstrom的,沒有裝也沒有關系但是必須有nodejs,現在都自帶了express,表示並沒有學過,所以簡單的寫個express_demo.js:

var express = require('express'); var app = express(); var fs = require("fs"); //此處設置為get請求,app里面直接寫 (本機ip:8081/update) app.get('/update', function (req, res) {//http://127.0.0.1:8081/update fs.readFile( __dirname + "/" + "version.json", 'utf8', function (err, data) {//讀取相同目錄下的version.json文件 console.log( data );//打印json數據 res.end( data );//把json數據response回去 }); }) var server = app.listen(8081, function () {//端口我這里寫的是8081 var host = server.address().address var port = server.address().port console.log("應用實例,訪問地址為 http://%s:%s", host, port) })

 

有webstrom的直接選中文件run就ok了,沒有直接 node express_demo.js,可以直接瀏覽器打開:http://127.0.0.1:8081/update

  • 效果如下:

這里寫圖片描述

上圖為打開瀏覽器后的顯示結果。

這里寫圖片描述

上圖為webstrom的終端顯示結果。

客戶端需要實現:

我們知道不同的需求有不同的操作方法和界面顯示:

  1. 從是否為app內部下載還是通知欄更新:

    • app內下載更新

    這時我們必須等下載安裝完全后才能進行操作,效果是這樣的:

    這里寫圖片描述

    • 通知欄下載更新

    這種情況是不在應用內更新,放在通知欄並不會影響當前app的使用,效果是這樣的:

  2. app更新分3種:強制更新,推薦更新,無需更新

    • 強制更新

      這里寫圖片描述

    • 推薦更新

      這里寫圖片描述

    • 無需更新

      這里寫圖片描述

具體思路:

  1. 實現bean用於對接后端接口實現app的更新(不寫網絡請求模擬本地數據也需要這個模型)
  2. 使用retrofit來請求版本更新接口
  3. 下載apk我們分別使用DownloadManager和普通的httpurlconnection
  4. 通過BroadcastReceiver來監聽是否下載完成

准備bean

首先我們要去解析服務端給的json,那么我們就要來創建一個bean類了,這里是嚴格根據json文件的格式來的:

package com.losileeya.appupdate.bean; /** * User: Losileeya (847457332@qq.com) * Date: 2016-09-27 * Time: 11:20 * 類描述:版本更新的實體與你服務器的字段相匹配 * @version : */ public class UpdateAppInfo { public UpdateInfo data; // 信息 public Integer error_code; // 錯誤代碼 public String error_msg; // 錯誤信息 public static class UpdateInfo{ // app名字 public String appname; //服務器版本 public String serverVersion; //服務器標志 public String serverFlag; //強制升級 public String lastForce; //app最新版本地址 public String updateurl; //升級信息 public String upgradeinfo; get... set... } get... set... }

 

網絡接口的實現

這里使用retrofit和rxjava來練筆

先加入 依賴

  compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid compile 'io.reactivex:rxjava:1.1.0' // 推薦同時加載RxJava compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit網絡處理 compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' // Retrofit的rx解析庫 compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' // Retrofit的gson庫

 

接下來網絡接口的定制:

public interface ApiService { //實際開發過程可能的接口方式 @GET("update") Observable<UpdateAppInfo> getUpdateInfo(@Query("appname") String appname, @Query("serverVersion") String appVersion); //以下方便版本更新接口測試 @GET("update") Observable<UpdateAppInfo> getUpdateInfo(); }

 

通過工廠模式來創建ApiService :

public class ServiceFactory { private static final String BASEURL="http://192.168.191.1:8081/"; public static <T> T createServiceFrom(final Class<T> serviceClass) { Retrofit adapter = new Retrofit.Builder() .baseUrl(BASEURL) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx適配器 .addConverterFactory(GsonConverterFactory.create()) // 添加Gson轉換器 .build(); return adapter.create(serviceClass); } }

 

版本檢測接口的使用:

 /** * 檢查更新 */ @SuppressWarnings("unused") public static void checkUpdate(String appCode, String curVersion,final CheckCallBack updateCallback) { ApiService apiService= ServiceFactory.createServiceFrom(ApiService.class); apiService.getUpdateInfo()//測試使用 // .apiService.getUpdateInfo(appCode, curVersion)//開發過程中可能使用的 .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<UpdateAppInfo>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(UpdateAppInfo updateAppInfo) { if (updateAppInfo.error_code == 0 || updateAppInfo.data == null || updateAppInfo.data.updateurl == null) { updateCallback.onError(); // 失敗 } else { updateCallback.onSuccess(updateAppInfo); } } }); }

 

以上就是版本更新接口的調用,具體的rxjava+retrofit請自行學習你真的會用Retrofit2嗎?Retrofit2完全教程

附上結果回調監聽:

  public interface CheckCallBack{//檢測成功或者失敗的相關接口 void onSuccess(UpdateAppInfo updateInfo); void onError(); }

 

具體使用接口的處理:

 //網絡檢查版本是否需要更新 CheckUpdateUtils.checkUpdate("apk", "1.0.0", new CheckUpdateUtils.CheckCallBack() { @Override public void onSuccess(UpdateAppInfo updateInfo) { String isForce=updateInfo.data.getLastForce();//是否需要強制更新 String downUrl= updateInfo.data.getUpdateurl();//apk下載地址 String updateinfo = updateInfo.data.getUpgradeinfo();//apk更新詳情 String appName = updateInfo.data.getAppname(); if(isForce.equals("1")&& !TextUtils.isEmpty(updateinfo)){//強制更新 forceUpdate(MainActivity.this,appName,downUrl,updateinfo); }else{//非強制更新 //正常升級 normalUpdate(MainActivity.this,appName,downUrl,updateinfo); } } @Override public void onError() { noneUpdate(MainActivity.this); } });

 

實在不想寫網絡也好,直接使用假想數據做相關操作如下:

  UpdateAppInfo.UpdateInfo  info =new UpdateAppInfo.UpdateInfo(); info.setLastForce("1"); info.setAppname("我日你"); info.setUpgradeinfo("whejjefjhrherkjreghgrjrgjjhrh"); info.setUpdateurl("http://releases.b0.upaiyun.com/hoolay.apk"); if(info.getLastForce().equals("1")){//強制更新 forceUpdate(MainActivity.this,info.getAppname(),info.getUpdateurl(),info.getUpgradeinfo()); }else{//非強制更新 //正常升級 normalUpdate(MainActivity.this,info.getAppname(),info.getUpdateurl(),info.getUpgradeinfo()); }

 

更新dialog的使用注意:

 private void forceUpdate(final Context context, final String appName, final String downUrl, final String updateinfo) { mDialog = new AlertDialog.Builder(context); mDialog.setTitle(appName+"又更新咯!"); mDialog.setMessage(updateinfo); mDialog.setPositiveButton("立即更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (!canDownloadState()) { showDownloadSetting(); return; } // DownLoadApk.download(MainActivity.this,downUrl,updateinfo,appName); AppInnerDownLoder.downLoadApk(MainActivity.this,downUrl,appName); } }).setCancelable(false).create().show(); }

 

上面以強制更新舉個例子,因為AlertDialog在不同的版本下面表現的美觀度不一致,所以我們需要

import android.support.v7.app.AlertDialog;
  • 1

然后顯然是不能按返回鍵取消的,我們需要

.setCancelable(false)

使用谷歌推薦的DownloadManager實現下載

Android自帶的DownloadManager模塊來下載,在api level 9之后,我們通過通知欄知道, 該模塊屬於系統自帶, 它已經幫我們處理了下載失敗、重新下載等功能。整個下載 過程全部交給系統負責,不需要我們過多的處理。

DownLoadManager.Query:主要用於查詢下載信息。

DownLoadManager.Request:主要用於發起一個下載請求。

先看下簡單的實現:

創建Request對象的代碼如下:

DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkurl)); //設置在什么網絡情況下進行下載 request.setAllowedNetworkTypes(Request.NETWORK_WIFI); //設置通知欄標題 request.setNotificationVisibility(Request.VISIBILITY_VISIBLE); request.setTitle("下載"); request.setDescription("apk正在下載"); request.setAllowedOverRoaming(false); //設置文件存放目錄 request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "mydown");

 

取得系統服務后,調用downloadmanager對象的enqueue方法進行下載,此方法返回一個編號用於標示此下載任務:

downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
id= downManager.enqueue(request);

 

這里我們可以看下request的一些屬性:

addRequestHeader(String header,String value):添加網絡下載請求的http頭信息
allowScanningByMediaScanner():用於設置是否允許本MediaScanner掃描。
setAllowedNetworkTypes(int flags):設置用於下載時的網絡類型,默認任何網絡都可以下載,提供的網絡常量有:NETWORK_BLUETOOTH、NETWORK_MOBILE、NETWORK_WIFI。 setAllowedOverRoaming(Boolean allowed):用於設置漫游狀態下是否可以下載 setNotificationVisibility(int visibility):用於設置下載時時候在狀態欄顯示通知信息 setTitle(CharSequence):設置Notification的title信息 setDescription(CharSequence):設置Notification的message信息 setDestinationInExternalFilesDir、setDestinationInExternalPublicDir、 setDestinationUri等方法用於設置下載文件的存放路徑,注意如果將下載文件存放在默認路徑,那么在空間不足的情況下系統會將文件刪除,所 以使用上述方法設置文件存放目錄是十分必要的。

 

具體實現思路:

  1. 我們通過downloaderManager來下載apk,並且本地保存downManager.enqueue(request)返回的id值,並且通過這個id獲取apk的下載文件路徑和下載的狀態,並且通過狀態來更新通知欄的顯示。

  2. 第一次下載成功,彈出安裝界面

    如果用戶沒有點擊安裝,而是按了返回鍵,在某個時候,又再次使用了我們的APP

    如果下載成功,則判斷本地的apk的包名是否和當前程序是相同的,並且本地apk的版本號大於當前程序的版本,如果都滿足則直接啟動安裝程序。

具體代碼實現:

文件下載管理的實現,包括創建request和加入隊列下載,通過返回的id來獲取下載路徑和下載狀態。

public class FileDownloadManager { private DownloadManager downloadManager; private Context context; private static FileDownloadManager instance; private FileDownloadManager(Context context) { downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); this.context = context.getApplicationContext(); } public static FileDownloadManager getInstance(Context context) { if (instance == null) { instance = new FileDownloadManager(context); } return instance; } /** * @param uri * @param title * @param description * @return download id */ public long startDownload(String uri, String title, String description,String appName) { DownloadManager.Request req = new DownloadManager.Request(Uri.parse(uri)); req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); //req.setAllowedOverRoaming(false); req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); //設置文件的保存的位置[三種方式] //第一種 //file:///storage/emulated/0/Android/data/your-package/files/Download/update.apk req.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, appName+".apk"); //第二種 //file:///storage/emulated/0/Download/update.apk //req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "update.apk"); //第三種 自定義文件路徑 //req.setDestinationUri() // 設置一些基本顯示信息 req.setTitle(title); req.setDescription(description); //req.setMimeType("application/vnd.android.package-archive"); return downloadManager.enqueue(req);//異步 //dm.openDownloadedFile() } /** * 獲取文件保存的路徑 * * @param downloadId an ID for the download, unique across the system. * This ID is used to make future calls related to this download. * @return file path * @see FileDownloadManager#getDownloadUri(long) */ public String getDownloadPath(long downloadId) { DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = downloadManager.query(query); if (c != null) { try { if (c.moveToFirst()) { return c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI)); } } finally { c.close(); } } return null; } /** * 獲取保存文件的地址 * * @param downloadId an ID for the download, unique across the system. * This ID is used to make future calls related to this download. * @see FileDownloadManager#getDownloadPath(long) */ public Uri getDownloadUri(long downloadId) { return downloadManager.getUriForDownloadedFile(downloadId); } public DownloadManager getDownloadManager() { return downloadManager; } /** * 獲取下載狀態 * * @param downloadId an ID for the download, unique across the system. * This ID is used to make future calls related to this download. * @return int * @see DownloadManager#STATUS_PENDING * @see DownloadManager#STATUS_PAUSED * @see DownloadManager#STATUS_RUNNING * @see DownloadManager#STATUS_SUCCESSFUL * @see DownloadManager#STATUS_FAILED */ public int getDownloadStatus(long downloadId) { DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = downloadManager.query(query); if (c != null) { try { if (c.moveToFirst()) { return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); } } finally { c.close(); } } return -1; } }

 

app的檢測安裝的實現:

public class DownLoadApk { public static final String TAG = DownLoadApk.class.getSimpleName(); public static void download(Context context, String url, String title,final String appName) { // 獲取存儲ID SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); long downloadId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L); if (downloadId != -1L) { FileDownloadManager fdm = FileDownloadManager.getInstance(context); int status = fdm.getDownloadStatus(downloadId); if (status == DownloadManager.STATUS_SUCCESSFUL) { //啟動更新界面 Uri uri = fdm.getDownloadUri(downloadId); if (uri != null) { if (compare(getApkInfo(context, uri.getPath()), context)) { startInstall(context, uri); return; } else { fdm.getDownloadManager().remove(downloadId); } } start(context, url, title,appName); } else if (status == DownloadManager.STATUS_FAILED) { start(context, url, title,appName); } else { Log.d(TAG, "apk is already downloading"); } } else { start(context, url, title,appName); } } private static void start(Context context, String url, String title,String appName) { long id = FileDownloadManager.getInstance(context).startDownload(url, title, "下載完成后點擊打開",appName); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); sp.edit().putLong(DownloadManager.EXTRA_DOWNLOAD_ID,id).commit(); Log.d(TAG, "apk start download " + id); } public static void startInstall(Context context, Uri uri) { Intent install = new Intent(Intent.ACTION_VIEW); install.setDataAndType(uri, "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install); } /** * 獲取apk程序信息[packageName,versionName...] * * @param context Context * @param path apk path */ private static PackageInfo getApkInfo(Context context, String path) { PackageManager pm = context.getPackageManager(); PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES); if (info != null) { return info; } return null; } /** * 下載的apk和當前程序版本比較 * * @param apkInfo apk file's packageInfo * @param context Context * @return 如果當前應用版本小於apk的版本則返回true */ private static boolean compare(PackageInfo apkInfo, Context context) { if (apkInfo == null) { return false; } String localPackage = context.getPackageName(); if (apkInfo.packageName.equals(localPackage)) { try { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0); if (apkInfo.versionCode > packageInfo.versionCode) { return true; } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } return false; } }

上面的代碼可知:我們通過獲取當前app的信息來比較是否需要下載和是否立即安裝。第一次下載把downloadId保存到本地,用戶下次進來的時候,取出保存的downloadId,然后通過downloadId來獲取下載的狀態信息。如果下載失敗,則重新下載並且把downloadId存起來。如果下載成功,則判斷本地的apk的包名是否和當前程序是相同的,並且本地apk的版本號大於當前程序的版本,如果都滿足則直接啟動安裝程序。

監聽app是否安裝完成

public class ApkInstallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){ long downloadApkId =intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); installApk(context, downloadApkId); } } /** * 安裝apk */ private void installApk(Context context,long downloadApkId) { // 獲取存儲ID SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); long downId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L); if(downloadApkId == downId){ DownloadManager downManager= (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); Uri downloadFileUri = downManager.getUriForDownloadedFile(downloadApkId); if (downloadFileUri != null) { Intent install= new Intent(Intent.ACTION_VIEW); install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install); }else{ Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show(); } } } } 

 

DownloadManager下載完成后會發出一個廣播 android.intent.action.DOWNLOAD_COMPLETE 新建一個廣播接收者即可:

清單配置:

先添加網絡下載的權限:

 <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 

 

再添加靜態廣播:

 <receiver android:name=".ApkInstallReceiver"> <intent-filter> <action android:name="android.intent.action.DOWNLOAD_COMPLETE" /> </intent-filter> </receiver>

 

使用HttpUrlConnection下載

這種情況下載的話我們就不需要考慮id的問題,因為是直接在項目中下載,所以我們就是一個網絡下載的過程,並且使用ProgressDialog顯示下載信息及進度更新就ok了。

public class AppInnerDownLoder { public final static String SD_FOLDER = Environment.getExternalStorageDirectory()+ "/VersionChecker/"; private static final String TAG = AppInnerDownLoder.class.getSimpleName(); /** * 從服務器中下載APK */ @SuppressWarnings("unused") public static void downLoadApk(final Context mContext,final String downURL,final String appName ) { final ProgressDialog pd; // 進度條對話框 pd = new ProgressDialog(mContext); pd.setCancelable(false);// 必須一直下載完,不可取消 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setMessage("正在下載安裝包,請稍后"); pd.setTitle("版本升級"); pd.show(); new Thread() { @Override public void run() { try { File file = downloadFile(downURL,appName, pd); sleep(3000); installApk(mContext, file); // 結束掉進度條對話框 pd.dismiss(); } catch (Exception e) { pd.dismiss(); } } }.start(); } /** * 從服務器下載最新更新文件 * * @param path * 下載路徑 * @param pd * 進度條 * @return * @throws Exception */ private static File downloadFile(String path,String appName ,ProgressDialog pd) throws Exception { // 如果相等的話表示當前的sdcard掛載在手機上並且是可用的 if (Environment.MEDIA_MOUNTED.equals(Environment .getExternalStorageState())) { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); // 獲取到文件的大小 pd.setMax(conn.getContentLength()); InputStream is = conn.getInputStream(); String fileName = SD_FOLDER + appName+".apk"; File file = new File(fileName); // 目錄不存在創建目錄 if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(file); BufferedInputStream bis = new BufferedInputStream(is); byte[] buffer = new byte[1024]; int len; int total = 0; while ((len = bis.read(buffer)) != -1) { fos.write(buffer, 0, len); total += len; // 獲取當前下載量 pd.setProgress(total); } fos.close(); bis.close(); is.close(); return file; } else { throw new IOException("未發現有SD卡"); } } /** * 安裝apk */ private static void installApk(Context mContext, File file) { Uri fileUri = Uri.fromFile(file); Intent it = new Intent(); it.setAction(Intent.ACTION_VIEW); it.setDataAndType(fileUri, "application/vnd.android.package-archive"); it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 防止打不開應用 mContext.startActivity(it); } /** * 獲取應用程序版本(versionName) * * @return 當前應用的版本號 */ private static double getLocalVersion(Context context) { PackageManager manager = context.getPackageManager(); PackageInfo info = null; try { info = manager.getPackageInfo(context.getPackageName(), 0); } catch (NameNotFoundException e) { Log.e(TAG, "獲取應用程序版本失敗,原因:" + e.getMessage()); return 0.0; } return Double.valueOf(info.versionName); } /** * byte(字節)根據長度轉成kb(千字節)和mb(兆字節) * * @param bytes * @return */ public static String bytes2kb(long bytes) { BigDecimal filesize = new BigDecimal(bytes); BigDecimal megabyte = new BigDecimal(1024 * 1024); float returnValue = filesize.divide(megabyte, 2, BigDecimal.ROUND_UP) .floatValue(); if (returnValue > 1) return (returnValue + "MB"); BigDecimal kilobyte = new BigDecimal(1024); returnValue = filesize.divide(kilobyte, 2, BigDecimal.ROUND_UP) .floatValue(); return (returnValue + "KB"); } }

 

 

基本上具體的代碼就寫完了,但是說如果停止了下載管理程序 調用dm.enqueue(req);就會上面的錯誤,從而程序閃退.

所以在使用該組件的時候,需要判斷該組件是否可用:

    private boolean canDownloadState() { try { int state = this.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads"); if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { return false; } } catch (Exception e) { e.printStackTrace(); return false; } return true; }

 

可以通過如下代碼進入 啟用/禁用 下載管理 界面:

 String packageName = "com.android.providers.downloads"; Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + packageName)); startActivity(intent);

 

總結

本文意在講解app的更新邏輯以及不同的表現形式的處理附帶的介紹了使用nodejs寫一個簡單的api接口,重點是如何使用DownloadManager來實現apk的下載更新安裝,順帶講一下retrofit+rxjava的使用以及如何監聽app是否下載完成。

DownloadManager的使用概括:

  1. 構建下載請求:

    new DownloadManager.Request(url)
    • 1
  2. 設置請求屬性

    request.setXXX()
    • 1
  3. 調用downloadmanager對象的enqueue方法進行下載,此方法返回一個編號用於標示此下載任務:

    downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
    
    id= downManager.enqueue(request);
    • 1
    • 2
    • 3
  4. DownManager會對所有的現在任務進行保存管理,那么我們如何獲取這些信息呢?這個時候就要用到DownManager.Query對象,通過此對象,我們可以查詢所有下載任務信息。

    setFilterById(long… ids):根據任務編號查詢下載任務信息

    setFilterByStatus(int flags):根據下載狀態查詢下載任務

  5. 如果想取消下載,則可以調用remove方法完成,此方法可以將下載任務和已經下載的文件同時刪除:

    downManager.remove(id);
    • 1

好了具體的都講的差不多了,本文以同步到我的github

demo 傳送門:AppUpdate.rar

后記

鑒於版本更新沒有對6.0的權限和7.0的FileProvider做適配,導致6.0和7.0的安裝失敗或者7.0直接crash,本文將不再解釋,自行處理這里提供一個已經適配的demo下載 

轉:https://blog.csdn.net/u013278099/article/details/52692008


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM