Android 下载模块分析(DownloadManager和DownloadProvider)
Android下载模块主要有2个部分组成:DownloadManager和DownloadProvider;其中DownloadManager提供接口供调用,具体的实现是 DownloadProvider,包括相关数据信息的保存及文件下载。
- DownloadManager.Request用来请求一个下载
- DownloadManager.Query 用来查询下载信息
- enqueue(Request request):执行下载,返回downloadId,downloadId可用于查询下载信息。
- remove(long ids):删除下载,若下载中取消下载。会同时删除下载文件和记录。
- query(Query query)查询下载信息
- getMaxBytesOverMobile(Context context)通过移动网络下载的最大字节数
- getMimeTypeForDownloadedFile(long id)得到下载的mineType
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
DownloadManagerdownloadManager=(DownloadManager)getSystemService(DOWNLOAD_SERVICE);
//文件下载地址
String url="http://v.yingshibao.chuanke.com//001_zongshu.mp4";
//创建一个Request对象
DownloadManager.Request request=newDownloadManager.Request(Uri.parse(url));
//设置下载文件路径
request.setDestinationInExternalPublicDir("itbox","zongshu.mp4");
//开始下载
longdownloadId=downloadManager.enqueue(request);
- setDestinationInExternalFilesDir 设置文件下载路径
- allowScanningByMediaScanner() 表示允许MediaScanner扫描到这个文件夹,默认不允许。
- setTitle()设置下载中通知栏提示的标题
- setDescription()设置下载中通知栏提示的介绍。
- setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)表示下载进行中和下载完成的通知栏是否显示。默认只显示下载中通知。VISIBILITY_VISIBLE_NOTIFY_COMPLETED表示下载完成后显示通知栏提示。VISIBILITY_HIDDEN表示不显示任何通知栏提示,这个需要在AndroidMainfest中添加权限android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
- request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) 表示下载允许的网络类型,默认在任何网络下都允许下载。有NETWORK_MOBILE、NETWORK_WIFI、NETWORK_BLUETOOTH三种及其组合可供选择。如果只允许wifi下载,而当前网络为3g,则下载会等待。
- request.setAllowedOverRoaming(boolean allow)移动网络情况下是否允许漫游。
- request.setMimeType() 设置下载文件的mineType。因为下载管理Ui中点击某个已下载完成文件及下载完成点击通知栏提示都会根据mimeType去打开文件。
- request.addRequestHeader(String header, String value)添加请求下载的网络链接的http头,比如User-Agent,gzip压缩等
class DownloadChangeObserver extends ContentObserver {
private Handler handler;
private long downloadId;
public DownloadChangeObserver(Handler handler, long downloadId) {
super(handler);
this.handler = handler;
this.downloadId = downloadId;
}
@Override
public void onChange(boolean selfChange) {
updateView(handler, downloadId);
}
}
mContext.getContentResolver().registerContentObserver(
Uri.parse("content://downloads/my_downloads"),
true,
new DownloadChangeObserver(handler,downloadId)
);
public void updateView(Handler handler, long downloadId) {
// 获取状态和字节
int[] bytesAndStatus = getBytesAndStatus(downloadId);
//
handler.sendMessage(handler.obtainMessage(0, bytesAndStatus[0],
bytesAndStatus[1], bytesAndStatus[2]));
}
public int[] getBytesAndStatus(long downloadId) {
int[] bytesAndStatus = new int[] { -1, -1, 0 };
DownloadManager.Query query = new DownloadManager.Query()
.setFilterById(downloadId);
Cursor c = null;
try {
c = downloadManager.query(query);
if (c != null && c.moveToFirst()) {
// 当前下载的字节
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
// 总字节数
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
// 状态
bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
} finally {
if (c != null) {
c.close();
}
}
return bytesAndStatus;
}
registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
Log.v("chadm", "action = " + action);
}
}
};
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;
}
/**
* Inserts a row in the database
*/
@Override
public Uri insert(final Uri uri, final ContentValues values) {
//检查权限,如果没有相应权限,则remove相关数据,插入一条空记录
checkInsertPermissions(values);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
// note we disallow inserting into ALL_DOWNLOADS
int match = sURIMatcher.match(uri);
//判断Uri,只支持对MY_DOWNLOADS的插入操作
if (match != MY_DOWNLOADS) {
Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);
throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
}
// copy some of the input values as it
ContentValues filteredValues = new ContentValues();
copyString(Downloads.Impl.COLUMN_URI, values, filteredValues);
copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues);
copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues);
copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues);
copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues);
copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues);
boolean isPublicApi =
values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE;
// validate the destination column
Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION);
if (dest != null) {
if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
!= PackageManager.PERMISSION_GRANTED
&& (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION
|| dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
|| dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) {
throw new SecurityException("setting destination to : " + dest +
" not allowed, unless PERMISSION_ACCESS_ADVANCED is granted");
}
// for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically
// switch to non-purgeable download
boolean hasNonPurgeablePermission =
getContext().checkCallingPermission(
Downloads.Impl.PERMISSION_CACHE_NON_PURGEABLE)
== PackageManager.PERMISSION_GRANTED;
if (isPublicApi && dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE
&& hasNonPurgeablePermission) {
dest = Downloads.Impl.DESTINATION_CACHE_PARTITION;
}
if (dest == Downloads.Impl.DESTINATION_FILE_URI) {
getContext().enforcePermission(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
Binder.getCallingPid(), Binder.getCallingUid(),
"need WRITE_EXTERNAL_STORAGE permission to use DESTINATION_FILE_URI");
checkFileUriDestination(values);
} else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) {
getContext().enforcePermission(
android.Manifest.permission.ACCESS_CACHE_FILESYSTEM,
Binder.getCallingPid(), Binder.getCallingUid(),
"need ACCESS_CACHE_FILESYSTEM permission to use system cache");
}
filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest);
}
// validate the visibility column
Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY);
if (vis == null) {
if (dest == Downloads.Impl.DESTINATION_EXTERNAL) {
filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
} else {
filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
Downloads.Impl.VISIBILITY_HIDDEN);
}
} else {
filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis);
}
// copy the control column as is
copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
/*
* requests coming from
* DownloadManager.addCompletedDownload(String, String, String,
* boolean, String, String, long) need special treatment
*/
if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==
Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
// these requests always are marked as 'completed'
filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES,
values.getAsLong(Downloads.Impl.COLUMN_TOTAL_BYTES));
filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues);
copyString(Downloads.Impl._DATA, values, filteredValues);
copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues);
} else {
filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
}
// set lastupdate to current time
long lastMod = mSystemFacade.currentTimeMillis();
filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, lastMod);
// use packagename of the caller to set the notification columns
String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
if (pckg != null && (clazz != null || isPublicApi)) {
int uid = Binder.getCallingUid();
try {
if (uid == 0 || mSystemFacade.userOwnsPackage(uid, pckg)) {
filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg);
if (clazz != null) {
filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz);
}
}
} catch (PackageManager.NameNotFoundException ex) {
/* ignored for now */
}
}
// copy some more columns as is
copyString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, values, filteredValues);
copyString(Downloads.Impl.COLUMN_COOKIE_DATA, values, filteredValues);
copyString(Downloads.Impl.COLUMN_USER_AGENT, values, filteredValues);
copyString(Downloads.Impl.COLUMN_REFERER, values, filteredValues);
// UID, PID columns
if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
== PackageManager.PERMISSION_GRANTED) {
copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues);
}
filteredValues.put(Constants.UID, Binder.getCallingUid());
if (Binder.getCallingUid() == 0) {
copyInteger(Constants.UID, values, filteredValues);
}
// copy some more columns as is
copyStringWithDefault(Downloads.Impl.COLUMN_TITLE, values, filteredValues, "");
copyStringWithDefault(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues, "");
// is_visible_in_downloads_ui column
if (values.containsKey(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI)) {
copyBoolean(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, values, filteredValues);
} else {
// by default, make external downloads visible in the UI
boolean isExternal = (dest == null || dest == Downloads.Impl.DESTINATION_EXTERNAL);
filteredValues.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, isExternal);
}
// public api requests and networktypes/roaming columns
if (isPublicApi) {
copyInteger(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, values, filteredValues);
copyBoolean(Downloads.Impl.COLUMN_ALLOW_ROAMING, values, filteredValues);
copyBoolean(Downloads.Impl.COLUMN_ALLOW_METERED, values, filteredValues);
}
if (Constants.LOGVV) {
Log.v(Constants.TAG, "initiating download with UID "
+ filteredValues.getAsInteger(Constants.UID));
if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) {
Log.v(Constants.TAG, "other UID " +
filteredValues.getAsInteger(Downloads.Impl.COLUMN_OTHER_UID));
}
}
//将数据插入到DB里面
long rowID = db.insert(DB_TABLE, null, filteredValues);
if (rowID == -1) {
Log.d(Constants.TAG, "couldn't insert into downloads database");
return null;
}
//将请求头数据插入到DB里面
insertRequestHeaders(db, rowID, values);
//通知有内容改变
notifyContentChanged(uri, match);
// Always start service to handle notifications and/or scanning
final Context context = getContext();
//启动DownloadService开始下载
context.startService(new Intent(context, DownloadService.class));
return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int returnValue = super.onStartCommand(intent, flags, startId);
if (Constants.LOGVV) {
Log.v(Constants.TAG, "Service onStart");
}
mLastStartId = startId;
enqueueUpdate();
return returnValue;
}
private void enqueueUpdate() {
mUpdateHandler.removeMessages(MSG_UPDATE);
mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget();
}
private Handler.Callback mUpdateCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
final int startId = msg.arg1;
if (DEBUG_LIFECYCLE) Log.v(TAG, "Updating for startId " + startId);
// Since database is current source of truth, our "active" status
// depends on database state. We always get one final update pass
// once the real actions have finished and persisted their state.
// TODO: switch to asking real tasks to derive active state
// TODO: handle media scanner timeouts
final boolean isActive;
synchronized (mDownloads) {
isActive = updateLocked();
}
if (msg.what == MSG_FINAL_UPDATE) {
// Dump thread stacks belonging to pool
for (Map.Entry<Thread, StackTraceElement[]> entry :
Thread.getAllStackTraces().entrySet()) {
if (entry.getKey().getName().startsWith("pool")) {
Log.d(TAG, entry.getKey() + ": " + Arrays.toString(entry.getValue()));
}
}
// Dump speed and update details
mNotifier.dumpSpeeds();
Log.wtf(TAG, "Final update pass triggered, isActive=" + isActive
+ "; someone didn't update correctly.");
}
if (isActive) {
// Still doing useful work, keep service alive. These active
// tasks will trigger another update pass when they're finished.
// Enqueue delayed update pass to catch finished operations that
// didn't trigger an update pass; these are bugs.
enqueueFinalUpdate();
} else {
// No active tasks, and any pending update messages can be
// ignored, since any updates important enough to initiate tasks
// will always be delivered with a new startId.
if (stopSelfResult(startId)) {
if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");
getContentResolver().unregisterContentObserver(mObserver);
mScanner.shutdown();
mUpdateThread.quit();
}
}
return true;
}
};
private boolean updateLocked() {
final long now = mSystemFacade.currentTimeMillis();
boolean isActive = false;
long nextActionMillis = Long.MAX_VALUE;
final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet());
final ContentResolver resolver = getContentResolver();
final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
null, null, null, null);
try {
//更新DB里面所有下载记录
final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID);
while (cursor.moveToNext()) {
final long id = cursor.getLong(idColumn);
staleIds.remove(id);
DownloadInfo info = mDownloads.get(id);
//如果下载信息保存在mDownloads里面,则直接更新,由于我们是新添加的一个任务,info为空,走insertDownloadLocked这一步
if (info != null) {
updateDownload(reader, info, now);
} else {
//创建一个新的DownloadInfo,然后添加到mDownloads里面去
info = insertDownloadLocked(reader, now);
}
if (info.mDeleted) {
// Delete download if requested, but only after cleaning up
if (!TextUtils.isEmpty(info.mMediaProviderUri)) {
resolver.delete(Uri.parse(info.mMediaProviderUri), null, null);
}
deleteFileIfExists(info.mFileName);
resolver.delete(info.getAllDownloadsUri(), null, null);
} else {
// Kick off download task if ready 准备开始下载
final boolean activeDownload = info.startDownloadIfReady(mExecutor);
// Kick off media scan if completed
final boolean activeScan = info.startScanIfReady(mScanner);
if (DEBUG_LIFECYCLE && (activeDownload || activeScan)) {
Log.v(TAG, "Download " + info.mId + ": activeDownload=" + activeDownload
+ ", activeScan=" + activeScan);
}
isActive |= activeDownload;
isActive |= activeScan;
}
// Keep track of nearest next action
nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis);
}
} finally {
cursor.close();
}
// Clean up stale downloads that disappeared
for (Long id : staleIds) {
deleteDownloadLocked(id);
}
// Update notifications visible to user
mNotifier.updateWith(mDownloads.values());
// Set alarm when next action is in future. It's okay if the service
// continues to run in meantime, since it will kick off an update pass.
if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) {
if (Constants.LOGV) {
Log.v(TAG, "scheduling start in " + nextActionMillis + "ms");
}
final Intent intent = new Intent(Constants.ACTION_RETRY);
intent.setClass(this, DownloadReceiver.class);
mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + nextActionMillis,
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT));
}
return isActive;
}
public boolean startDownloadIfReady(ExecutorService executor) {
synchronized (this) {
//判断是否可以下载,由于mControl为0,返回true
final boolean isReady = isReadyToDownload();
//判断是否有任务正在进行,对象是新创建的,mSubmittedTask 为空
final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
if (isReady && !isActive) {
//如果当前状态不是正在下载,将当前状态更新为正在下载
if (mStatus != Impl.STATUS_RUNNING) {
mStatus = Impl.STATUS_RUNNING;
ContentValues values = new ContentValues();
values.put(Impl.COLUMN_STATUS, mStatus);
mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
}
//开始下载任务
mTask = new DownloadThread(
mContext, mSystemFacade, this, mStorageManager, mNotifier);
mSubmittedTask = executor.submit(mTask);
}
return isReady;
}
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
runInternal();
} finally {
mNotifier.notifyDownloadSpeed(mInfo.mId, 0);
}
}
private void runInternal() {
// Skip when download already marked as finished; this download was
// probably started again while racing with UpdateThread.
if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mInfo.mId)
== Downloads.Impl.STATUS_SUCCESS) {
Log.d(TAG, "Download " + mInfo.mId + " already finished; skipping");
return;
}
State state = new State(mInfo);
PowerManager.WakeLock wakeLock = null;
int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
int numFailed = mInfo.mNumFailed;
String errorMsg = null;
final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext);
final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
try {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
wakeLock.setWorkSource(new WorkSource(mInfo.mUid));
wakeLock.acquire();
// while performing download, register for rules updates
netPolicy.registerListener(mPolicyListener);
Log.i(Constants.TAG, "Download " + mInfo.mId + " starting");
// Remember which network this download started on; used to
// determine if errors were due to network changes.
final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
if (info != null) {
state.mNetworkType = info.getType();
}
// Network traffic on this thread should be counted against the
// requesting UID, and is tagged with well-known value.
TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD);
TrafficStats.setThreadStatsUid(mInfo.mUid);
try {
// TODO: migrate URL sanity checking into client side of API
state.mUrl = new URL(state.mRequestUri);
} catch (MalformedURLException e) {
throw new StopRequestException(STATUS_BAD_REQUEST, e);
}
//执行下载
executeDownload(state);
finalizeDestinationFile(state);
finalStatus = Downloads.Impl.STATUS_SUCCESS;
} catch (StopRequestException error) {
// remove the cause before printing, in case it contains PII
errorMsg = error.getMessage();
String msg = "Aborting request for download " + mInfo.mId + ": " + errorMsg;
Log.w(Constants.TAG, msg);
if (Constants.LOGV) {
Log.w(Constants.TAG, msg, error);
}
finalStatus = error.getFinalStatus();
// Nobody below our level should request retries, since we handle
// failure counts at this level.
if (finalStatus == STATUS_WAITING_TO_RETRY) {
throw new IllegalStateException("Execution should always throw final error codes");
}
// Some errors should be retryable, unless we fail too many times.
if (isStatusRetryable(finalStatus)) {
if (state.mGotData) {
numFailed = 1;
} else {
numFailed += 1;
}
if (numFailed < Constants.MAX_RETRIES) {
final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
if (info != null && info.getType() == state.mNetworkType
&& info.isConnected()) {
// Underlying network is still intact, use normal backoff
finalStatus = STATUS_WAITING_TO_RETRY;
} else {
// Network changed, retry on any next available
finalStatus = STATUS_WAITING_FOR_NETWORK;
}
}
}
// fall through to finally block
} catch (Throwable ex) {
errorMsg = ex.getMessage();
String msg = "Exception for id " + mInfo.mId + ": " + errorMsg;
Log.w(Constants.TAG, msg, ex);
finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
// falls through to the code that reports an error
} finally {
if (finalStatus == STATUS_SUCCESS) {
TrafficStats.incrementOperationCount(1);
}
TrafficStats.clearThreadStatsTag();
TrafficStats.clearThreadStatsUid();
cleanupDestination(state, finalStatus);
notifyDownloadCompleted(state, finalStatus, errorMsg, numFailed);
Log.i(Constants.TAG, "Download " + mInfo.mId + " finished with status "
+ Downloads.Impl.statusToString(finalStatus));
netPolicy.unregisterListener(mPolicyListener);
if (wakeLock != null) {
wakeLock.release();
wakeLock = null;
}
}
mStorageManager.incrementNumDownloadsSoFar();
}
/**
* Fully execute a single download request. Setup and send the request,
* handle the response, and transfer the data to the destination file.
*/
private void executeDownload(State state) throws StopRequestException {
state.resetBeforeExecute();
//设置下载文件相关信息,文件是否存在、是否从0开始下载还是接着下载
setupDestinationFile(state);
// skip when already finished; remove after fixing race in 5217390
if (state.mCurrentBytes == state.mTotalBytes) {
Log.i(Constants.TAG, "Skipping initiating request for download " +
mInfo.mId + "; already completed");
return;
}
while (state.mRedirectionCount++ < Constants.MAX_REDIRECTS) {
// Open connection and follow any redirects until we have a useful
// response with body.
HttpURLConnection conn = null;
try {
checkConnectivity();
conn = (HttpURLConnection) state.mUrl.openConnection();
conn.setInstanceFollowRedirects(false);
conn.setConnectTimeout(DEFAULT_TIMEOUT);
conn.setReadTimeout(DEFAULT_TIMEOUT);
addRequestHeaders(state, conn);
final int responseCode = conn.getResponseCode();
switch (responseCode) {
case HTTP_OK:
if (state.mContinuingDownload) {
throw new StopRequestException(
STATUS_CANNOT_RESUME, "Expected partial, but received OK");
}
processResponseHeaders(state, conn);
transferData(state, conn);
return;
case HTTP_PARTIAL:
if (!state.mContinuingDownload) {
throw new StopRequestException(
STATUS_CANNOT_RESUME, "Expected OK, but received partial");
}
transferData(state, conn);
return;
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
case HTTP_TEMP_REDIRECT:
final String location = conn.getHeaderField("Location");
state.mUrl = new URL(state.mUrl, location);
if (responseCode == HTTP_MOVED_PERM) {
// Push updated URL back to database
state.mRequestUri = state.mUrl.toString();
}
continue;
case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
throw new StopRequestException(
STATUS_CANNOT_RESUME, "Requested range not satisfiable");
case HTTP_UNAVAILABLE:
parseRetryAfterHeaders(state, conn);
throw new StopRequestException(
HTTP_UNAVAILABLE, conn.getResponseMessage());
case HTTP_INTERNAL_ERROR:
throw new StopRequestException(
HTTP_INTERNAL_ERROR, conn.getResponseMessage());
default:
StopRequestException.throwUnhandledHttpError(
responseCode, conn.getResponseMessage());
}
} catch (IOException e) {
// Trouble with low-level sockets
throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e);
} finally {
if (conn != null) conn.disconnect();
}
}
throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects");
}
private void addRequestHeaders(State state, HttpURLConnection conn) {
for (Pair<String, String> header : mInfo.getHeaders()) {
conn.addRequestProperty(header.first, header.second);
}
// Only splice in user agent when not already defined
if (conn.getRequestProperty("User-Agent") == null) {
conn.addRequestProperty("User-Agent", userAgent());
}
// Defeat transparent gzip compression, since it doesn't allow us to
// easily resume partial downloads.
conn.setRequestProperty("Accept-Encoding", "identity");
if (state.mContinuingDownload) {
if (state.mHeaderETag != null) {
conn.addRequestProperty("If-Match", state.mHeaderETag);
}
conn.addRequestProperty("Range", "bytes=" + state.mCurrentBytes + "-");
}
}
public void onReceive(final Context context, final Intent intent) {
if (mSystemFacade == null) {
mSystemFacade = new RealSystemFacade(context);
}
String action = intent.getAction();
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
if (Constants.LOGVV) {
Log.v(Constants.TAG, "Received broadcast intent for " +
Intent.ACTION_BOOT_COMPLETED);
}
startService(context);
} else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
if (Constants.LOGVV) {
Log.v(Constants.TAG, "Received broadcast intent for " +
Intent.ACTION_MEDIA_MOUNTED);
}
startService(context);
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
final ConnectivityManager connManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo info = connManager.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
startService(context);
}
} else if (action.equals(Constants.ACTION_RETRY)) {
startService(context);
} else if (action.equals(Constants.ACTION_OPEN)
|| action.equals(Constants.ACTION_LIST)
|| action.equals(Constants.ACTION_HIDE)) {
final PendingResult result = goAsync();
if (result == null) {
// TODO: remove this once test is refactored
handleNotificationBroadcast(context, intent);
} else {
sAsyncHandler.post(new Runnable() {
@Override
public void run() {
handleNotificationBroadcast(context, intent);
result.finish();
}
});
}
}
}
Android 下载模块分析(DownloadManager和DownloadProvider)的更多相关文章
- android Camera模块分析
Android Camera Module Architecture and Bottom layer communication mechanism ----------- ...
- Android WIFI模块分析
一:什么是WIFI WIFI是一种无线连接技术.可用于手机.电脑.PDA等终端. WIFI技术产生的目的是改善基于IEEE802.11标准的无线网络产品之间的互通性,也就是说WIFI是基于802.11 ...
- android 新闻应用、Xposed模块、酷炫的加载动画、下载模块、九宫格控件等源码
Android精选源码 灵活的ShadowView,可替代CardView使用 基于Tesseract-OCR实现自动扫描识别手机号 Android播放界面仿QQ音乐开源音乐播放器 新闻应用项目采用了 ...
- Android平台APK分析工具包androguard的部署使用和原理分析
原创文章,转载请注明出处,谢谢. Android应用程序分析主要有静态分析和动态分析两种,常见的静态分析工具是Apktool.dex2jar以及jdgui.今天突然主要到Google code上有个叫 ...
- Qualcomm Android display架构分析
Android display架构分析(一) http://blog.csdn.net/BonderWu/archive/2010/08/12/5805961.aspx http://hi.baidu ...
- android usb挂载分析---MountService启动
android usb挂载分析---MountService启动 分类: android框架 u盘挂载2012-03-27 23:00 11799人阅读 评论(4) 收藏 举报 androidsock ...
- 高通Android display架构分析
目录(?)[-] Kernel Space Display架构介绍 函数和数据结构介绍 函数和数据结构介绍 函数和数据结构介绍 数据流分析 初始化过程分析 User Space display接口 K ...
- React Native Android原生模块开发实战|教程|心得|怎样创建React Native Android原生模块
尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 告诉大家一个好消息. ...
- ZT 4.3 android bluetooth hfp分析
4.3 android bluetooth hfp分析 2013-08-20 20:16 592人阅读 评论(3) 收藏 举报 所有程序执行的代码都是有入口的,在这里我们暂时分析一种情景,蓝牙打开着, ...
随机推荐
- Flex 日志管理
在Flex中调试方法有两种: 一是用trace()函数,在flex builder中进行调试: 二是用logTarget类,例如以下代码: // Create a target. var logTar ...
- 题目1380:lucky number
转载请注明文本链接 http://blog.csdn.net/yangnanhai93/article/details/40441709 题目链接地址:http://ac.jobdu.com/prob ...
- C#中ISpostback
响应客户端控件时ispostback为true 代码: using System; using System.Collections.Generic; using System.Linq; using ...
- passenger安装nginx
1.更换淘宝gem gem sources --remove https://rubygems.org/ gem sources -a https://ruby.taobao.org/ 2.gem安装 ...
- MongoDB学习笔记<四>
今天继续学习MongoDB的相关知识,主要包含例如以下: --find具体解释 --分页与排序 --游标和其它知识 1.指定返回的键 db.person.find({},{"_id" ...
- Asp.net MVC + EF + Spring.Net 项目实践(三)
这一篇要整合Model层和Repository层,提供一个统一的操作entity的接口层,代码下载地址(博客园上传不了10M以上的文件,所以用了百度):http://pan.baidu.com/s/1 ...
- C---通过指针访问数组
C语言规定:如果指针变量P已指向数组中的一个元素,则P+1指向同一数组中的下一个元素. 引入指针变量后,就可以用俩种方法来访问数组元素了. 如果p的初值为&a[0],则: P+i 和a+i 就 ...
- Oracle OS认证和口令文件认证方法
OS认证 1.在SQLNET.ORA(位于$ORACLE_HOME/NETWORK/ADMIN文件夹中)文件里,使用vi编辑,凝视掉#SQLNET.AUTHENTICATION_SERVICES = ...
- Bash shell 简单的并行任务,并等待
首先启动两个command line对于实验 第一 command line 依次输入: bash$ sleep 10001 & [1] 38272 bash$ job1=$! bash$ s ...
- linux下mysql的远程连接
在服务器上安装mysql后,想使用本地的mysql客户端连接数据库时,提示不允许连接,比较郁闷,找到了这篇文章解决了我的问题: 内容如下: 本地计算机ip:192.168.1.100远程计算机ip:1 ...