软件编程
位置:首页>> 软件编程>> Android编程>> Android DownloadProvider 源码详解

Android DownloadProvider 源码详解

作者:极度寒冰  发布时间:2023-02-11 03:26:17 

标签:Android,DownloadProvider,源码

Android DownloadProvider 源码分析:

Download的源码编译分为两个部分,一个是DownloadProvider.apk, 一个是DownloadProviderUi.apk.

这两个apk的源码分别位于

packages/providers/DownloadProvider/ui/src
packages/providers/DownloadProvider/src

其中,DownloadProvider的部分是下载逻辑的实现,而DownloadProviderUi是界面部分的实现。

然后DownloadProvider里面的下载虽然主要是通过DownloadService进行的操作,但是由于涉及到Notification的更新,下载进度的展示,下载的管理等。

所以还是有不少其它的类来分别进行操作。

DownloadProvider --  数据库操作的封装,继承自ContentProvider;
DownloadManager -- 大部分逻辑是进一步封装数据操作,供外部调用;
DownloadService -- 封装文件download,delete等操作,并且操纵下载的norification;继承自Service;
DownloadNotifier -- 状态栏Notification逻辑;
DownloadReceiver -- 配合DownloadNotifier进行文件的操作及其Notification;
DownloadList -- Download app主界面,文件界面交互;

下载一般是从Browser里面点击链接开始,我们先来看一下Browser中的代码

在browser的src/com/Android/browser/DownloadHandler.Java函数中,我们可以看到一个很完整的Download的调用,我们在写自己的app的时候,也可以对这一段进行参考:


public static void startingDownload(Activity activity,
   String url, String userAgent, String contentDisposition,
   String mimetype, String referer, boolean privateBrowsing, long contentLength,
   String filename, String downloadPath) {
 // java.net.URI is a lot stricter than KURL so we have to encode some
 // extra characters. Fix for b 2538060 and b 1634719
 WebAddress webAddress;
 try {
   webAddress = new WebAddress(url);
   webAddress.setPath(encodePath(webAddress.getPath()));
 } catch (Exception e) {
   // This only happens for very bad urls, we want to chatch the
   // exception here
   Log.e(LOGTAG, "Exception trying to parse url:" + url);
   return;
 }

String addressString = webAddress.toString();
 Uri uri = Uri.parse(addressString);
 final DownloadManager.Request request;
 try {
   request = new DownloadManager.Request(uri);
 } catch (IllegalArgumentException e) {
   Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();
   return;
 }
 request.setMimeType(mimetype);
 // set downloaded file destination to /sdcard/Download.
 // or, should it be set to one of several Environment.DIRECTORY* dirs
 // depending on mimetype?
 try {
   setDestinationDir(downloadPath, filename, request);
 } catch (Exception e) {
   showNoEnoughMemoryDialog(activity);
   return;
 }
 // let this downloaded file be scanned by MediaScanner - so that it can
 // show up in Gallery app, for example.
 request.allowScanningByMediaScanner();
 request.setDescription(webAddress.getHost());
 // XXX: Have to use the old url since the cookies were stored using the
 // old percent-encoded url.
 String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);
 request.addRequestHeader("cookie", cookies);
 request.addRequestHeader("User-Agent", userAgent);
 request.addRequestHeader("Referer", referer);
 request.setNotificationVisibility(
     DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 final DownloadManager manager = (DownloadManager) activity
     .getSystemService(Context.DOWNLOAD_SERVICE);
 new Thread("Browser download") {
   public void run() {
     manager.enqueue(request);
   }
 }.start();
 showStartDownloadToast(activity);
}

在这个操作中,我们看到添加了request的各种参数,然后最后调用了DownloadManager的enqueue进行下载,并且在开始后,弹出了开始下载的这个toast。manager是一个DownloadManager的实例,DownloadManager是存在与frameworks/base/core/java/android/app/DownloadManager.java。可以看到enqueue的实现为:


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;

enqueue函数主要是将Rquest实例分解组成一个ContentValues实例,并且添加到数据库中,函数返回插入的这条数据返回的ID;ContentResolver.insert函数会调用到DownloadProvider实现的ContentProvider的insert函数中去,如果我们去查看insert的code的话,我们可以看到操作是很多的。但是我们只需要关注几个关键的部分:


......
//将相关的请求参数,配置等插入到downloads数据库;
long rowID = db.insert(DB_TABLE, null, filteredValues);
......
//将相关的请求参数,配置等插入到request_headers数据库中;
insertRequestHeaders(db, rowID, values);
......
if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==
       Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
     // When notification is requested, kick off service to process all
     // relevant downloads.
