读懂Android :使用Android内部的DownloadProvider下载文件,并获取cache权限



Android内部提供了一个DownloadProvider,是一个非常完整的下载工具,提供了很好的外部接口可以被其他应用程序调用,来完成下载工作。同时也提供和很好的下载、通知、存储等机制。

在Android的Browser等工具里面都用到了这个DownloadProvider。

但是很遗憾的是,这个DownloadProvider不对app开发人员开放,只作为内部使用。

我们现在去探究如何将DownloadProvider拿来给自己用。

让我们先找到DownloadProvider不能用的原因:

先找到它的源代码,在这个位置:/packages/providers/DownloadProvider

打开AndroidManifest.xml文件,里面有几个自定义的权限。

  1.    
  2.         android:label="@string/permlab_downloadManager"
  3.         android:description="@string/permdesc_downloadManager"
  4.         android:protectionLevel="signatureOrSystem" />
  5.    
  6.    
  7.         android:label="@string/permlab_downloadManagerAdvanced"
  8.         android:description="@string/permdesc_downloadManagerAdvanced"
  9.         android:protectionLevel="signatureOrSystem" />
  10.    
  11.    
  12.         android:label="@string/permlab_cacheFilesystem"
  13.         android:description="@string/permdesc_cacheFilesystem"
  14.         android:protectionLevel="signature" />
  15.    
  16.    
  17.         android:label="@string/permlab_downloadCompletedIntent"
  18.         android:description="@string/permdesc_downloadCompletedIntent"
  19.         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. DownloadActivity.Java
  2. package com.xxxx.usedownload;
  3. import java.io.FileNotFoundException;
  4. import java.NET.URI;
  5. import android.app.Activity;
  6. import android.content.ContentResolver;
  7. import android.content.ContentValues;
  8. import android.content.Context;
  9. import android.net.Uri;
  10. import android.os.Bundle;
  11. import android.webkit.URLUtil;
  12. /**
  13. * @author lixinso
  14. * 使用DownloadProvider
  15. */
  16. public class DownloadActivity extends Activity {
  17.      @Override
  18.     public void onCreate(Bundle savedInstanceState) {
  19.         super.onCreate(savedInstanceState);
  20.         setContentView(R.layout.main);
  21.         
  22.         //String url = "http://192.168.200.76:8080/webserver/dancing-skeleton.3gp";
  23.         String contentDisposition = "attachment; filename=/"dancing-skeleton.3gp/"";
  24.         String mimetype = "video/3GPP";
  25.         
  26.         String filename = URLUtil.guessFileName(url,contentDisposition, mimetype);
  27.         
  28.         URI uri = null;
  29.          
  30.         try {
  31.             // Undo the percent-encoding that KURL may have done.
  32.             String newUrl = new String(URLUtil.decode(url.getBytes()));
  33.             // Parse the url into pieces
  34.             WebAddress w = new WebAddress(newUrl);
  35.             String frag = null;
  36.             String query = null;
  37.             String path = w.mPath;
  38.             // Break the path into path, query, and fragment
  39.             if (path.length() > 0) {
  40.                 // Strip the fragment
  41.                 int idx = path.lastIndexOf('#');
  42.                 if (idx != -1) {
  43.                     frag = path.substring(idx + 1);
  44.                     path = path.substring(0, idx);
  45.                 }
  46.                 idx = path.lastIndexOf('?');
  47.                 if (idx != -1) {
  48.                     query = path.substring(idx + 1);
  49.                     path = path.substring(0, idx);
  50.                 }
  51.             }
  52.             uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
  53.                     query, frag);
  54.         } catch (Exception e) {
  55.             //Log.e(LOGTAG, "Could not parse url for download: " + url, e);
  56.             return;
  57.         }
  58.         
  59.         ContentValues values = new ContentValues();
  60.         values.put("uri", uri.toString());
  61.         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");
  62.         values.put("notificationpackage", getPackageName());
  63.         values.put("notificationclass", "HelloWorld");
  64.         values.put("visibility", 1);
  65.         values.put("mimetype", mimetype);
  66.         values.put("hint", filename);
  67.         values.put("description", uri.getHost());
  68.         values.put("total_bytes", 1349528);
  69.         values.put("destination", 1);
  70.         
  71.         
  72.          
  73.         //这些参数参考:DownloadProvider工程中的:Helpers.java
  74.         //public static DownloadFileInfo generateSaveFile(
  75.         //        Context context,
  76.         //      String url,
  77.         //        String hint,
  78.         //        String contentDisposition,
  79.         //        String contentLocation,
  80.         //        String mimeType,
  81.         //        int destination,
  82.         //        int contentLength) throws FileNotFoundException {
  83.         //以及:  framework里的Downloads.java;
  84.         
  85.         
  86.         ContentResolver mResolver = getContentResolver();
  87.         mResolver.insert(Uri.parse("content://downloads/download"), values);
  88.         
  89.     }
  90. }
  91. AndroidManifest.xml
  92. <?xml version="1.0" encoding="utf-8"?>
  93.       package="com.xxxx.usedownload"
  94.       android:versionCode="1"
  95.       android:versionName="1.0">
  96.    
  97.         
  98.                   android:label="@string/app_name">
  99.             
  100.                
  101.                
  102.             
  103.         
  104.    
  105.    
  106.    
  107.    
  108.    
  109.    
  110.    
  111.    
  112.      
  113.    
  114.    
  115.    
  116.    
  117.    
复制代码

代码里面引用了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; )

c) 注意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中有这段代码:

  1. if (dest != null) {
  2.             if (getContext().checkCallingPermission(Downloads.PERMISSION_ACCESS_ADVANCED)
  3.                     != PackageManager.PERMISSION_GRANTED
  4.                     && dest != Downloads.DESTINATION_EXTERNAL
  5.                     && dest != Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE) {
  6.                 throw new SecurityException("unauthorized destination code");
  7.             }
复制代码

所以:要往/cache目录下存东西,一定要记得这个权限哦。

实际运行起来,只加这个权限往/cache下存东西还不够,就又把其他一堆权限都加上了,具体哪些有用还没细看。

5) 将这个app直接以普通app安装上去,运行,可以看到下载成功到/cache里了。

第二种思路就是想办法获得system权限或者签名:

这样不修改DownloadProvider的代码,不动它。

而是将自己编写的app做完以后放到/packages/app目录下和整个系统一起编译,将其编译到img中的系统app下 这样编译完成以后运行,使用编译的img运行模拟器。在模拟器中启动自己写的调用DownloadProvider的app,发现竟然也是可以调用的。

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

更多相关文章

  1. Android群英传笔记——第九章:Android系统信息和安全机制
  2. Android本地视频播放器开发--视频解码
  3. android studio 编译的时候出现的错误和解决方法
  4. android studio编译android M时无法使用org.apache.http.**的解
  5. Android(安卓)App拥有system权限
  6. 使用Android(安卓)NDK编译OpenCV应用
  7. zxing-client-android 导入Android(安卓)Studio项目
  8. spring android 编译环境搭建
  9. 系统签名apk--转

随机推荐

  1. php中函数参数传递的3种方式和区别(附详解
  2. php魔术方法简介
  3. PHP不适合高并发?
  4. 详解PHP中self关键字
  5. PHP去掉字符串中的“#”
  6. 分享一个生成文件层级树类
  7. PHP中如何使用PDO修改数据?
  8. Linux下查看PHP配置文件php.ini的位置
  9. 基于 Hyperf + RabbitMQ + WebSocket 实
  10. PHP之Category类库 无限分类