[轉]使用Download Provider 進行檔案下載全攻略

l_serein發表於2013-05-19

Android內部提供了一個DownloadProvider,是一個非常完整的下載工具,提供了很好的外部介面可以被其他應用程式呼叫,來完成下載工作。同時也提供和很好的下載、通知、儲存等機制。
在Android的Browser等工具裡面都用到了這個DownloadProvider。
但是很遺憾的是,這個DownloadProvider不對app開發人員開放,只作為內部使用。
我們現在去探究如何將DownloadProvider拿來給自己用。
讓我們先找到DownloadProvider不能用的原因:
先找到它的原始碼,在這個位置:/packages/providers/DownloadProvider
開啟AndroidManifest.xml檔案,裡面有幾個自定義的許可權



  1.     <!-- Allows access to the Download Manager -->
  2.     <permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"
  3.         android:label="@string/permlab_downloadManager"
  4.         android:description="@string/permdesc_downloadManager"
  5.         android:protectionLevel="signatureOrSystem" />
  6.     <!-- Allows advanced access to the Download Manager -->
  7.     <permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED"
  8.         android:label="@string/permlab_downloadManagerAdvanced"
  9.         android:description="@string/permdesc_downloadManagerAdvanced"
  10.         android:protectionLevel="signatureOrSystem" />
  11.     <!-- Allows filesystem access to /cache -->
  12.     <permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
  13.         android:label="@string/permlab_cacheFilesystem"
  14.         android:description="@string/permdesc_cacheFilesystem"
  15.         android:protectionLevel="signature" />
  16.     <!-- Allows to send download completed intents -->
  17.     <permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"
  18.         android:label="@string/permlab_downloadCompletedIntent"
  19.         android:description="@string/permdesc_downloadCompletedIntent"
  20.         android:protectionLevel="signature" />
複製程式碼



這幾個許可權裡面都是android:protectionLevel="signatureOrSystem" 或者   android:protectionLevel="signature", 這個意思是隻有你的app擁有system許可權,或者和系統一樣的簽名,才能呼叫它。
這裡是問題的關鍵。那我們有兩種思路:
一種思路是:將這個protectionLevel改成normal,重新編譯DownloadProvider工程,讓其他app可以直接呼叫。
另一種思路是:將你自己的app弄成system許可權或者和系統一樣的簽名。
前一種思路已經完全成功了,第二種思路驗證了一部分。
先看第一種思路的辦法:
1)先將上面幾個許可權都改成:android:protectionLevel="normal"
2)重新編譯DownloadProvider
   mmm packages/providers/DownloadProvider
3) 將編譯後的apk替換現有的apk
   因為DownloadProvider.apk是系統app,你可以先給/system以root許可權,然後將這個app替換掉。 (作為一個使用者app安裝也可以,不過重啟以後就沒有了)
   使用類似 # mount -t ubifs -o remount ubi0:system /system   或者  # mount -o remount ubi0:system /system  給/system rw許可權。
   然後通過adb push 將DownloadProvider.apk push到 /system/app/下。系統會自動替換這個app。
4)寫一個工程來使用DownloadProvider.
   直接貼原始碼了:

  1. package com.xxxx.usedownload;

  2. import java.io.FileNotFoundException;
  3. import java.NET.URI;
  4. import android.app.Activity;
  5. import android.content.ContentResolver;
  6. import android.content.ContentValues;
  7. import android.content.Context;
  8. import android.net.Uri;
  9. import android.os.Bundle;
  10. import android.webkit.URLUtil;
  11. /**
  12. * @author lixinso
  13. * 使用DownloadProvider
  14. */
  15. public class DownloadActivity extends Activity {
  16.      @Override
  17.     public void onCreate(Bundle savedInstanceState) {
  18.         super.onCreate(savedInstanceState);
  19.         setContentView(R.layout.main);
  20.         
  21.         //String url = "http://192.168.200.76:8080/webserver/dancing-skeleton.3gp";
  22.         String contentDisposition = "attachment; filename=\"dancing-skeleton.3gp\"";
  23.         String mimetype = "video/3GPP";
  24.         
  25.         String filename = URLUtil.guessFileName(url,contentDisposition, mimetype);
  26.         
  27.         URI uri = null;
  28.          
  29.         try {
  30.             // Undo the percent-encoding that KURL may have done.
  31.             String newUrl = new String(URLUtil.decode(url.getBytes()));
  32.             // Parse the url into pieces
  33.             WebAddress w = new WebAddress(newUrl);
  34.             String frag = null;
  35.             String query = null;
  36.             String path = w.mPath;
  37.             // Break the path into path, query, and fragment
  38.             if (path.length() > 0) {
  39.                 // Strip the fragment
  40.                 int idx = path.lastIndexOf('#');
  41.                 if (idx != -1) {
  42.                     frag = path.substring(idx + 1);
  43.                     path = path.substring(0, idx);
  44.                 }
  45.                 idx = path.lastIndexOf('?');
  46.                 if (idx != -1) {
  47.                     query = path.substring(idx + 1);
  48.                     path = path.substring(0, idx);
  49.                 }
  50.             }
  51.             uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
  52.                     query, frag);
  53.         } catch (Exception e) {
  54.             //Log.e(LOGTAG, "Could not parse url for download: " + url, e);
  55.             return;
  56.         }
  57.         
  58.         ContentValues values = new ContentValues();
  59.         values.put("uri", uri.toString());
  60.         values.put("useragent", "Mozilla/5.0 (linux; U; Android 1.5; en-us; SDK Build/CUPCAKE) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1");
  61.         values.put("notificationpackage", getPackageName());
  62.         values.put("notificationclass", "HelloWorld");
  63.         values.put("visibility", 1);
  64.         values.put("mimetype", mimetype);
  65.         values.put("hint", filename);
  66.         values.put("description", uri.getHost());
  67.         values.put("total_bytes", 1349528);
  68.         values.put("destination", 1);
  69.         
  70.         
  71.          
  72.         //這些引數參考:DownloadProvider工程中的:Helpers.java
  73.         //public static DownloadFileInfo generateSaveFile(
  74.         //        Context context,
  75.         //      String url,
  76.         //        String hint,
  77.         //        String contentDisposition,
  78.         //        String contentLocation,
  79.         //        String mimeType,
  80.         //        int destination,
  81.         //        int contentLength) throws FileNotFoundException {
  82.         //以及:  framework裡的Downloads.java;
  83.         
  84.         
  85.         ContentResolver mResolver = getContentResolver();
  86.         mResolver.insert(Uri.parse("content://downloads/download"), values);
  87.         
  88.     }
  89. }
  90. AndroidManifest.xml
  91. <?xml version="1.0" encoding="utf-8"?>
  92. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  93.       package="com.xxxx.usedownload"
  94.       android:versionCode="1"
  95.       android:versionName="1.0">
  96.     <application android:icon="@drawable/icon" android:label="@string/app_name">
  97.         <activity android:name=".DownloadActivity"
  98.                   android:label="@string/app_name">
  99.             <intent-filter>
  100.                 <action android:name="android.intent.action.MAIN" />
  101.                 <category android:name="android.intent.category.LAUNCHER" />
  102.             </intent-filter>
  103.         </activity>

  104.     </application>
  105.     <uses-sdk android:minSdkVersion="7" />
  106.     
  107.     <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
  108.     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  109.     <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
  110.     <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" />
  111.     <uses-permission android:name="android.permission.ACCESS_DRM" />
  112.      <uses-permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS" />
  113.     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  114.     <uses-permission android:name="android.permission.INTERNET" />
  115.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  116.     <uses-permission android:name="android.permission.INSTALL_DRM" />
  117.     
  118. </manifest>
