Android下载模块主要有2个部分组成:DownloadManager和DownloadProvider;其中DownloadManager提供接口供调用,具体的实现是 DownloadProvider,包括相关数据信息的保存及文件下载。
DownloadManager是系统开放给第三方应用使用的类,包含两个静态内部类DownloadManager.Query和DownloadManager.Request。
- DownloadManager.Request用来请求一个下载
- DownloadManager.Query 用来查询下载信息
DownloadManager主要提供了一下主要方法
- enqueue(Request request):执行下载,返回downloadId,downloadId可用于查询下载信息。
- remove(long ids):删除下载,若下载中取消下载。会同时删除下载文件和记录。
- query(Query query)查询下载信息
- getMaxBytesOverMobile(Context context)通过移动网络下载的最大字节数
- getMimeTypeForDownloadedFile(long id)得到下载的mineType
通过查看代码我们可以发现还有个CursorTranslator私有静态内部类。这个类主要对Query做了一层代理。将DownloadProvider和DownloadManager之间做个映射。将DownloadProvider中的十几种状态对应到了DownloadManager中的五种状态,DownloadProvider中的失败、暂停原因转换为了DownloadManager的原因。
1.DownloadManager的一般用法
1.1 调用DownloadManager.Request开始下载
在开始之前,在AndroidManifest.xml中添加网络访问权限和sdcard写入权限。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
接着,调用DownloadManager.Request开始下载
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);
DownloadManager.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压缩等
1.2 下载进度查询
DownloadManager下载过程中,会将下载的数据和下载的状态插入ContentProvider中,所以我们可以通过注册一个ContentObserver,通过ContentObserver不断获取数据,对UI进行更新。
我们主要调用DownloadManager.Query()进行查询,DownloadManager.Query为下载管理对外开放的信息查询类,主要包括以下方法:
* setFilterById(long… ids)根据下载id进行过滤
* setFilterByStatus(int flags)根据下载状态进行过滤
* setOnlyIncludeVisibleInDownloadsUi(boolean value)根据是否在download ui中可见进行过滤。
* orderBy(String column, int direction)根据列进行排序,不过目前仅支持DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP和DownloadManager.COLUMN_TOTAL_SIZE_BYTES排序。
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);
}
}
注册ContentResolver
mContext.getContentResolver().registerContentObserver(
Uri.parse("content://downloads/my_downloads"),
true,
new DownloadChangeObserver(handler,downloadId)
);
updateView()方法,获取进度,通过handle发送消息,更新UI
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;
}
1.3 下载成功监听
下载完成后,下载管理会发出DownloadManager.ACTION_DOWNLOAD_COMPLETE这个广播,并传递downloadId作为参数。通过接受广播我们可以打开对下载完成的内容进行操作。
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);
}
}
};
2.下载流程分析
2.1使用DownloadManager启动下载流程
具体流程如时序图所示:
a.)enqueue执行一个下载任务
文件位置framewok/base/core/java/android/app/DownloadManager.java
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;
}
首先将Request对象中包含的信息转换成ContentValues对象,然后将ContentValues对象插入到DownloadProvider里面。此方法会返回一个long类型值,此值即为该条下载记录的id值
b.)insert,将数据插入到DB对应表里面
文件位置packages/providers/com.android.providers.downloads/DownloadProvider.java
/**
* 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);
}
DownloadProvider里面的insert方法里面,会先检测相关权限,如果有权限,将表中的各列依次赋值,然后插入到数据库对应表中,然后启动DownloadService开始下载。
c.)onStartCommand服务中开始下载任务
文件位置packages/providers/com.android.providers.downloads/DownloadService.java
@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;
}
服务启动后执行enqueueUpdate,此方法会发送一个MSG_UPDATE消息,
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;
}
};
看代码第18行,isActive = updateLocked();
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;
}
此方法会更新DB里面的所有下载记录,先获取所有记录,然后依次更新。当我们新添加一条记录时,会新创建一个DownloadInfo对象,并添加到mDownloads集合里面;然后调用DownloadInfo的startDownloadIfReady方法
d.) startDownloadIfReady 准备开始下载
文件位置packages/providers/com.android.providers.downloads/DownloadInfo.java
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;
}
}
此方法中,先判断当前状态是否可以下载,如果可以下载,则开始一个任务下载
e.) DownloadThread的run方法
文件位置packages/providers/com.android.providers.downloads/DownloadThread.java
@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();
}
启动任务,里面会调用runInternal,这个里面的逻辑很复杂,我们只关注executeDownload方法
/**
* 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");
}
addRequestHeaders方法里面也有一些比较重要的东西
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 + "-");
}
}
如果是继续下载,则把当前的下载进度放在请求头里面。
2.2 系统广播启动下载
在packages/providers/com.android.providers.downloads/DownloadReceiver.java文件里面
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();
}
});
}
}
}
注意:
当service第一次被启动时会调用onCreate()方法, 然后再调用onStartCommand()方法. 在该service的生命周期内, 如果再次启动这个service, 就会直接调用onStartCommand()方法了.