今天來說說DownloadManager的功能,看類名就知道是一個(gè)下載管理器踩官,處理下載操作的鳍刷,那么這個(gè)怎么用呢租漂?
一般來說搭配BroadcastReceiver 來使用,因?yàn)橄螺d管理器在下載完成的時(shí)候是會(huì)發(fā)送廣播的喊儡,而且在通知欄也可以設(shè)置是否顯示下載的通知拨与,也可以有點(diǎn)擊事件,這在DownloadManager里面有對(duì)應(yīng)的常量艾猜;下載管理器那肯定是下載的买喧,那么我想知道我下載的文件大小呢?下載的進(jìn)度呢匆赃?別急淤毛,首先來說說源碼,以下基本都包括了 也標(biāo)有注釋
/**
* 下載管理器是一個(gè)處理長(zhǎng)時(shí)間HTTP下載的系統(tǒng)服務(wù)算柳〉偷客戶端可以請(qǐng)求將URI下載到特定的目標(biāo)文件。下載管理器將在
* 后臺(tái)進(jìn)行下載瞬项,處理HTTP交互查牌,并在失敗后或連接更改和系統(tǒng)重新啟動(dòng)時(shí)重試下載。
* 通過此API請(qǐng)求下載的應(yīng)用程序應(yīng)為ACTION_NOTIFICATION_LICKED注冊(cè)廣播接收器滥壕,以便在用戶在通知中或從下
* 載UI中單擊正在運(yùn)行的下載時(shí)進(jìn)行適當(dāng)處理纸颜。
* 必須具有android.Manifest.permission.INTERNET權(quán)限才能使用此類。
*/
@SystemService(Context.DOWNLOAD_SERVICE)
public class DownloadManager {
/**
* 特定下載的標(biāo)識(shí)符绎橘,在整個(gè)系統(tǒng)中是唯一的胁孙∵刖耄客戶端使用此ID進(jìn)行與下載相關(guān)的后續(xù)操作
*/
public final static String COLUMN_ID = Downloads.Impl._ID;
/**
* 顯示在系統(tǒng)通知中的標(biāo)題
*/
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";
/**
* 下載的總大小(字節(jié))涮较。最初是-1
*/
public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
/**
* 存儲(chǔ)下載文件的Uri稠鼻。如果提供了目的地,使用該URI狂票。否則默認(rèn)為空候齿,下載開始
* 之后使用生成的URI
*/
public final static String COLUMN_LOCAL_URI = "local_uri";
/**
* 下載的當(dāng)前狀態(tài)
*/
public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
/**
* 提供下載狀態(tài)的詳細(xì)信息。取決于COLUMN_STATUS的值闺属。當(dāng)COLUMN_STATUS 為
* STATUS_FAILED時(shí)慌盯,表示發(fā)生的錯(cuò)誤類型。如果發(fā)生HTTP錯(cuò)誤掂器,這將保存RFC 2616中定義的HTTP狀態(tài)代
* 碼亚皂。否則,它將保存ERROR_*常數(shù)之一国瓮。當(dāng)COLUMN_STATUS為STATUS_PAUSED時(shí)灭必,表示下載暫停的原
* 因。保存PAUSED_*常數(shù)之一乃摹。如果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 下載狀態(tài) 開始時(shí)
*/
public final static int STATUS_PENDING = 1 << 0;
/**
* @link COLUMN_STATUS 下載正在運(yùn)行時(shí)
*/
public final static int STATUS_RUNNING = 1 << 1;
/**
* @link COLUMN_STATUS} 下載等待重試或者恢復(fù)時(shí)
*/
public final static int STATUS_PAUSED = 1 << 2;
/**
* @link COLUMN_STATUS 下載成功完成
*/
public final static int STATUS_SUCCESSFUL = 1 << 3;
/**
* @link COLUMN_STATUS 下載失敗 (不會(huì)重試)
*/
public final static int STATUS_FAILED = 1 << 4;
/**
* 下載錯(cuò)誤 不符合其他錯(cuò)誤的時(shí)候
*/
public final static int ERROR_UNKNOWN = 1000;
// 下面是一些下載錯(cuò)誤的狀態(tài)
...
/**
* @link COLUMN_REASON} 由網(wǎng)絡(luò)錯(cuò)誤而暫停下載時(shí) COLUMN_REASON的值 重試下載之前 正在等待開始
*/
public final static int PAUSED_WAITING_TO_RETRY = 1;
/**
* @link COLUMN_REASON 下載正在等待網(wǎng)絡(luò)連接進(jìn)行時(shí)
*/
public final static int PAUSED_WAITING_FOR_NETWORK = 2;
/**
* @link COLUMN_REASON 當(dāng)下載超過移動(dòng)網(wǎng)絡(luò)下載的大小限制 并且下載正在等待Wi-Fi連接繼續(xù)時(shí)
*/
public final static int PAUSED_QUEUED_FOR_WIFI = 3;
/**
* @link COLUMN_REASON 下載由于某些其他原因而暫停時(shí)
*/
public final static int PAUSED_UNKNOWN = 4;
/**
* 下載完成時(shí)孵睬,發(fā)送廣播事件
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
/**
* 當(dāng)用戶點(diǎn)擊從下拉系統(tǒng)通知UI時(shí)璃饱,發(fā)送廣播事件。
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public final static String ACTION_NOTIFICATION_CLICKED =
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
// 省略代碼
...
/**
* 此類包含請(qǐng)求新下載所需的所有信息肪康。URI是必傳的參數(shù)荚恶。默認(rèn)下載目標(biāo)是可以共享的,如果系統(tǒng)需要回收空間
* 的話磷支,系統(tǒng)會(huì)在其中刪除文件谒撼。如果覺得這個(gè)有問題,請(qǐng)用外部存儲(chǔ)雾狈±保看這個(gè) @link setDestinationUri(Uri)
*/
public static class Request {
/**
* 移動(dòng)
*/
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;
// 默認(rèn)為允許的所有網(wǎng)絡(luò)類型
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;
/**
* 下載可見,進(jìn)行中和完成后都顯示
*/
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;
/**
* 通知默認(rèn)是可見的
*/
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);
}
/**
* 設(shè)置下載文件的本地目標(biāo)善榛。必須得是指向外部存儲(chǔ)上路徑的文件URI辩蛋,并且調(diào)用應(yīng)用程序必須具有
* WRITE_EXTERNAL_STORAGE權(quán)限。
* 默認(rèn)下載保存到共享下載緩存中生成的文件名中移盆,系統(tǒng)可以隨時(shí)刪除下載回收空間悼院。
* 對(duì)于Q或更高版本,不需要WRITE EXTERNAL_STORAGE權(quán)限咒循,并且uri必須引用應(yīng)用程序擁有的目錄內(nèi)的路徑(比
* 如Context.getExternalFilesDir() )或頂級(jí)下載目錄內(nèi)的路徑(Environment.getExternalStoragePublicDirectory()
* 和Environment.directory_Downloads返回)据途。
* 參數(shù):uri–下載文件目標(biāo)的文件uri
*/
public Request setDestinationUri(Uri uri) {
mDestinationUri = uri;
return this;
}
/**
* 下載文件的本地目標(biāo)設(shè)置為應(yīng)用程序外部文件目錄 @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;
}
/**
* 下載文件的本地目標(biāo)設(shè)置為應(yīng)用程序外部文件目錄 @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");
}
// 通過已編碼的路徑段 創(chuàng)建新的Uri颖医。
mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
}
/**
* 添加請(qǐng)求頭
*/
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;
}
/**
* 設(shè)置標(biāo)題
*/
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;
}
/**
* 設(shè)置通知是否顯示
*/
public Request setNotificationVisibility(int visibility) {
mNotificationVisibility = visibility;
return this;
}
/**
* 設(shè)置下載的網(wǎng)絡(luò)類型
* 一般把兩種 都設(shè)置上 第三種過時(shí)了 無所謂
*/
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;
/**
* 設(shè)置條件id
*/
public Query setFilterById(long... ids) {
mIds = ids;
return this;
}
/**
* 設(shè)置條件 字符串
*/
public Query setFilterByString(@Nullable String filter) {
mFilterString = filter;
return this;
}
/**
* 設(shè)置條件 狀態(tài)
* @link STATUS_PENDING STATUS_RUNNING STATUS_PAUSED STATUS_SUCCESSFUL STATUS_FAILED
*/
public Query setFilterByStatus(int flags) {
mStatusFlags = flags;
return this;
}
/**
* 查詢是否包含在系統(tǒng)的下載UI中不可見的下載 默認(rèn)是false 全部查詢
* 如果為true位衩,則查詢將僅包括應(yīng)在系統(tǒng)的下載UI中顯示的下載
*/
@UnsupportedAppUsage
public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
mOnlyIncludeVisibleInDownloadsUi = value;
return this;
}
/**
* 返回游標(biāo)的排序順序
* @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
}
}
/**
* 加入下載隊(duì)列,一旦下載管理器準(zhǔn)備好執(zhí)行下載并且連接可用熔萧,下載將自動(dòng)開始
*
* @param request 下載的請(qǐng)求
* @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));
}
/**
* 取消下載并將從下載管理器中刪除糖驴。如果每次下載都在運(yùn)行,將停止佛致,并且不再可以通過下載管理器訪問贮缕。如果有下載的部分
* 或完整文件,則會(huì)將其刪除
* @param ids 要?jiǎng)h除下載的ID
* @return 返回實(shí)際刪除的下載數(shù)
*/
public int remove(long... ids) {
return markRowDeleted(ids);
}
/**
* 向下載管理器中 查詢已請(qǐng)求的下載晌杰。
*/
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);
}
/**
* 打開下載的文件進(jìn)行閱讀。下載必須已完成
* @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;
}
/**
* 重新啟動(dòng)下載
*/
@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梗夸,也強(qiáng)制下載繼續(xù)。
*/
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;
}
...
/**
* 添加文件到下載數(shù)據(jù)庫(kù)系統(tǒng)中
* Q或更高版本反症,路徑必須位于所擁有的目錄{例如Context.getExternalFilesDir(String)},或者如果應(yīng)用程序在舊存儲(chǔ)模型下
* 運(yùn)行(請(qǐng)參見android:requestLegacyExternalStorage)畔派,路徑也可以位于頂級(jí)下載目錄中(由
* 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返回的游標(biāo)铅碍,并顯示一組不同的列,這些列是在DownloadManager.COLUMN_*常
* 量中定義线椰。
* 主要是封裝了一下 返回的各種狀態(tài)胞谈,以及成功失敗的狀態(tài)碼
*/
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的源碼了,其實(shí)也沒有太多憨愉,首先構(gòu)建一個(gè)請(qǐng)求烦绳,把要下載的url傳進(jìn)去,然后設(shè)置一些下載請(qǐng)求的參數(shù)配紫,比如通知欄的標(biāo)題径密、描述、網(wǎng)絡(luò)類型躺孝、是否顯示在通知欄等等睹晒;然后通過ContentValues傳遞參數(shù)趟庄,加入下載隊(duì)列,然后開始下載伪很;
...
val request = DownloadManager.Request(Uri.parse(fileMsg.url)) //添加下載文件的網(wǎng)絡(luò)路徑
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileMsg.name) //添加保存文件路徑與名稱
request.setTitle(fileMsg.name) //添加在通知欄里顯示的標(biāo)題
request.setDescription(StringUtils.getString(R.string.down_loading)) //添加在通知欄里顯示的描述
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI) //設(shè)置下載的網(wǎng)絡(luò)類型
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) //下載中與下載完成后都會(huì)在通知中顯示| 另外可以選 DownloadManager.Request.VISIBILITY_VISIBLE 僅在下載中時(shí)顯示在通知中,完成后會(huì)自動(dòng)隱藏
val downloadId = downloadManager.enqueue(request) //加入隊(duì)列戚啥,會(huì)返回一個(gè)唯一下載id
// 線程池 更好的利用資源
executorServiceSingle.submit(UpdateProcessTask(downloadManager, downloadId, item))
但是在下載之前,咱們還需要有其他的邏輯操作锉试,咱們不能來一個(gè)就直接下載吧猫十?是不是得判斷當(dāng)前文件的地址是不是下載成功?或者正在下載呆盖?所以咱們得有一段邏輯判斷拖云,下面就開始寫代碼
/**
* 廣播接受器, 下載完成監(jiān)聽器
*/
val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
// 下載完成
if (action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
//獲取當(dāng)前完成任務(wù)的ID
val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
// 自行根據(jù)需求寫邏輯
}
if (action == DownloadManager.ACTION_NOTIFICATION_CLICKED) {
//廣播被點(diǎn)擊了
}
}
}
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 {
// 根據(jù)下載返回的id或者下載文件的uri 然后打開這個(gè)文件
val uri = downloadManager.getUriForDownloadedFile(it)
openFileInBrowser(activity, uri, url)
}
} else {
val request = DownloadManager.Request(Uri.parse(fileMsg.url)) //添加下載文件的網(wǎng)絡(luò)路徑
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileMsg.name) //添加保存文件路徑與名稱
request.setTitle(name) //添加在通知欄里顯示的標(biāo)題
request.setDescription(StringUtils.getString(R.string.down_loading)) //添加在通知欄里顯示的描述
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI) //設(shè)置下載的網(wǎng)絡(luò)類型
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) //下載中與下載完成后都會(huì)在通知中顯示| 另外可以選 DownloadManager.Request.VISIBILITY_VISIBLE 僅在下載中時(shí)顯示在通知中,完成后會(huì)自動(dòng)隱藏
val downloadId = downloadManager.enqueue(request) //加入隊(duì)列,會(huì)返回一個(gè)唯一下載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
//獲取下載器任務(wù)隊(duì)列
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))
// 進(jìn)行替換路徑 把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()
}
}
// 獲取類型 先是獲取文件的擴(kuò)展名 然后根據(jù)擴(kuò)展名給定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]
//獲取到文件下載的狀態(tài)
val downloadState = sizeArray[2]
// 如果需要更新下載進(jìn)度 那么在這里發(fā)送 下載的進(jìn)度和狀態(tài)
LiveEventBus.get(EventKeys.FILE_DOWNLOAD_PROCESS, ChatMessageBean::class.java).post(downloadProcess)
SystemClock.sleep(200)
} while (sizeArray[0] < sizeArray[1])
}
}
/**
* 通過query查詢下載狀態(tài)宙项,包括已下載數(shù)據(jù)大小,總大小株扛,下載狀態(tài)
* 根據(jù)DownloadManager 的常量獲取對(duì)應(yīng)的數(shù)據(jù)
* @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()) {
//已經(jīng)下載文件大小
bytesAndStatus[0] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
//下載文件的總大小
bytesAndStatus[1] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
//下載狀態(tài)
bytesAndStatus[2] = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
}
} finally {
cursor?.close()
}
return bytesAndStatus
}
至此 基本就已經(jīng)完成了簡(jiǎn)單的需求下載顯示進(jìn)度尤筐,打開文件等這只是一部分模板代碼,自行可根據(jù)實(shí)際需求修改洞就,比如UI部分盆繁,比如更新的邏輯,還有通知的點(diǎn)擊事件等等旬蟋,這里只是寫出用法以及源碼的方法油昂。
源碼中包括了幾個(gè)部分:
Request請(qǐng)求:設(shè)置基本信息,標(biāo)題倾贰、描述冕碟、類型等等;下載文件的本地目標(biāo)設(shè)置為外部文件目錄中的路徑匆浙;通知顯示等等鸣哀;
Query查詢:根據(jù)下載的id或下載中的狀態(tài)條件查詢下載的進(jìn)度,大小等吞彤;
DownloadManager:添加下載到隊(duì)列我衬,根據(jù)id移除下載,根據(jù)id獲取uri等饰恕;
CursorTranslator:封裝了DownloadProvider返回的游標(biāo)挠羔,顯示了一組不同的列,這些列是在DownloadManager.COLUMN_*常量中定義的埋嵌,某些列直接對(duì)應(yīng)于基礎(chǔ)值破加,而其他列則根據(jù)基礎(chǔ)數(shù)據(jù)計(jì)算。
當(dāng)然還有很多方法雹嗦,這里就不一一提了范舀,如果感興趣的話合是,可以去看源碼。
如果有遺漏或者不對(duì)的地方锭环,請(qǐng)指正@我