android中DownloadManager實(shí)現(xiàn)版本更新,監(jiān)聽(tīng)下載進(jìn)度實(shí)例
DownloadManager簡(jiǎn)介
DownloadManager是Android 2.3(API level 9)用系統(tǒng)服務(wù)(Service)的方式提供了DownloadManager來(lái)處理長(zhǎng)時(shí)間的下載操作。它包含兩個(gè)靜態(tài)內(nèi)部類DownloadManager.Query(用來(lái)查詢下載信息)和DownloadManager.Request(用來(lái)請(qǐng)求一個(gè)下載)。
DownloadManager主要提供了下面幾個(gè)方法:
public long enqueue(Request request)把任務(wù)加入下載隊(duì)列并返回downloadId,以便后面用于查詢下載信息。若網(wǎng)絡(luò)不滿足條件、Sdcard掛載中、超過(guò)最大并發(fā)數(shù)等異常會(huì)等待下載,正常則直接下載。
public int remove(long… ids)刪除下載,若取消下載,會(huì)同時(shí)刪除下載文件和記錄。
public Cursor query(Query query)查詢下載信息,包括下載文件總大小,已經(jīng)下載的大小以及下載狀態(tài)等。
ContentObserver簡(jiǎn)介
public void ContentObserver(Handler handler) 所有ContentObserver的派生類都需要調(diào)用該構(gòu)造方法,參數(shù):handler Handler對(duì)象用于在主線程中修改UI。
public void onChange(boolean selfChange)當(dāng)觀察到的Uri中內(nèi)容發(fā)生變化時(shí),就會(huì)回調(diào)該方法。所有ContentObserver的派生類都需要重載該方法去處理邏輯。
觀察特定Uri的步驟如下:
1、創(chuàng)建我們特定的ContentObserver派生類,必須重載父類構(gòu)造方法,必須重載onChange()方法去處理回調(diào)后的功能實(shí)現(xiàn)。
2、為指定的Uri注冊(cè)一個(gè)ContentObserver派生類實(shí)例,當(dāng)給定的Uri發(fā)生改變時(shí),回調(diào)該實(shí)例對(duì)象去處理,調(diào)用registerContentObserver()方法去注冊(cè)內(nèi)容觀察者。
3、由于ContentObserver的生命周期不同步于Activity和Service等。因此,在不需要時(shí),需要手動(dòng)的調(diào)用unregisterContentObserver()注銷內(nèi)容觀察者。
效果圖:
一:執(zhí)行下載
下載配置
downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); downloadObserver = new DownloadChangeObserver(); //在執(zhí)行下載前注冊(cè)內(nèi)容監(jiān)聽(tīng)者 registerContentObserver(); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); /**設(shè)置用于下載時(shí)的網(wǎng)絡(luò)狀態(tài)*/ request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE); /**設(shè)置通知欄是否可見(jiàn)*/ request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); /**設(shè)置漫游狀態(tài)下是否可以下載*/ request.setAllowedOverRoaming(false); /**如果我們希望下載的文件可以被系統(tǒng)的Downloads應(yīng)用掃描到并管理, 我們需要調(diào)用Request對(duì)象的setVisibleInDownloadsUi方法,傳遞參數(shù)true.*/ request.setVisibleInDownloadsUi(true); /**設(shè)置文件保存路徑*/ request.setDestinationInExternalFilesDir(getApplicationContext(), "phoenix", "phoenix.apk"); /**將下載請(qǐng)求放入隊(duì)列, return下載任務(wù)的ID*/ downloadId = downloadManager.enqueue(request); //執(zhí)行下載任務(wù)時(shí)注冊(cè)廣播監(jiān)聽(tīng)下載成功狀態(tài) registerBroadcast();
添加權(quán)限
<!--網(wǎng)絡(luò)通信權(quán)限--> <uses-permission android:name="android.permission.INTERNET"/> <!--SD卡寫入數(shù)據(jù)權(quán)限--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!--SD卡創(chuàng)建與刪除權(quán)限--> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!--VISIBILITY_HIDDEN表示不顯示任何通知欄提示的權(quán)限--> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/> <!--DownloadManager--> <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/> 在清單文件中注冊(cè)Service <!--版本更新服務(wù)--> <service android:name="com.github.phoenix.service.DownloadService"></service>
二:監(jiān)聽(tīng)下載進(jìn)度
注冊(cè)ContentObserver
三個(gè)參數(shù)分別是所要監(jiān)聽(tīng)的Uri、false表示精確匹配此Uri,true表示可以匹配其派生的Uri、ContentObserver的派生類實(shí)例。
/** * 注冊(cè)ContentObserver */ private void registerContentObserver() { /** observer download change **/ if (downloadObserver != null) { getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true, downloadObserver); } }
查詢已下載數(shù)據(jù)大小
為了提高性能,在這里開(kāi)啟定時(shí)任務(wù),每2秒去查詢數(shù)據(jù)大小并發(fā)送到handle中更新UI。
/** * 監(jiān)聽(tīng)下載進(jìn)度 */ private class DownloadChangeObserver extends ContentObserver { public DownloadChangeObserver() { super(downLoadHandler); scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); } /** * 當(dāng)所監(jiān)聽(tīng)的Uri發(fā)生改變時(shí),就會(huì)回調(diào)此方法 * * @param selfChange 此值意義不大, 一般情況下該回調(diào)值false */ @Override public void onChange(boolean selfChange) { scheduledExecutorService.scheduleAtFixedRate(progressRunnable, 0, 2, TimeUnit.SECONDS); } } /** * 通過(guò)query查詢下載狀態(tài),包括已下載數(shù)據(jù)大小,總大小,下載狀態(tài) * * @param downloadId * @return */ private int[] getBytesAndStatus(long downloadId) { int[] bytesAndStatus = new int[]{ -1, -1, 0 }; DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor cursor = null; try { cursor = downloadManager.query(query); if (cursor != null && cursor.moveToFirst()) { //已經(jīng)下載文件大小 bytesAndStatus[0] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); //下載文件的總大小 bytesAndStatus[1] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)); //下載狀態(tài) bytesAndStatus[2] = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); } } finally { if (cursor != null) { cursor.close(); } } return bytesAndStatus; }
Activity與Service通信
既然我們要在Activity中實(shí)時(shí)更新下載進(jìn)度,那么就需要Activity綁定Service建立通信。
在Service中提供一個(gè)接口實(shí)時(shí)回調(diào)進(jìn)度值。用isBindService來(lái)標(biāo)識(shí)Activity是否綁定過(guò)Service,在調(diào)用bindService(ServiceConnection conn)方法時(shí),如果綁定成功會(huì)返回true,否則返回false,只有返回true時(shí)才可以進(jìn)行解綁,否則報(bào)錯(cuò)。
private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { DownloadService.DownloadBinder binder = (DownloadService.DownloadBinder) service; DownloadService downloadService = binder.getService(); //接口回調(diào),下載進(jìn)度 downloadService.setOnProgressListener(new DownloadService.OnProgressListener() { @Override public void onProgress(float fraction) { LogUtil.i(TAG, "下載進(jìn)度:" + fraction); bnp.setProgress((int)(fraction * 100)); //判斷是否真的下載完成進(jìn)行安裝了,以及是否注冊(cè)綁定過(guò)服務(wù) if (fraction == DownloadService.UNBIND_SERVICE && isBindService) { unbindService(conn); isBindService = false; MToast.shortToast("下載完成!"); } } }); } @Override public void onServiceDisconnected(ComponentName name) { } };
三:廣播監(jiān)聽(tīng)下載成功
下載完成,自動(dòng)安裝,記錄APK存儲(chǔ)路徑
在下載成功后把APK存儲(chǔ)路徑保存到SP中,同時(shí)關(guān)閉定時(shí)器,開(kāi)啟apk安裝界面。
/** * 安裝APK * @param context * @param apkPath 安裝包的路徑 */ public static void installApk(Context context, Uri apkPath) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); //此處因?yàn)樯舷挛氖荂ontext,所以要加此Flag,不然會(huì)報(bào)錯(cuò) intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(apkPath, "application/vnd.android.package-archive"); context.startActivity(intent); }
四:善后處理
1、關(guān)閉定時(shí)器,線程
當(dāng)收到下載完成的廣播時(shí)立即停掉定時(shí)器,取消線程。
2、解綁Service,注銷廣播,注銷ContentObserver
當(dāng)Service解綁的時(shí)候,要把監(jiān)聽(tīng)下載完成的廣播和監(jiān)聽(tīng)下載進(jìn)度的ContentObserver注銷。
3、刪除APK
當(dāng)應(yīng)用安裝成功后,再次啟動(dòng)就執(zhí)行刪除Apk操作。
/** * 刪除上次更新存儲(chǔ)在本地的apk */ private void removeOldApk() { //獲取老APK的存儲(chǔ)路徑 File fileName = new File(SPUtil.getString(Constant.SP_DOWNLOAD_PATH, "")); LogUtil.i(TAG, "老APK的存儲(chǔ)路徑 =" + SPUtil.getString(Constant.SP_DOWNLOAD_PATH, "")); if (fileName != null && fileName.exists() && fileName.isFile()) { fileName.delete(); LogUtil.i(TAG, "存儲(chǔ)器內(nèi)存在老APK,進(jìn)行刪除操作"); } }
五:具體應(yīng)用
首先上傳當(dāng)前應(yīng)用版本號(hào)給服務(wù)器,讓服務(wù)器檢查是否可以進(jìn)行版本更新;如果可以進(jìn)行版本更新,則綁定Service,開(kāi)始下載APK,下載完成直接彈出安裝界面,同時(shí)記錄APK存儲(chǔ)路徑;待下次啟動(dòng)時(shí),檢查刪除APK。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 適配Android 8.0版本更新安裝與通知欄的一些坑
- Android版本更新實(shí)例詳解
- 非常實(shí)用的小功能 Android應(yīng)用版本的更新實(shí)例
- 安卓(Android)應(yīng)用版本更新方法
- Android程序版本更新之通知欄更新下載安裝
- android 版本檢測(cè) Android程序的版本檢測(cè)與更新實(shí)現(xiàn)介紹
- 圖解Windows環(huán)境下Android Studio安裝和使用教程
- Android studio 3.0安裝配置方法圖文教程
- Android開(kāi)發(fā)環(huán)境安裝和配置圖文教程
- Android 8.0版本更新無(wú)法自動(dòng)安裝問(wèn)題的解決方法
相關(guān)文章
Android仿新浪微博發(fā)布微博界面設(shè)計(jì)(5)
這篇文章主要為大家詳細(xì)介紹了Android仿新浪微博發(fā)布微博界面設(shè)計(jì)方案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Android實(shí)現(xiàn)歡迎界面停留3秒效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)歡迎界面停留3秒效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02Android Viewpager實(shí)現(xiàn)輪播廣告圖
這篇文章主要為大家詳細(xì)介紹了Android Viewpager實(shí)現(xiàn)輪播廣告圖,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05Android 讓自定義TextView的drawableLeft與文本一起居中
本文主要介紹Android 自定義控件TextView顯示居中問(wèn)題,在開(kāi)發(fā)過(guò)程中經(jīng)常會(huì)遇到控件的重寫,這里主要介紹TextView的drawableLeft與文本一起居中的問(wèn)題2016-07-07Android 系統(tǒng)語(yǔ)言切換監(jiān)聽(tīng)和設(shè)置實(shí)例代碼
本篇文章主要介紹了Android 系統(tǒng)語(yǔ)言切換監(jiān)聽(tīng)和設(shè)置實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Android實(shí)現(xiàn)動(dòng)態(tài)自動(dòng)匹配輸入內(nèi)容
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)動(dòng)態(tài)自動(dòng)匹配輸入內(nèi)容,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Android實(shí)現(xiàn)注冊(cè)頁(yè)面
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)注冊(cè)頁(yè)面之監(jiān)聽(tīng)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Android基準(zhǔn)配置文件Baseline?Profile方案提升啟動(dòng)速度
這篇文章主要為大家介紹了Android基準(zhǔn)配置文件Baseline?Profile方案提升啟動(dòng)速度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02