//启动DownloadService进行下载及其它工作
     if (Downloads.Impl.isNotificationToBeDisplayed(vis)) {
       context.startService(new Intent(context, DownloadService.class));
     }
   } else {
     context.startService(new Intent(context, DownloadService.class));
   }
   notifyContentChanged(uri, match);
   return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);

在这边,我们就可以看到下载的DownloadService的调用了。因为是一个startService的方法,所以我们在DownloadService里面,是要去走oncreate的方法的。


@Override
public void onCreate() {
 super.onCreate();
 if (Constants.LOGVV) {
   Log.v(Constants.TAG, "Service onCreate");
 }

if (mSystemFacade == null) {
   mSystemFacade = new RealSystemFacade(this);
 }

mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
 mStorageManager = new StorageManager(this);

mUpdateThread = new HandlerThread(TAG + "-UpdateThread");
 mUpdateThread.start();
 mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);
 mScanner = new DownloadScanner(this);
 mNotifier = new DownloadNotifier(this);
 mNotifier.cancelAll();

mObserver = new DownloadManagerContentObserver();
 getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
     true, mObserver);
}

这边的话,我们可以看到先去启动了一个handler去接收callback的处理


mUpdateThread = new HandlerThread(TAG + "-UpdateThread");
mUpdateThread.start();
mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);

然后去


getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
       true, mObserver)

是去注册监听Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI的Observer。
而oncreate之后,就会去调用onStartCommand方法.


@Override
ublic 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的函数中,我们会向mUpdateHandler发送一个MSG_UPDATE Message,


private void enqueueUpdate() {
 mUpdateHandler.removeMessages(MSG_UPDATE);
 mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget();
}

mUpdateCallback中接收到并且处理:


private Handler.Callback mUpdateCallback = new Handler.Callback() {
   @Override
   public boolean handleMessage(Message msg) {
     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
     final int startId = msg.arg1;
     final boolean isActive;
     synchronized (mDownloads) {
       isActive = updateLocked();
     }
     ......
     if (isActive) {
//如果Active,则会在Delayed 5×60000ms后发送MSG_FINAL_UPDATE Message,主要是为了“any finished operations that didn't trigger an update pass.”
       enqueueFinalUpdate();
     } else {
//如果没有Active的任务正在进行,就会停止Service以及其它
       if (stopSelfResult(startId)) {
         if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");
         getContentResolver().unregisterContentObserver(mObserver);
         mScanner.shutdown();
         mUpdateThread.quit();
       }
     }
     return true;
   }
 };

这边的重点是updateLocked()函数



private boolean updateLocked() {
   final long now = mSystemFacade.currentTimeMillis();

boolean isActive = false;
   long nextActionMillis = Long.MAX_VALUE;
//mDownloads初始化是一个空的Map<Long, DownloadInfo>
   final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet());

final ContentResolver resolver = getContentResolver();
//获取所有的DOWNLOADS任务
   final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
       null, null, null, null);
   try {
     final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
     final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID);
//迭代Download Cusor
     while (cursor.moveToNext()) {
       final long id = cursor.getLong(idColumn);
       staleIds.remove(id);

DownloadInfo info = mDownloads.get(id);
//开始时,mDownloads是没有任何内容的,info==null
       if (info != null) {
//从数据库更新最新的Download info信息,来监听数据库的改变并且反应到界面上
         updateDownload(reader, info, now);
       } else {
//添加新下载的Dwonload info到mDownloads,并且从数据库读取新的Dwonload info
         info = insertDownloadLocked(reader, now);
       }
//这里的mDeleted参数表示的是当我删除了正在或者已经下载的内容时,首先数据库会update这个info.mDeleted为true,而不是直接删除文件
       if (info.mDeleted) {
//不详细解释delete函数,主要是删除数据库内容和现在文件内容
         if (!TextUtils.isEmpty(info.mMediaProviderUri)) {
       resolver.delete(Uri.parse(info.mMediaProviderUri), null, null);
         }
         deleteFileIfExists(info.mFileName);
         resolver.delete(info.getAllDownloadsUri(), null, null);

} else {
         // 开始下载文件
         final boolean activeDownload = info.startDownloadIfReady(mExecutor);

// 开始media scanner
         final boolean activeScan = info.startScanIfReady(mScanner);
         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());
   if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) {
     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;
 }

重点来看看文件的下载,startDownloadIfReady函数:



