1. 程式人生 > >動手造輪子,用DownLoadManage封裝一個App的更新元件(相容android 6、7、8)

動手造輪子,用DownLoadManage封裝一個App的更新元件(相容android 6、7、8)

前言

android app的更新是我們在平時開發的時候常常需要遇到的問題。通常的情況是我們用第三方的網路載入庫去進行地址的下載,然後進行更新。例如okHttp、volley等,都具備了下載的功能。

但是我們在用這些第三方庫進行下載的時候可能需要做很多之外的處理,比如更新的時候處理進度。寫一個notification去提示下載顯示,這無疑讓我們在編寫程式碼的時候增加了很多不必要的麻煩。其實Android系統他已經自帶了一個下載的庫,DownloadManage,並且在裡面已經幫我們處理了很多事情,我們只需知道他的用法,再做一些封裝便可以處理我們日常中絕大多數下載的問題。

那麼我們先來講解一些常見的api用法。

DownloadManager

首先,下載嘛,當然需要網路許可權和檔案讀取寫入許可權啦,不然沒網路如何下載?下載之後的apk放哪裡?於是我們首先在清單檔案中新增許可權。

    <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情況下進行下載還是在行動網路情況下進行下載。

request.setAllowedNetworkTypes()
複製程式碼
  • DownloadManager.Request.NETWORK_WIFI: 代表在wifi情況下下載

  • DownloadManager.Request.NETWORK_MOBILE: 代表在行動網路下進行下載

如果設定的是wifi情況下下載,但是切換到了4g網路,那麼程式會自動停止,如果這時候再次切換回來,那麼又會自動下載,並且還是會自動斷點續傳。

定製化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地址

有興趣可以關注我的小專欄,學習更多職場產品思考知識:小專欄