Android开发

Android DownloadProvider 源码详解

本文主要是介绍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

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

这篇关于Android DownloadProvider 源码详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!