動手造輪子,用DownLoadManage封裝一個App的更新組件(兼容android 六、七、8)

前言

android app的更新是咱們在平時開發的時候經常須要遇到的問題。一般的狀況是咱們用第三方的網絡加載庫去進行地址的下載,而後進行更新。例如okHttp、volley等,都具有了下載的功能。java

可是咱們在用這些第三方庫進行下載的時候可能須要作不少以外的處理,好比更新的時候處理進度。寫一個notification去提示下載顯示,這無疑讓咱們在編寫代碼的時候增長了不少沒必要要的麻煩。其實Android系統他已經自帶了一個下載的庫,DownloadManage,而且在裏面已經幫咱們處理了不少事情,咱們只需知道他的用法,再作一些封裝即可以處理咱們平常中絕大多數下載的問題。android

那麼咱們先來說解一些常見的api用法。git

DownloadManager

首先,下載嘛,固然須要網絡權限和文件讀取寫入權限啦,否則沒網絡如何下載?下載以後的apk放哪裏?因而咱們首先在清單文件中添加權限。github

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    
複製代碼

以後是實例化這個DownloadManager類,而且傳入下載的地址。設計模式

DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
複製代碼

在DownloadManager內部會判斷手機所處在的環境是什麼,也就是說,咱們能夠設置是在wifi狀況下進行下載仍是在移動網絡狀況下進行下載。api

request.setAllowedNetworkTypes()
複製代碼
  • DownloadManager.Request.NETWORK_WIFI: 表明在wifi狀況下下載緩存

  • DownloadManager.Request.NETWORK_MOBILE: 表明在移動網絡下進行下載安全

若是設置的是wifi狀況下下載,可是切換到了4g網絡,那麼程序會自動中止,若是這時候再次切換回來,那麼又會自動下載,而且仍是會自動斷點續傳。bash

定製化notification:網絡

在點擊進行下載的時候,通常在手機下拉框中會出現一個notification來顯示下載進行,DownloadManager在這方面作得很智能,幾行代碼就能夠直接搞定這個複雜的功能。

//下載時顯示notification
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        //添加描述信息
        request.setDescription(description);
複製代碼
  • VISIBILTY_HIDDEN: Notification:將不會顯示,若是設置該屬性的話,必需要添加權限。Android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. VISIBILITY_VISIBLE: Notification顯示,可是隻是在下載任務執行的過程當中顯示,下載完成自動消失。(默認值)

  • VISIBILITY_VISIBLE_NOTIFY_COMPLETED : Notification顯示,下載進行時,和完成以後都會顯示。

  • VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION :只有當任務完成時,Notification纔會顯示。

以後即可以設置存儲地址

//file:///storage/emulated/0/Download/downloadName.apk
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, downloadName +".apk");
複製代碼

最後將請求加入隊列,即可以開始進行下載了。

request.setMimeType("application/vnd.android.package-archive");
        DownloadManager systemService = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        systemService.enqueue(request);
複製代碼

這時,就能夠看到開始進行了下載:

那麼如何才知道下載完成,來進行安裝呢?在DownloadManager內部在下載完成以後會發送一個廣播告訴下載完成。DownloadManager.ACTION_DOWNLOAD_COMPLETE

因而咱們即可以寫一個broadcast來進行接收廣播,同時處理安裝事件。

class DownloadCompleteBroadcast extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){

                //TODO...
            }
        }
    }
複製代碼

兼容處理:

在安裝的時候就開始體現了版本的差別了,須要開始作兼容。咱們在6.0如下版本,能夠直接使用如下代碼進行安裝便可。

Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.parse("file:///storage/emulated/0/Download/" + downloadName +".apk"), "application/vnd.android.package-archive");
            //爲這個新apk開啓一個新的activity棧
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //開始安裝
            startActivity(intent);
複製代碼

6.0兼容:

在6.0時候引入的動態權限問題。也就是說,咱們在清單文件設置了權限問題,可是在須要一些比較私密的權限的時候,必須由用戶去進行選擇,若是不在處理這些權限的時候讓用戶去選擇,那麼程序一定會奔潰。這裏推薦一個好用的動態權限庫,RxPersmissions。這個庫運用了RxJava的鏈式思想,來處理動態權限問題。github地址:RxPermissions。使用起來也很是簡單,直接在進行下載的時候給出權限受權提示便可。

RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    .subscribe(new Consumer<Permission>() {
                        @Override
                        public void accept(Permission permission) throws Exception {
                            if (permission.granted){
                                //TODO...
                                
                            }else {
                                Toast.makeText(context, "權限未開啓", Toast.LENGTH_SHORT).show();
                            }
                        }
                    });
複製代碼

7.0兼容:

從文檔裏知道,Android 7 開始增長安全性,文件私有化,而須要共享文件給其餘程序,例如APK安裝程序,須要經過FileProvider配置共享文件,配置表是基於XML文件實現,而後經過Content URI攜帶配置文件xml來共享文件.

實現配置FileProvider 須要兩步: 第一步: 須要配置AndroidManifest.xml清單.

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.qubin.downloadmanager"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>
複製代碼

