美文网首页
解析DownloadManager源码

解析DownloadManager源码

作者: Db_z | 来源:发表于2023-02-26 15:57 被阅读0次

    今天来说说DownloadManager的功能,看类名就知道是一个下载管理器,处理下载操作的,那么这个怎么用呢?
    一般来说搭配BroadcastReceiver 来使用,因为下载管理器在下载完成的时候是会发送广播的,而且在通知栏也可以设置是否显示下载的通知,也可以有点击事件,这在DownloadManager里面有对应的常量;下载管理器那肯定是下载的,那么我想知道我下载的文件大小呢?下载的进度呢?别急,首先来说说源码,以下基本都包括了 也标有注释

    /**
     * 下载管理器是一个处理长时间HTTP下载的系统服务。客户端可以请求将URI下载到特定的目标文件。下载管理器将在 
     * 后台进行下载,处理HTTP交互,并在失败后或连接更改和系统重新启动时重试下载。
     * 通过此API请求下载的应用程序应为ACTION_NOTIFICATION_LICKED注册广播接收器,以便在用户在通知中或从下 
     * 载UI中单击正在运行的下载时进行适当处理。
     * 必须具有android.Manifest.permission.INTERNET权限才能使用此类。
     */
    @SystemService(Context.DOWNLOAD_SERVICE)
    public class DownloadManager {
    
        /**
         * 特定下载的标识符,在整个系统中是唯一的。客户端使用此ID进行与下载相关的后续操作
         */
        public final static String COLUMN_ID = Downloads.Impl._ID;
        /**
         * 显示在系统通知中的标题
         */
        public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE;
    
         /**
         * 下载的URI
         */
        public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI;
    
        /**
         * 下载文件的internet媒体类型
         */
        public final static String COLUMN_MEDIA_TYPE = "media_type";
    
        /**
         * 下载的总大小(字节)。最初是-1
         */
        public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
    
        /**
         * 存储下载文件的Uri。如果提供了目的地,使用该URI。否则默认为空,下载开始 
         * 之后使用生成的URI
         */
        public final static String COLUMN_LOCAL_URI = "local_uri";
    
        /**
         * 下载的当前状态
         */
        public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
    
        /**
         * 提供下载状态的详细信息。取决于COLUMN_STATUS的值。当COLUMN_STATUS  为    
         * STATUS_FAILED时,表示发生的错误类型。如果发生HTTP错误,这将保存RFC 2616中定义的HTTP状态代    
         * 码。否则,它将保存ERROR_*常数之一。当COLUMN_STATUS为STATUS_PAUSED时,表示下载暂停的原 
         * 因。保存PAUSED_*常数之一。如果COLUMN_STATUS既不是STATUS_FAILED也不是STATUS_PAUSED,则  
         * 表示此列的值未定。
         * RFC 2616 查看  https://www.rfc-editor.org/rfc/rfc9110.html
         */
        public final static String COLUMN_REASON = "reason";
    
        /**
         * @link COLUMN_STATUS 下载状态  开始时
         */
        public final static int STATUS_PENDING = 1 << 0;
    
        /**
         * @link COLUMN_STATUS 下载正在运行时
         */
        public final static int STATUS_RUNNING = 1 << 1;
    
        /**
         * @link COLUMN_STATUS} 下载等待重试或者恢复时
         */
        public final static int STATUS_PAUSED = 1 << 2;
    
        /**
         * @link COLUMN_STATUS 下载成功完成
         */
        public final static int STATUS_SUCCESSFUL = 1 << 3;
    
        /**
         * @link COLUMN_STATUS 下载失败 (不会重试)
         */
        public final static int STATUS_FAILED = 1 << 4;
    
        /**
         * 下载错误  不符合其他错误的时候
         */
        public final static int ERROR_UNKNOWN = 1000;
      
        // 下面是一些下载错误的状态
        ...
    
        /**
         * @link COLUMN_REASON} 由网络错误而暂停下载时  COLUMN_REASON的值  重试下载之前 正在等待开始
         */
        public final static int PAUSED_WAITING_TO_RETRY = 1;
    
        /**
         * @link COLUMN_REASON 下载正在等待网络连接进行时
         */
        public final static int PAUSED_WAITING_FOR_NETWORK = 2;
    
        /**
         * @link COLUMN_REASON 当下载超过移动网络下载的大小限制 并且下载正在等待Wi-Fi连接继续时
         */
        public final static int PAUSED_QUEUED_FOR_WIFI = 3;
    
        /**
         * @link COLUMN_REASON 下载由于某些其他原因而暂停时
         */
        public final static int PAUSED_UNKNOWN = 4;
    
        /**
         * 下载完成时,发送广播事件
         */
        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
        public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
    
        /**
         * 当用户点击从下拉系统通知UI时,发送广播事件。
         */
        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
        public final static String ACTION_NOTIFICATION_CLICKED =
                "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
    
        // 省略代码
        ...
    
        /**
         * 此类包含请求新下载所需的所有信息。URI是必传的参数。默认下载目标是可以共享的,如果系统需要回收空间  
         * 的话,系统会在其中删除文件。如果觉得这个有问题,请用外部存储。看这个 @link setDestinationUri(Uri)
         */
        public static class Request {
            /**
             * 移动
             */
            public static final int NETWORK_MOBILE = 1 << 0;
    
            /**
             * WIFI
             */
            public static final int NETWORK_WIFI = 1 << 1;
    
            @UnsupportedAppUsage
            private Uri mUri;
            private Uri mDestinationUri;
            private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
            private CharSequence mTitle;
            private CharSequence mDescription;
            private String mMimeType;
            // 默认为允许的所有网络类型
            private int mAllowedNetworkTypes = ~0; 
            private boolean mRoamingAllowed = true;
            private boolean mMeteredAllowed = true;
            private int mFlags = 0;
            private boolean mIsVisibleInDownloadsUi = true;
            private boolean mScannable = false;
            /** if a file is designated as a MediaScanner scannable file, the following value is
             * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
             */
            private static final int SCANNABLE_VALUE_YES = Downloads.Impl.MEDIA_NOT_SCANNED;
            // value of 1 is stored in the above column by DownloadProvider after it is scanned by
            // MediaScanner
            /** if a file is designated as a file that should not be scanned by MediaScanner,
             * the following value is stored in the database column
             * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
             */
            private static final int SCANNABLE_VALUE_NO = Downloads.Impl.MEDIA_NOT_SCANNABLE;
    
            /**
             * 下载可见
             */
            public static final int VISIBILITY_VISIBLE = 0;
    
            /**
             * 下载可见,进行中和完成后都显示
             */
            public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
    
            /**
             * 下载隐藏 不可见
             */
            public static final int VISIBILITY_HIDDEN = 2;
    
            /**
             * 下载只在完成后的通知中显示
             * {@link DownloadManager#addCompletedDownload(String, String,
             * boolean, String, String, long, boolean)}.
             */
            public static final int VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION = 3;
    
            /**
             * 通知默认是可见的
             */
            private int mNotificationVisibility = VISIBILITY_VISIBLE;
    
            /**
             * @param uri 下载的uri
             */
            public Request(Uri uri) {
                if (uri == null) {
                    throw new NullPointerException();
                }
                String scheme = uri.getScheme();
                if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
                    throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
                }
                mUri = uri;
            }
    
            Request(String uriString) {
                mUri = Uri.parse(uriString);
            }
    
            /**
             * 设置下载文件的本地目标。必须得是指向外部存储上路径的文件URI,并且调用应用程序必须具有    
             * WRITE_EXTERNAL_STORAGE权限。
             * 默认下载保存到共享下载缓存中生成的文件名中,系统可以随时删除下载回收空间。
             * 对于Q或更高版本,不需要WRITE EXTERNAL_STORAGE权限,并且uri必须引用应用程序拥有的目录内的路径(比    
             * 如Context.getExternalFilesDir() )或顶级下载目录内的路径(Environment.getExternalStoragePublicDirectory()              
             * 和Environment.directory_Downloads返回)。
             * 参数:uri–下载文件目标的文件uri
             */
            public Request setDestinationUri(Uri uri) {
                mDestinationUri = uri;
                return this;
            }
    
            /**
             * 下载文件的本地目标设置为应用程序外部文件目录 @link Context#getExternalFilesDir(String)
             * 
             * @param context 
             * @param dirType 传递给Context.getExternalFilesDir(String))的目录类型
             * @param subPath 外部目录中的路径,包括文件名
             */
            public Request setDestinationInExternalFilesDir(Context context, String dirType,
                    String subPath) {
                final File file = context.getExternalFilesDir(dirType);
                if (file == null) {
                    throw new IllegalStateException("Failed to get external storage files directory");
                } else if (file.exists()) {
                    if (!file.isDirectory()) {
                        throw new IllegalStateException(file.getAbsolutePath() +
                                " already exists and is not a directory");
                    }
                } else {
                    if (!file.mkdirs()) {
                        throw new IllegalStateException("Unable to create directory: "+
                                file.getAbsolutePath());
                    }
                }
                setDestinationFromBase(file, subPath);
                return this;
            }
    
            /**
             * 下载文件的本地目标设置为应用程序外部文件目录 @link Context#getExternalStoragePublicDirectory(String)
             * @param dirType 传递给Context.getExternalStoragePublicDirectory(String))的目录类型
             * @param subPath 外部目录中的路径,包括文件名
             */
            public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
                File file = Environment.getExternalStoragePublicDirectory(dirType);
                if (file == null) {
                    throw new IllegalStateException("Failed to get external storage public directory");
                }
    
                final Context context = AppGlobals.getInitialApplication();
                if (context.getApplicationInfo().targetSdkVersion
                        >= Build.VERSION_CODES.Q || !Environment.isExternalStorageLegacy()) {
                    try (ContentProviderClient client = context.getContentResolver()
                            .acquireContentProviderClient(Downloads.Impl.AUTHORITY)) {
                        final Bundle extras = new Bundle();
                        extras.putString(Downloads.DIR_TYPE, dirType);
                        client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras);
                    } catch (RemoteException e) {
                        throw new IllegalStateException("Unable to create directory: "
                                + file.getAbsolutePath());
                    }
                } else {
                    if (file.exists()) {
                        if (!file.isDirectory()) {
                            throw new IllegalStateException(file.getAbsolutePath()
                                    + " already exists and is not a directory");
                        }
                    } else if (!file.mkdirs()) {
                        throw new IllegalStateException("Unable to create directory: "
                                + file.getAbsolutePath());
                    }
                }
                setDestinationFromBase(file, subPath);
                return this;
            }
    
            private void setDestinationFromBase(File base, String subPath) {
                if (subPath == null) {
                    throw new NullPointerException("subPath cannot be null");
                }
                // 通过已编码的路径段 创建新的Uri。
                mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
            }
    
            /**
             * 添加请求头
             */
            public Request addRequestHeader(String header, String value) {
                if (header == null) {
                    throw new NullPointerException("header cannot be null");
                }
                if (header.contains(":")) {
                    throw new IllegalArgumentException("header may not contain ':'");
                }
                if (value == null) {
                    value = "";
                }
                mRequestHeaders.add(Pair.create(header, value));
                return this;
            }
    
            /**
             * 设置标题
             */
            public Request setTitle(CharSequence title) {
                mTitle = title;
                return this;
            }
    
            /**
             * 描述
             */
            public Request setDescription(CharSequence description) {
                mDescription = description;
                return this;
            }
    
            /**
             * 类型
             */
            public Request setMimeType(String mimeType) {
                mMimeType = mimeType;
                return this;
            }
    
            /**
             * 设置通知是否显示
             */
            public Request setNotificationVisibility(int visibility) {
                mNotificationVisibility = visibility;
                return this;
            }
    
            /**
             * 设置下载的网络类型
             * 一般把两种 都设置上 第三种过时了  无所谓
             */
            public Request setAllowedNetworkTypes(int flags) {
                mAllowedNetworkTypes = flags;
                return this;
            }
    
           ...
        }
    
        /**
         * 查询
         */
        public static class Query {
            /**
             * 查询排序  升序
             */
            public static final int ORDER_ASCENDING = 1;
    
            /**
             * 查询排序  倒序
             */
            public static final int ORDER_DESCENDING = 2;
    
            private long[] mIds = null;
            private Integer mStatusFlags = null;
            private String mFilterString = null;
            private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
            private int mOrderDirection = ORDER_DESCENDING;
            private boolean mOnlyIncludeVisibleInDownloadsUi = false;
    
            /**
             * 设置条件id
             */
            public Query setFilterById(long... ids) {
                mIds = ids;
                return this;
            }
    
            /**
             * 设置条件 字符串
             */
            public Query setFilterByString(@Nullable String filter) {
                mFilterString = filter;
                return this;
            }
    
            /**
             * 设置条件 状态
             * @link STATUS_PENDING  STATUS_RUNNING STATUS_PAUSED  STATUS_SUCCESSFUL  STATUS_FAILED
             */
            public Query setFilterByStatus(int flags) {
                mStatusFlags = flags;
                return this;
            }
    
            /**
             * 查询是否包含在系统的下载UI中不可见的下载 默认是false  全部查询
             * 如果为true,则查询将仅包括应在系统的下载UI中显示的下载
             */
            @UnsupportedAppUsage
            public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
                mOnlyIncludeVisibleInDownloadsUi = value;
                return this;
            }
    
            /**
             * 返回游标的排序顺序
             * @param column  @link COLUMN_LAST_MODIFIED_TIMESTAMP  @link COLUMN_TOTAL_SIZE_BYTES 
             * @param direction @link ORDER_ASCENDING or @link ORDER_DESCENDING
             */
            @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
            public Query orderBy(String column, int direction) {
                if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
                    throw new IllegalArgumentException("Invalid direction: " + direction);
                }
    
                if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
                    mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
                } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
                    mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES;
                } else {
                    throw new IllegalArgumentException("Cannot order by " + column);
                }
                mOrderDirection = direction;
                return this;
            }
    
          private final ContentResolver mResolver;
          private final String mPackageName;
    
          private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
          private boolean mAccessFilename;
        
          public DownloadManager(Context context) {
            mResolver = context.getContentResolver();
            mPackageName = context.getPackageName();
    
            // Callers can access filename columns when targeting old platform
            // versions; otherwise we throw telling them it's deprecated.
            mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N;
        }
    
        /**
         * Makes this object access the download provider through /all_downloads URIs rather than
         * /my_downloads URIs, for clients that have permission to do so.
         * @hide
         */
        @UnsupportedAppUsage
        public void setAccessAllDownloads(boolean accessAllDownloads) {
            if (accessAllDownloads) {
                mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
            } else {
                mBaseUri = Downloads.Impl.CONTENT_URI;
            }
        }
    
        /** {@hide} */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        public void setAccessFilename(boolean accessFilename) {
            mAccessFilename = accessFilename;
        }
    
        /**
         * Notify {@link DownloadManager} that the given {@link MediaStore} items
         * were just deleted so that {@link DownloadManager} internal data
         * structures can be cleaned up.
         *
         * @param idToMime map from {@link BaseColumns#_ID} to
         *            {@link ContentResolver#getType(Uri)}.
         * @hide
         */
        @SystemApi
        @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
        public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) {
            try (ContentProviderClient client = mResolver
                    .acquireUnstableContentProviderClient(mBaseUri)) {
               final Bundle callExtras = new Bundle();
               final long[] ids = new long[idToMime.size()];
               final String[] mimeTypes = new String[idToMime.size()];
               for (int i = idToMime.size() - 1; i >= 0; --i) {
                   ids[i] = idToMime.keyAt(i);
                   mimeTypes[i] = idToMime.valueAt(i);
               }
               callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids);
               callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES,
                       mimeTypes);
               client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED,
                       null, callExtras);
            } catch (RemoteException e) {
                // Should not happen
            }
        }
    
        /**
         *  加入下载队列,一旦下载管理器准备好执行下载并且连接可用,下载将自动开始
         *
         * @param request 下载的请求
         * @return  返回下载的id  唯一的id 
         */
        public long enqueue(Request request) {
            ContentValues values = request.toContentValues(mPackageName);
            Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
            long id = Long.parseLong(downloadUri.getLastPathSegment());
            return id;
        }
    
        /**
         * Marks the specified download as 'to be deleted'. This is done when a completed download
         * is to be removed but the row was stored without enough info to delete the corresponding
         * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
         *
         * @param ids the IDs of the downloads to be marked 'deleted'
         * @return the number of downloads actually updated
         * @hide
         */
        public int markRowDeleted(long... ids) {
            if (ids == null || ids.length == 0) {
                // called with nothing to remove!
                throw new IllegalArgumentException("input param 'ids' can't be null");
            }
            return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
        }
    
        /**
         * 取消下载并将从下载管理器中删除。如果每次下载都在运行,将停止,并且不再可以通过下载管理器访问。如果有下载的部分  
         * 或完整文件,则会将其删除
         * @param ids 要删除下载的ID
         * @return 返回实际删除的下载数
         */
        public int remove(long... ids) {
            return markRowDeleted(ids);
        }
    
        /**
         * 向下载管理器中 查询已请求的下载。
         */
        public Cursor query(Query query) {
            return query(query, UNDERLYING_COLUMNS);
        }
    
        /** @hide */
        public Cursor query(Query query, String[] projection) {
            Cursor underlyingCursor = query.runQuery(mResolver, projection, mBaseUri);
            if (underlyingCursor == null) {
                return null;
            }
            return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename);
        }
    
        /**
         * 打开下载的文件进行阅读。下载必须已完成
         *  @param id 下载的id 
         */
        public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
            return mResolver.openFileDescriptor(getDownloadUri(id), "r");
        }
    
        /**
         * 如果文件下载成功,返回下载文件id的Uri。否则返回null。
         * @param id–下载文件的id。
         */
        public Uri getUriForDownloadedFile(long id) {
            // to check if the file is in cache, get its destination from the database
            Query query = new Query().setFilterById(id);
            Cursor cursor = null;
            try {
                cursor = query(query);
                if (cursor == null) {
                    return null;
                }
                if (cursor.moveToFirst()) {
                    int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
                    if (DownloadManager.STATUS_SUCCESSFUL == status) {
                        return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
                    }
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
            // downloaded file not found or its status is not 'successfully completed'
            return null;
        }
    
        /**
         * 如果文件下载成功,返回下载文件id的媒体类型。否则返回null。
         */
        public String getMimeTypeForDownloadedFile(long id) {
            Query query = new Query().setFilterById(id);
            Cursor cursor = null;
            try {
                cursor = query(query);
                if (cursor == null) {
                    return null;
                }
                while (cursor.moveToFirst()) {
                    return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
            // downloaded file not found or its status is not 'successfully completed'
            return null;
        }
    
        /**
         * 重新启动下载
         */
        @UnsupportedAppUsage
        public void restartDownload(long... ids) {
            Cursor cursor = query(new Query().setFilterById(ids));
            try {
                for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                    int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
                    if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
                        throw new IllegalArgumentException("Cannot restart incomplete download: "
                                + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
                    }
                }
            } finally {
                cursor.close();
            }
    
            ContentValues values = new ContentValues();
            values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
            values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
            values.putNull(Downloads.Impl._DATA);
            values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
            values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
            mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
        }
    
        /**
         * 即使下载的大小 大于getMaxBytesOverMobile,也强制下载继续。
         */
        public void forceDownload(long... ids) {
            ContentValues values = new ContentValues();
            values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
            values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
            values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1);
            mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
        }
    
        /**
         * 如果下载已完成,则重命名
         */
        public boolean rename(Context context, long id, String displayName) {
            if (!FileUtils.isValidFatFilename(displayName)) {
                throw new SecurityException(displayName + " is not a valid filename");
            }
    
            final String filePath;
            final Query query = new Query().setFilterById(id);
            try (Cursor cursor = query(query)) {
                if (cursor == null) {
                    throw new IllegalStateException("Missing cursor for download id=" + id);
                }
                if (cursor.moveToFirst()) {
                    final int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
                    if (status != DownloadManager.STATUS_SUCCESSFUL) {
                        throw new IllegalStateException("Download is not completed yet: "
                                + DatabaseUtils.dumpCurrentRowToString(cursor));
                    }
                    filePath = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME));
                    if (filePath == null) {
                        throw new IllegalStateException("Download doesn't have a valid file path: "
                                + DatabaseUtils.dumpCurrentRowToString(cursor));
                    } else if (!new File(filePath).exists()) {
                        throw new IllegalStateException("Downloaded file doesn't exist anymore: "
                                + DatabaseUtils.dumpCurrentRowToString(cursor));
                    }
                } else {
                    throw new IllegalStateException("Missing download id=" + id);
                }
            }
    
            final File before = new File(filePath);
            final File after = new File(before.getParentFile(), displayName);
    
            if (after.exists()) {
                throw new IllegalStateException("File already exists: " + after);
            }
            if (!before.renameTo(after)) {
                throw new IllegalStateException(
                        "Failed to rename file from " + before + " to " + after);
            }
    
            // TODO: DownloadProvider.update() should take care of updating corresponding
            // MediaProvider entries.
            MediaStore.scanFile(mResolver, before);
            MediaStore.scanFile(mResolver, after);
    
            final ContentValues values = new ContentValues();
            values.put(Downloads.Impl.COLUMN_TITLE, displayName);
            values.put(Downloads.Impl._DATA, after.toString());
            values.putNull(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
            final long[] ids = { id };
    
            return mResolver.update(
                    mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)) == 1;
        }
    
       ...
    
        /**
         * 添加文件到下载数据库系统中
         * Q或更高版本,路径必须位于所拥有的目录{例如Context.getExternalFilesDir(String)},或者如果应用程序在旧存储模型下  
         * 运行(请参见android:requestLegacyExternalStorage),路径也可以位于顶级下载目录中(由 
         * Environment.getExternalStoragePublicDirectory(String)和 Environment.directory_Downloads返回)
         * {@link Environment#getExternalStoragePublicDirectory(String)} with
         * {@link Environment#DIRECTORY_DOWNLOADS}).
         * 返回id
         */
        @Deprecated
        public long addCompletedDownload(String title, String description,
                boolean isMediaScannerScannable, String mimeType, String path, long length,
                boolean showNotification) {
            return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
                    length, showNotification, false, null, null);
        }
    
        @Deprecated
        public long addCompletedDownload(String title, String description,
                boolean isMediaScannerScannable, String mimeType, String path, long length,
                boolean showNotification, Uri uri, Uri referer) {
            return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
                    length, showNotification, false, uri, referer);
        }
    
        @Deprecated
        public long addCompletedDownload(String title, String description,
                boolean isMediaScannerScannable, String mimeType, String path, long length,
                boolean showNotification, boolean allowWrite) {
            return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
                    length, showNotification, allowWrite, null, null);
        }
    
        @Deprecated
        public long addCompletedDownload(String title, String description,
                boolean isMediaScannerScannable, String mimeType, String path, long length,
                boolean showNotification, boolean allowWrite, Uri uri, Uri referer) {
            // make sure the input args are non-null/non-zero
            validateArgumentIsNonEmpty("title", title);
            validateArgumentIsNonEmpty("description", description);
            validateArgumentIsNonEmpty("path", path);
            validateArgumentIsNonEmpty("mimeType", mimeType);
            if (length < 0) {
                throw new IllegalArgumentException(" invalid value for param: totalBytes");
            }
    
            // if there is already an entry with the given path name in downloads.db, return its id
            Request request;
            if (uri != null) {
                request = new Request(uri);
            } else {
                request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD);
            }
            request.setTitle(title)
                    .setDescription(description)
                    .setMimeType(mimeType);
            if (referer != null) {
                request.addRequestHeader("Referer", referer.toString());
            }
            ContentValues values = request.toContentValues(null);
            values.put(Downloads.Impl.COLUMN_DESTINATION,
                    Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
            values.put(Downloads.Impl._DATA, path);
            values.put(Downloads.Impl.COLUMN_MIME_TYPE, resolveMimeType(new File(path)));
            values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
            values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
            values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
                    (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
                            Request.SCANNABLE_VALUE_NO);
            values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
                    Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
            values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0);
            Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
            if (downloadUri == null) {
                return -1;
            }
            return Long.parseLong(downloadUri.getLastPathSegment());
        }
    
       ...
    
        /**
         *  DownloadProvider返回的游标,并显示一组不同的列,这些列是在DownloadManager.COLUMN_*常 
         * 量中定义。
         * 主要是封装了一下  返回的各种状态,以及成功失败的状态码
         */
        private static class CursorTranslator extends CursorWrapper {
            private final Uri mBaseUri;
            private final boolean mAccessFilename;
    
            public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) {
                super(cursor);
                mBaseUri = baseUri;
                mAccessFilename = accessFilename;
            }
    
            @Override
            public int getInt(int columnIndex) {
                return (int) getLong(columnIndex);
            }
    
            @Override
            public long getLong(int columnIndex) {
                if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
                    return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
                } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
                    return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
                } else {
                    return super.getLong(columnIndex);
                }
            }
    
            @Override
            public String getString(int columnIndex) {
                final String columnName = getColumnName(columnIndex);
                switch (columnName) {
                    case COLUMN_LOCAL_URI:
                        return getLocalUri();
                    case COLUMN_LOCAL_FILENAME:
                        if (!mAccessFilename) {
                            throw new SecurityException(
                                    "COLUMN_LOCAL_FILENAME is deprecated;"
                                            + " use ContentResolver.openFileDescriptor() instead");
                        }
                    default:
                        return super.getString(columnIndex);
                }
            }
    
            private String getLocalUri() {
                long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
                if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
                        destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
                        destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
                    String localPath = super.getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
                    if (localPath == null) {
                        return null;
                    }
                    return Uri.fromFile(new File(localPath)).toString();
                }
    
                // return content URI for cache download
                long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
                return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString();
            }
    
            private long getReason(int status) {
                switch (translateStatus(status)) {
                    case STATUS_FAILED:
                        return getErrorCode(status);
    
                    case STATUS_PAUSED:
                        return getPausedReason(status);
    
                    default:
                        return 0; // arbitrary value when status is not an error
                }
            }
    
            private long getPausedReason(int status) {
                switch (status) {
                    case Downloads.Impl.STATUS_WAITING_TO_RETRY:
                        return PAUSED_WAITING_TO_RETRY;
    
                    case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
                        return PAUSED_WAITING_FOR_NETWORK;
    
                    case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
                        return PAUSED_QUEUED_FOR_WIFI;
    
                    default:
                        return PAUSED_UNKNOWN;
                }
            }
    
            private long getErrorCode(int status) {
                if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
                        || (500 <= status && status < 600)) {
                    // HTTP status code
                    return status;
                }
    
                switch (status) {
                    case Downloads.Impl.STATUS_FILE_ERROR:
                        return ERROR_FILE_ERROR;
    
                    case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
                    case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
                        return ERROR_UNHANDLED_HTTP_CODE;
    
                    case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
                        return ERROR_HTTP_DATA_ERROR;
    
                    case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
                        return ERROR_TOO_MANY_REDIRECTS;
    
                    case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
                        return ERROR_INSUFFICIENT_SPACE;
    
                    case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
                        return ERROR_DEVICE_NOT_FOUND;
    
                    case Downloads.Impl.STATUS_CANNOT_RESUME:
                        return ERROR_CANNOT_RESUME;
    
                    case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
                        return ERROR_FILE_ALREADY_EXISTS;
    
                    default:
                        return ERROR_UNKNOWN;
                }
            }
    
            private int translateStatus(int status) {
                switch (status) {
                    case Downloads.Impl.STATUS_PENDING:
                        return STATUS_PENDING;
    
                    case Downloads.Impl.STATUS_RUNNING:
                        return STATUS_RUNNING;
    
                    case Downloads.Impl.STATUS_PAUSED_BY_APP:
                    case Downloads.Impl.STATUS_WAITING_TO_RETRY:
                    case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
                    case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
                        return STATUS_PAUSED;
    
                    case Downloads.Impl.STATUS_SUCCESS:
                        return STATUS_SUCCESSFUL;
    
                    default:
                        assert Downloads.Impl.isStatusError(status);
                        return STATUS_FAILED;
                }
            }
        }
    }
    

    以上基本就是DownloadManager的源码了,其实也没有太多,首先构建一个请求,把要下载的url传进去,然后设置一些下载请求的参数,比如通知栏的标题、描述、网络类型、是否显示在通知栏等等;然后通过ContentValues传递参数,加入下载队列,然后开始下载;

                    ...
                    val request = DownloadManager.Request(Uri.parse(fileMsg.url)) //添加下载文件的网络路径
                    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileMsg.name) //添加保存文件路径与名称
                    request.setTitle(fileMsg.name) //添加在通知栏里显示的标题
                    request.setDescription(StringUtils.getString(R.string.down_loading)) //添加在通知栏里显示的描述
                    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI) //设置下载的网络类型
                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) //下载中与下载完成后都会在通知中显示| 另外可以选 DownloadManager.Request.VISIBILITY_VISIBLE 仅在下载中时显示在通知中,完成后会自动隐藏
                    val downloadId = downloadManager.enqueue(request) //加入队列,会返回一个唯一下载id
                    // 线程池 更好的利用资源
                    executorServiceSingle.submit(UpdateProcessTask(downloadManager, downloadId, item))
    

    但是在下载之前,咱们还需要有其他的逻辑操作,咱们不能来一个就直接下载吧?是不是得判断当前文件的地址是不是下载成功?或者正在下载?所以咱们得有一段逻辑判断,下面就开始写代码

        /**
         * 广播接受器, 下载完成监听器
         */
        val receiver: BroadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent) {
                val action = intent.action
                 // 下载完成
                if (action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
                    //获取当前完成任务的ID
                    val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
                    // 自行根据需求写逻辑
                }
                if (action == DownloadManager.ACTION_NOTIFICATION_CLICKED) {
                    //广播被点击了
                }
            }
        }
    
      fun downloadFile(activity: Activity, executorServiceSingle: ExecutorService, url: String) {
            try {
                val downloadManager = activity.getSystemService(AppCompatActivity.DOWNLOAD_SERVICE) as DownloadManager
                // 查询是否存在
                val (isExit, id) = queryExist(activity, url)
                // 存在即是下载过了
                if (isExit) {
                    id?.let {
                        // 根据下载返回的id或者下载文件的uri  然后打开这个文件
                        val uri = downloadManager.getUriForDownloadedFile(it)
                        openFileInBrowser(activity, uri, url)
                    }
                } else {
                    val request = DownloadManager.Request(Uri.parse(fileMsg.url)) //添加下载文件的网络路径
                    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileMsg.name) //添加保存文件路径与名称
                    request.setTitle(name) //添加在通知栏里显示的标题
                    request.setDescription(StringUtils.getString(R.string.down_loading)) //添加在通知栏里显示的描述
                    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI) //设置下载的网络类型
                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) //下载中与下载完成后都会在通知中显示| 另外可以选 DownloadManager.Request.VISIBILITY_VISIBLE 仅在下载中时显示在通知中,完成后会自动隐藏
                    val downloadId = downloadManager.enqueue(request) //加入队列,会返回一个唯一下载id
                    executorServiceSingle.submit(UpdateProcessTask(downloadManager, downloadId))
                }
            } catch (e: Exception) {
                StringUtils.getString(R.string.prompt_msg_15).toast()
            }
        }
    
        // 查询是否下载过
        fun queryExist(context: Context, url: String): Pair<Boolean, Long?> {
            //获取下载管理器
            val manager = context.getSystemService(AppCompatActivity.DOWNLOAD_SERVICE) as DownloadManager
            //获取下载器任务队列
            val query = DownloadManager.Query()
           // 过滤条件 查询下载成功的
            query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL)
            manager.query(query).use {
                while (it.moveToNext()) {
                    if (it.getString(it.getColumnIndex(DownloadManager.COLUMN_URI)).contains(url)) {
                        val id = it.getLong(it.getColumnIndex(DownloadManager.COLUMN_ID))
                        // 进行替换路径 把file:// 去掉
                        val path = it.getString(it.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)).replace("file://","")
                        // 有一些文件的路径需要解码,不然下面的判断不存在
                        val file = File(Uri.decode(path))
                        if (file.exists()) {
                            return Pair(true, id)
                        }
                    }
                }
            }
            return Pair(false, null)
        }
    
      // 打开文件
      private fun openFileInBrowser(context: Context, uri: Uri, url: String) {
            try {
                val intent = Intent(Intent.ACTION_VIEW)
                intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.packageName)
                intent.setDataAndType(uri, getMimeType(url))
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                context.startActivity(Intent.createChooser(intent, "Chooser"))
            } catch (e: ActivityNotFoundException) {
                StringUtils.getString(R.string.prompt_msg_17).toast()
            }
        }
    
      // 获取类型  先是获取文件的扩展名  然后根据扩展名给定MIME类型
      fun getMimeType(url: String?): String? {
            var type: String? = ""
            val extension = MimeTypeMap.getFileExtensionFromUrl(url)
            if (extension != null) {
                type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
            }
            return type
        }
    
    class UpdateProcessTask(private val downloadManager: DownloadManager, private val downloadId: Long) : Runnable {
        override fun run() {
            do {
                val sizeArray = getBytesAndStatus(downloadManager, downloadId)
                if (sizeArray[0] <= 0) {
                    sizeArray[0] = 0
                }
                if (sizeArray[1] <= 0) {
                    sizeArray[1] = 1
                }
                val downloadProcess = sizeArray[0].toFloat() / sizeArray[1]
                //获取到文件下载的状态
                val downloadState = sizeArray[2]
                // 如果需要更新下载进度  那么在这里发送 下载的进度和状态
                LiveEventBus.get(EventKeys.FILE_DOWNLOAD_PROCESS, ChatMessageBean::class.java).post(downloadProcess)
                SystemClock.sleep(200)
            } while (sizeArray[0] < sizeArray[1])
    
        }
    }
    
        /**
         * 通过query查询下载状态,包括已下载数据大小,总大小,下载状态
         * 根据DownloadManager 的常量获取对应的数据
         * @param downloadId
         */
        fun getBytesAndStatus(downloadManager: DownloadManager, downloadId: Long): IntArray {
            val bytesAndStatus = intArrayOf(0, 1, 0)
            val query = DownloadManager.Query().setFilterById(downloadId)
            var cursor: Cursor? = null
            try {
                cursor = downloadManager.query(query)
                if (cursor != null && cursor.moveToFirst()) {
                    //已经下载文件大小
                    bytesAndStatus[0] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
                    //下载文件的总大小
                    bytesAndStatus[1] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
                    //下载状态
                    bytesAndStatus[2] = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
                }
            } finally {
                cursor?.close()
            }
            return bytesAndStatus
        }
    

    至此 基本就已经完成了简单的需求下载显示进度,打开文件等这只是一部分模板代码,自行可根据实际需求修改,比如UI部分,比如更新的逻辑,还有通知的点击事件等等,这里只是写出用法以及源码的方法。
    源码中包括了几个部分:
    Request请求:设置基本信息,标题、描述、类型等等;下载文件的本地目标设置为外部文件目录中的路径;通知显示等等;
    Query查询:根据下载的id或下载中的状态条件查询下载的进度,大小等;
    DownloadManager:添加下载到队列,根据id移除下载,根据id获取uri等;
    CursorTranslator:封装了DownloadProvider返回的游标,显示了一组不同的列,这些列是在DownloadManager.COLUMN_*常量中定义的,某些列直接对应于基础值,而其他列则根据基础数据计算。
    当然还有很多方法,这里就不一一提了,如果感兴趣的话,可以去看源码。
    如果有遗漏或者不对的地方,请指正@我

    相关文章

      网友评论

          本文标题:解析DownloadManager源码

          本文链接:https://www.haomeiwen.com/subject/fftgkdtx.html