使用Android內部的DownloadProvider下載文件,並獲取cache權限
Android內部提供了一個DownloadProvider,是一個非常完整的下載工具,提供了很好的外部接口可以被其他應用程序調用,來完成下載工作。同時也提供和很好的下載、通知、存儲等機製。在Android的Browser等工具裏麵都用到了這個DownloadProvider。
但是很遺憾的是,這個DownloadProvider不對app開發人員開放,隻作為內部使用。
我們現在去探究如何將DownloadProvider拿來給自己用。
讓我們先找到DownloadProvider不能用的原因:
先找到它的源代碼,在這個位置:/packages/providers/DownloadProvider
打開AndroidManifest.xml文件,裏麵有幾個自定義的權限
<!-- Allows access to the Download Manager -->
<permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"
android:label="@string/permlab_downloadManager"
android:description="@string/permdesc_downloadManager"
android:protectionLevel="signatureOrSystem" />
<!-- Allows advanced access to the Download Manager -->
<permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED"
android:label="@string/permlab_downloadManagerAdvanced"
android:description="@string/permdesc_downloadManagerAdvanced"
android:protectionLevel="signatureOrSystem" />
<!-- Allows filesystem access to /cache -->
<permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
android:label="@string/permlab_cacheFilesystem"
android:description="@string/permdesc_cacheFilesystem"
android:protectionLevel="signature" />
<!-- Allows to send download completed intents -->
<permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"
android:label="@string/permlab_downloadCompletedIntent"
android:description="@string/permdesc_downloadCompletedIntent"
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.
直接貼源碼了:
DownloadActivity.java
package com.xxxx.usedownload;
import java.io.FileNotFoundException;
import java.net.URI;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.webkit.URLUtil;
/**
* @author lixinso
* 使用DownloadProvider
*/
public class DownloadActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//String url = "https://192.168.200.76:8080/webserver/dancing-skeleton.3gp";
String contentDisposition = "attachment; filename=/"dancing-skeleton.3gp/"";
String mimetype = "video/3gpp";
String filename = URLUtil.guessFileName(url,contentDisposition, mimetype);
URI uri = null;
try {
// Undo the percent-encoding that KURL may have done.
String newUrl = new String(URLUtil.decode(url.getBytes()));
// Parse the url into pieces
WebAddress w = new WebAddress(newUrl);
String frag = null;
String query = null;
String path = w.mPath;
// Break the path into path, query, and fragment
if (path.length() > 0) {
// Strip the fragment
int idx = path.lastIndexOf('#');
if (idx != -1) {
frag = path.substring(idx + 1);
path = path.substring(0, idx);
}
idx = path.lastIndexOf('?');
if (idx != -1) {
query = path.substring(idx + 1);
path = path.substring(0, idx);
}
}
uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
query, frag);
} catch (Exception e) {
//Log.e(LOGTAG, "Could not parse url for download: " + url, e);
return;
}
ContentValues values = new ContentValues();
values.put("uri", uri.toString());
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");
values.put("notificationpackage", getPackageName());
values.put("notificationclass", "HelloWorld");
values.put("visibility", 1);
values.put("mimetype", mimetype);
values.put("hint", filename);
values.put("description", uri.getHost());
values.put("total_bytes", 1349528);
values.put("destination", 1);
//這些參數參考:DownloadProvider工程中的:Helpers.java
//public static DownloadFileInfo generateSaveFile(
// Context context,
// String url,
// String hint,
// String contentDisposition,
// String contentLocation,
// String mimeType,
// int destination,
// int contentLength) throws FileNotFoundException {
//以及: framework裏的Downloads.java;
ContentResolver mResolver = getContentResolver();
mResolver.insert(Uri.parse("content://downloads/download"), values);
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:andro
package="com.xxxx.usedownload"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".DownloadActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" />
<uses-permission android:name="android.permission.ACCESS_DRM" />
<uses-permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INSTALL_DRM" />
</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,發現竟然也是可以調用的。
不過這種方法在模擬器上成功了,但是在真機上沒成功,可能還有些問題沒解決。第一種方法是完全成功的。
最後更新:2017-04-02 06:51:43