2026年10月29日 · 今日 0 帖 · 会员 6.1万 · 主题 17.4万
登录 · 注册 · 游客,欢迎随便逛逛
返回版块
JK-0146865
Lv2独赏二月雪等级图标
帖子
1
回复
179
积分
6,335
注册
2013-11-19
[怀旧国机]

使用Download Provider 进行文件下载全攻略 (求加精上手啊)

[复制链接]
查看 853 回复 3
楼主 发表于 | 只看该作者

在10086的开发社区看到这篇未审核的文章,自觉得还不错,挺有用的,转给大家共享:http://dev.10086.cn/cmdn/wiki/index.php?doc-view-4606.html

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.

   直接贴源码了:

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 on*(Bundle savedInstanceState) {

        super.on*(savedInstanceState);

        setContentView(R.layout.main);

        

        //String url = “http://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.mAuth*, 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 DownloadFile* 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.*(Uri.parse(“content://downloads/download“), values);

        

    }

}

AndroidManifest.xml

<?xml version=“1.0“ encoding=“utf-8“?>

<manifest xmlns:android=“http://schemas.android.com/apk/res/android“

      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); 是下载文件存储在什么地方, 如果没有这个参数,默认保存在sd*的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.*(DownloadProvider.java:277)

09-16 17:16:38.062: ERROR/DatabaseUtils(763):     at android.content.ContentProvider$$Transport.*(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.*Transact(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,发现竟然也是可以调用的。

不过这种方法在模拟器上成功了,但是在真机上没成功,可能还有些问题没解决。第一种方法是完全成功的。

               

(非原创)

登录回复
JK-0117194
Lv5甘首五朝臣等级图标等级图标等级图标等级图标等级图标
勋章
帖子
17
回复
1,040
积分
5,272
注册
2012-07-26
1 楼

晕

JK-0121680
Lv7可吟七步诗等级图标等级图标等级图标等级图标
勋章勋章勋章勋章勋章勋章勋章勋章
帖子
194
回复
5,528
积分
2.5万
注册
2012-09-06
2 楼

就是难懂。。。。

JK-0146212
Lv10拔剑十里寒等级图标
勋章勋章勋章勋章勋章勋章勋章勋章
帖子
118
回复
3,468
积分
2.1万
注册
2013-11-01
3 楼

晕了

登录后可以回复这个帖子。旧账号可使用原密码登录。

登录 注册新号
← 返回版块