使用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 = "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.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: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); 是下载文件存储在什么地方, 如果没有这个参数,默认保存在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,发现竟然也是可以调用的。
不过这种方法在模拟器上成功了,但是在真机上没成功,可能还有些问题没解决。第一种方法是完全成功的。




相关文章
|
2月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
180 0
|
6天前
|
存储 安全 Android开发
"解锁Android权限迷宫:一场惊心动魄的动态权限请求之旅,让你的应用从平凡跃升至用户心尖的宠儿!"
【8月更文挑战第13天】随着Android系统的更新,权限管理变得至关重要。尤其从Android 6.0起,引入了动态权限请求,增强了用户隐私保护并要求开发者实现更精细的权限控制。本文采用问答形式,深入探讨动态权限请求机制与最佳实践,并提供示例代码。首先解释了动态权限的概念及其重要性;接着详述实现步骤:定义、检查、请求权限及处理结果;最后总结了六大最佳实践,包括适时请求、解释原因、提供替代方案、妥善处理拒绝情况、适应权限变更及兼容旧版系统,帮助开发者打造安全易用的应用。
15 0
|
2月前
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
131 1
|
2月前
|
Android开发 Windows
如何离线下载 Microsoft Corporation II Windows Subsystem for Android
如何离线下载 Microsoft Corporation II Windows Subsystem for Android
52 1
|
2月前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
129 5
|
2月前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
2月前
|
JSON 编解码 Apache
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
32 1
|
2月前
|
Java 开发工具 Android开发
详细解读Android开发DNK开发将.c文件打包成os
详细解读Android开发DNK开发将.c文件打包成os
20 0
|
3月前
|
Android开发
【苹果安卓通用】xlsx 和 vCard 文件转换器,txt转vCard文件格式,CSV转 vCard格式,如何批量号码导入手机通讯录,一篇文章说全
本文介绍了如何快速将批量号码导入手机通讯录,适用于企业客户管理、营销团队、活动组织、团队协作和新员工入职等场景。步骤包括:1) 下载软件,提供腾讯云盘和百度网盘链接;2) 打开软件,复制粘贴号码并进行加载预览和制作文件;3) 将制作好的文件通过QQ或微信发送至手机,然后按苹果、安卓或鸿蒙系统的指示导入。整个过程简便快捷,可在1分钟内完成。
|
2月前
|
开发工具 Android开发
Android studio历史版本下载
Android studio历史版本下载
291 0