public boolean startDownloadIfReady(ExecutorService executor) {
   synchronized (this) {
     final boolean isReady = isReadyToDownload();
     final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
     if (isReady && !isActive) {
//更新数据库的任务状态为STATUS_RUNNING
       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;
   }
 }

在DownloadThread的处理中,如果HTTP的状态是ok的话,会去进行transferDate的处理。


private void transferData(State state, HttpURLConnection conn) throws StopRequestException {
......
in = conn.getInputStream();
......
//获取InputStream和OutPutStream
if (DownloadDrmHelper.isDrmConvertNeeded(state.mMimeType)) {
         drmClient = new DrmManagerClient(mContext);
         final RandomAccessFile file = new RandomAccessFile(
             new File(state.mFilename), "rw");
         out = new DrmOutputStream(drmClient, file, state.mMimeType);
         outFd = file.getFD();
       } else {
         out = new FileOutputStream(state.mFilename, true);
         outFd = ((FileOutputStream) out).getFD();
       }
......
// Start streaming data, periodically watch for pause/cancel
     // commands and checking disk space as needed.
     transferData(state, in, out);
......
}

------


private void transferData(State state, InputStream in, OutputStream out)
     throws StopRequestException {
   final byte data[] = new byte[Constants.BUFFER_SIZE];
   for (;;) {
//从InputStream中读取内容信息,“in.read(data)”,并且对数据库中文件下载大小进行更新
     int bytesRead = readFromResponse(state, data, in);
     if (bytesRead == -1) { // success, end of stream already reached
       handleEndOfStream(state);
       return;
     }
     state.mGotData = true;
//利用OutPutStream写入读取的InputStream,"out.write(data, 0, bytesRead)"
     writeDataToDestination(state, data, bytesRead, out);
     state.mCurrentBytes += bytesRead;
     reportProgress(state);
     }
     checkPausedOrCanceled(state);
   }
 }

至此,下载文件的流程就说完了,继续回到DownloadService的updateLocked()函数中来;重点来分析DownloadNotifier的updateWith()函数,这个方法用来更新Notification


//这段代码是根据不同的状态设置不同的Notification的icon
if (type == TYPE_ACTIVE) {
       builder.setSmallIcon(android.R.drawable.stat_sys_download);
     } else if (type == TYPE_WAITING) {
       builder.setSmallIcon(android.R.drawable.stat_sys_warning);
     } else if (type == TYPE_COMPLETE) {
       builder.setSmallIcon(android.R.drawable.stat_sys_download_done);
     }

//这段代码是根据不同的状态来设置不同的notification Intent
// Build action intents
     if (type == TYPE_ACTIVE || type == TYPE_WAITING) {
       // build a synthetic uri for intent identification purposes
       final Uri uri = new Uri.Builder().scheme("active-dl").appendPath(tag).build();
       final Intent intent = new Intent(Constants.ACTION_LIST,
           uri, mContext, DownloadReceiver.class);
       intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,
           getDownloadIds(cluster));
       builder.setContentIntent(PendingIntent.getBroadcast(mContext,
           0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
       builder.setOngoing(true);

} else if (type == TYPE_COMPLETE) {
       final DownloadInfo info = cluster.iterator().next();
       final Uri uri = ContentUris.withAppendedId(
           Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, info.mId);
       builder.setAutoCancel(true);

final String action;
       if (Downloads.Impl.isStatusError(info.mStatus)) {
         action = Constants.ACTION_LIST;
       } else {
         if (info.mDestination != Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) {
           action = Constants.ACTION_OPEN;
         } else {
           action = Constants.ACTION_LIST;
         }
       }

final Intent intent = new Intent(action, uri, mContext, DownloadReceiver.class);
       intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,
           getDownloadIds(cluster));
       builder.setContentIntent(PendingIntent.getBroadcast(mContext,
           0, intent, PendingIntent.FLAG_UPDATE_CURRENT));

final Intent hideIntent = new Intent(Constants.ACTION_HIDE,
           uri, mContext, DownloadReceiver.class);
       builder.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, hideIntent, 0));
     }



//这段代码是更新下载的Progress
if (total > 0) {
         final int percent = (int) ((current * 100) / total);
         percentText = res.getString(R.string.download_percent, percent);

if (speed > 0) {
           final long remainingMillis = ((total - current) * 1000) / speed;
           remainingText = res.getString(R.string.download_remaining,
               DateUtils.formatDuration(remainingMillis));
         }

builder.setProgress(100, percent, false);
       } else {
         builder.setProgress(100, 0, true);
       }

最后调用mNotifManager.notify(tag, 0, notif);根据不同的状态来设置不同的Notification的title和description

 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

来源:http://blog.csdn.net/chaoy1116/article/details/22384841

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com