安卓開發實戰之app之版本更新升級(DownloadManager和http下載)完整實現
前言
本文將講解app的升級與更新。一般而言使用者使用App的時候升級提醒有兩種方式獲得:
- 一種是通過應用市場 獲取
- 一種是開啟應用之後提醒使用者更新升級
而更新操作一般是在使用者點選了升級按鈕之後開始執行的,這裡的升級操作也分為兩種形式:
- 一般升級
- 強制升級
app升級操作:
- 應用市場的app升級
在App Store中升級需要為App Store上傳新版App,我們在新版本完成之後都會上傳到App Store中,在稽核完成之後就相當於完成了這個應用市場的釋出了,也就是釋出上線了。這時候如果使用者安裝了這個應用市場,那麼就能看到我們的App有新版本的升級提醒了。
- 應用內升級
除了可以在應用市場升級,我們還可以在應用內升級,在應用內升級主要是通過呼叫伺服器端介面獲取應用的升級資訊,然後通過獲取的伺服器升級應用資訊與本地的App版本比對,若伺服器下發的最新的App版本高於本地的版本號,則說明有新版本釋出,那麼我們就可以執行更新操作了,否則忽略掉即可。
顯然應用市場提醒的升級不是我們的重點,本篇主要是對於app升級的場景來進行不同角度的實現,便於以後開發過程中直接拿去用就ok了。
伺服器端:
- 服務端提供一個介面,或者網址,這裡提供一個網址如下:
http://192.168.191.1:8081/update
一般作為一個安卓程式設計師要測試還得寫一個服務端(醉了),這裡我就使用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的終端顯示結果。
客戶端需要實現:
我們知道不同的需求有不同的操作方法和介面顯示:
從是否為app內部下載還是通知欄更新:
- app內下載更新
這時我們必須等下載安裝完全後才能進行操作,效果是這樣的:
- 通知欄下載更新
這種情況是不在應用內更新,放在通知欄並不會影響當前app的使用,效果是這樣的:
app更新分3種:強制更新,推薦更新,無需更新
強制更新
推薦更新
無需更新
具體思路:
- 實現bean用於對接後端介面實現app的更新(不寫網路請求模擬本地資料也需要這個模型)
- 使用retrofit來請求版本更新介面
- 下載apk我們分別使用DownloadManager和普通的httpurlconnection
- 通過BroadcastReceiver來監聽是否下載完成
準備bean
首先我們要去解析服務端給的json,那麼我們就要來建立一個bean類了,這裡是嚴格根據json檔案的格式來的:
package com.losileeya.appupdate.bean;
/**
* User: Losileeya ([email protected])
* 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);
}
}
});
}
附上結果回撥監聽:
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;
然後顯然是不能按返回鍵取消的,我們需要
.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等方法用於設定下載檔案的存放路徑,注意如果將下載檔案存放在預設路徑,那麼在空間不足的情況下系統會將檔案刪除,所 以使用上述方法設定檔案存放目錄是十分必要的。
具體實現思路:
我們通過downloaderManager來下載apk,並且本地儲存downManager.enqueue(request)返回的id值,並且通過這個id獲取apk的下載檔案路徑和下載的狀態,並且通過狀態來更新通知欄的顯示。
第一次下載成功,彈出安裝介面
如果使用者沒有點選安裝,而是按了返回鍵,在某個時候,又再次使用了我們的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的使用概括:
構建下載請求:
new DownloadManager.Request(url)
設定請求屬性
request.setXXX()
呼叫downloadmanager物件的enqueue方法進行下載,此方法返回一個編號用於標示此下載任務:
downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE); id= downManager.enqueue(request);
DownManager會對所有的現在任務進行儲存管理,那麼我們如何獲取這些資訊呢?這個時候就要用到DownManager.Query物件,通過此物件,我們可以查詢所有下載任務資訊。
setFilterById(long… ids):根據任務編號查詢下載任務資訊
setFilterByStatus(int flags):根據下載狀態查詢下載任務
如果想取消下載,則可以呼叫remove方法完成,此方法可以將下載任務和已經下載的檔案同時刪除:
downManager.remove(id);
好了具體的都講的差不多了,本文以同步到我的github
後記
鑑於版本更新沒有對6.0的許可權和7.0的FileProvider做適配,導致6.0和7.0的安裝失敗或者7.0直接crash,本文將不再解釋,自行處理這裡提供一個已經適配的demo下載