第二步:創建文件 res/xml/file_paths.xml.

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <paths>  
        <!--  
        files-path:          該方式提供在應用的內部存儲區的文件/子目錄的文件。  
                              它對應Context.getFilesDir返回的路徑:eg:」/data/data/com.***.***/files」。  

        cache-path:          該方式提供在應用的內部存儲區的緩存子目錄的文件。  
                              它對應Context.getCacheDir返回的路:eg:「/data/data/com.***.***/cache」;  

        external-path:       該方式提供在外部存儲區域根目錄下的文件。  
                              它對應Environment.getExternalStorageDirectory返回的路徑

        external-files-path:  Context.getExternalFilesDir(null)

        external-cache-path: Context.getExternalCacheDir(String)
        -->  
        <external-path name="external" path="" />  
    </paths>  
</resources>

複製代碼

而其中的 path=""是表明根目錄,也就是向共享的應用程序共享根目錄以及其子目錄的任何一個文件.理論上說假如共享程序是惡意程序,那它即可以獲取你的應用的全部共享文件信息.

最後準備好上面兩步即可以安裝文件

File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "/" + downloadName +".apk");
            //參數1 上下文, 參數2 Provider主機地址 和配置文件中保持一致 參數3 共享的文件
            Uri apkUri = FileProvider.getUriForFile(context, "com.qubin.downloadmanager", file);

            Intent intent = new Intent(Intent.ACTION_VIEW);
            // 因爲沒有在Activity環境下啓動Activity,設置下面的標籤
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //添加這一句表示對目標應用臨時受權該Uri所表明的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
            startActivity(intent);
複製代碼

8.0兼容:

Android 8到時有了什麼改變以至安裝apk的方法有很大改變呢?

在2017年8月29號的谷歌開發者博客中寫道 <<在 Android O 中更安全地獲取應用>>新的安裝未知應用的,Android O 禁用了老是安裝未知應用的選擇,改成安裝未知應用時提出設置的提示,減小惡意應用經過虛假的安裝界面欺騙用戶行爲. 因此開發者須要調整AndroidManifest文件裏的權限,增長 REQUEST_INSTALL_PACKAGES權限.

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
複製代碼

谷歌建議是經過PackageManager canRequestPackageInstalls() 的API,查詢此權限的狀態,而後使用使用 ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent 操做。

Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);

startActivityForResult(intent, RESULT_CODE);

複製代碼

可是我不建議這樣使用,由於使用 ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent 操做後會跳到全部應用列表,而後從衆多的應用裏選擇對應的APP的選擇進入再打開權限,這樣的用戶體驗很差。能夠直接等到安裝的時候點擊跳轉開發這個權限便可。

封裝

好了,有了以上的一些操做,以後我利用了builder模式直接進行了一層封裝操做,即可以方便咱們使用這個下載的方法了。具體的builder寫法不難,這裏不作過多的說明,直接看代碼就能看懂。

另外,咱們在使用更新的時候通常來講,會先進行網絡請求接口,拿到更新提示文案,彈出一個dialog彈窗,點擊下載以後即可以開始下載。這裏我也寫了一個通用的dialog,經過這個即可以進行操做了。也是利用了builder設計模式。若是對這塊不懂,能夠參考一下我寫的另外一篇文章。動手造輪子——用Builder模式擼一個通用版本的Dialog

這裏只是寫了一個大概的界面,具體的界面操做,能夠本身去根據這個demo進行改造。

在咱們使用這個dialog:

commonDialog = new CommonDialog.Builder(MainActivity.this)
                        .view(R.layout.dialog) //佈局文件
                        .style(R.style.Dialog) //樣式透明
                        .setMessage(R.id.txt_sure,"開始更新") //更新按鈕文字
                        .setMessage(R.id.txt_cancel,"取消更新") //取消按鈕文字
                        .addViewOnClick(R.id.txt_sure, new View.OnClickListener() { //點擊開始更新按鈕點擊事件
                            @Override
                            public void onClick(View v) {

                                Toast.makeText(MainActivity.this, "開始下載", Toast.LENGTH_SHORT).show();
                                commonDialog.dismiss();
                            }
                        })
                        .addViewOnClick(R.id.txt_cancel, new View.OnClickListener() { //取消按鈕點擊事件
                            @Override
                            public void onClick(View v) {
                                commonDialog.dismiss();
                            }
                        })
                        .build();

                commonDialog.show();
複製代碼

在進行更新時,寫下一下一行代碼即可以開始進行更新了

new DownLoadBuilder.Builder(MainActivity.this)
                                        .addUrl(url)
                                        .isWiFi(true)
                                        .addDownLoadName(apkName)
                                        .addDscription("開始下載")
                                        .builder();
複製代碼

是否是以爲很方便?不要忘記下完寫一個廣播來接收下載完成事件。

全部代碼都放到了github上,若是須要使用以上兩個方法,只須要將 DownLoadBuilderCommonDialog 這兩個類引入到本身項目中便可操做。

若是以爲能夠,歡迎start。

代碼dmeo的github地址

有興趣能夠關注個人小專欄,學習更多職場產品思考知識:小專欄

相關文章
相關標籤/搜索