複製程式碼


程式碼裡面引用了ParseException和WebAddress兩個類,可以從Android原始碼裡找到copy進來,在這裡frameworks/base/core/java/android/net。程式碼裡面有幾個地方比較重要的:
a) 通過往DownloadProvider提供的ContentProvider “content://downloads/download” 中插入資料就能觸發DownloadProvider的執行。
b) values.put("destination", 1); 是下載檔案儲存在什麼地方, 如果沒有這個引數,預設儲存在sdcard的download 下面 (Constants.java 中的 DEFAULT_DL_SUBDIR = "/download" )
   如果指定為1,是往記憶體的 /cache目錄下存東西 (在/frameworks/base/core/java/android/provider/Downloads.java中定義, public static final int DESTINATION_CACHE_PARTITION = 1; )
b) 注意Manifest中的一堆許可權: ACCESS_DOWNLOAD_MANAGER是最基本的許可權,這樣可以使用DownloadProvider下載。
   如果需要destination=1,則需要 ACCESS_DOWNLOAD_MANAGER許可權。(Downloads.java中的註釋 : All file types are allowed, and only the initiating
     application can access the file (indirectly through a content provider). This requires the android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.)如果沒有這個許可權,在往 content://downloads/download插入的時候有許可權問題報錯:
09-16 17:16:38.062: ERROR/DatabaseUtils(763): Writing exception to parcel
09-16 17:16:38.062: ERROR/DatabaseUtils(763): java.lang.SecurityException: unauthorized destination code
09-16 17:16:38.062: ERROR/DatabaseUtils(763):     at com.android.providers.downloads.DownloadProvider.insert(DownloadProvider.java:277)
09-16 17:16:38.062: ERROR/DatabaseUtils(763):     at android.content.ContentProvider$Transport.insert(ContentProvider.java:150)
09-16 17:16:38.062: ERROR/DatabaseUtils(763):     at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:140)
09-16 17:16:38.062: ERROR/DatabaseUtils(763):     at android.os.Binder.execTransact(Binder.java:287)
09-16 17:16:38.062: ERROR/DatabaseUtils(763):     at dalvik.system.NativeStart.run(Native Method)
09-16 17:16:38.102: DEBUG/AndroidRuntime(4086): Shutting down VM因為DownloadProvider.java中有這段程式碼:
        if (dest != null) {
            if (getContext().checkCallingPermission(Downloads.PERMISSION_ACCESS_ADVANCED)
                    != PackageManager.PERMISSION_GRANTED
                    && dest != Downloads.DESTINATION_EXTERNAL
                    && dest != Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE) {
                throw new SecurityException("unauthorized destination code");
            }
所以:要往/cache目錄下存東西,一定要記得這個許可權哦。實際執行起來,只加這個許可權往/cache下存東西還不夠,就又把其他一堆許可權都加上了,具體哪些有用還沒細看。5) 將這個app直接以普通app安裝上去,執行,可以看到下載成功到/cache裡了。
第二種思路就是想辦法獲得system許可權或者簽名:
這樣不修改DownloadProvider的程式碼,不動它。
而是將自己編寫的app做完以後放到/packages/app目錄下和整個系統一起編譯,將其編譯到img中的系統app下 這樣編譯完成以後執行,使用編譯的img執行模擬器。在模擬器中啟動自己寫的呼叫DownloadProvider的app,發現竟然也是可以呼叫的。 
不過這種方法在模擬器上成功了,但是在真機上沒成功,可能還有些問題沒解決。第一種方法是完全成功的。

相關文章