Android DownloadProvider 源码详解_Android

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
源码分析
android源码详解、android path源码详解、downloadprovider、downloadprovider.apk、downloadprovider下载,以便于您获取更多的相关知识。

时间: 2024-10-31 06:30:25

Android DownloadProvider 源码详解_Android的相关文章

Android Matrix源码详解_Android

Matrix的数学原理 在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类.Android中的Matrix是一个3 x 3的矩阵,其内容如下:  Matrix的对图像的处理可分为四类基本变换: Translate           平移变换 Rotate                旋转变换 Scale                  缩放变换 Skew                  错切变换 从字面上理解,矩阵中的MSCALE用于处理缩放变换,MS

Android DownloadProvider 源码详解

Android DownloadProvider 源码分析: Download的源码编译分为两个部分,一个是DownloadProvider.apk, 一个是DownloadProviderUi.apk. 这两个apk的源码分别位于 packages/providers/DownloadProvider/ui/src packages/providers/DownloadProvider/src 其中,DownloadProvider的部分是下载逻辑的实现,而DownloadProviderUi

Android实现屏幕锁定源码详解_Android

最近有朋友问屏幕锁定的问题,自己也在学习,网上找了下也没太详细的例子,看的资料书上也没有有关屏幕锁定程序的介绍,下个小决心,自己照着官方文档学习下,现在做好了,废话不多说,先发下截图,看下效果,需要注意的地方会加注释,有问题的朋友可以直接留言,我们共同学习交流,共同提高进步!直接看效果图: 一:未设置密码时进入系统设置的效果图如下:   二:设置密码方式预览: 三:密码解密效果图 四:九宫格解密时的效果图 下面来简单的看下源码吧,此处讲下,这个小DEMO也是临时学习下的,有讲的不明白的地方请朋友

Android Matrix源码详解

Matrix的数学原理 在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类.Android中的Matrix是一个3 x 3的矩阵,其内容如下: Matrix的对图像的处理可分为四类基本变换: Translate           平移变换 Rotate                旋转变换 Scale                  缩放变换 Skew                  错切变换 从字面上理解,矩阵中的MSCALE用于处理缩放变换,MSK

Android实现屏幕锁定源码详解

最近有朋友问屏幕锁定的问题,自己也在学习,网上找了下也没太详细的例子,看的资料书上也没有有关屏幕锁定程序的介绍,下个小决心,自己照着官方文档学习下,现在做好了,废话不多说,先发下截图,看下效果,需要注意的地方会加注释,有问题的朋友可以直接留言,我们共同学习交流,共同提高进步!直接看效果图: 一:未设置密码时进入系统设置的效果图如下: 二:设置密码方式预览: 三:密码解密效果图 四:九宫格解密时的效果图 下面来简单的看下源码吧,此处讲下,这个小DEMO也是临时学习下的,有讲的不明白的地方请朋友直接

走近VB.Net(三) 源码详解—运用颜色的初步探讨

详解 走近VB.Net(三) 源码详解-运用颜色的初步探讨 新建一个工程,加入以下控件1. label控件:label1,label2,labred ,labblue,labgreen2. picturebox控件:picturebox13. button控件:Button1设置form1的opacity属性为80%,设置透明的窗体在office中抓取一幅取色图片存为bmp格式,并设为picturebox1的backgroundimage,即背景图片Option Strict Off '关闭 s

Thrift的TProtocol类体系原理及源码详解:紧凑协议类TCompactProtocolT(TCom

Thrift的TProtocol类体系原理及源码详解:紧凑协议类TCompactProtocolT(TCompactProtocol) 这个协议类采用了zigzag 编码,这种编码是基于Variable-length quantity编码提出来 的,因为Variable-length quantity编码对于负数的编码都需要很长的字节数,而zigzag 编 码对于绝对值小的数字,无论正负都可以采用较少的字节来表示,充分利用了 Varint技术. 所以这个协议类采用zigzag 编码可以节省传输空

Thrift的TProtocol类体系原理及源码详解:二进制协议类TBinaryProtocolT(TBi

Thrift的TProtocol类体系原理及源码详解:二进制协议类TBinaryProtocolT(TBinaryProtocol) 这个协议是Thrift支持的默认二进制协议,它以二进制的格式写所有的数据,基本上直接 发送原始数据.因为它直接从TVirtualProtocol类继承,而且是一个模板类.它的模板参数 就是一个封装具体传输发送的类,这个类才是真正实现数据传输的.这个类的定义上一节举 例已经出现过了就不在列出来了. 下面我就结合scribe的Log函数执行的具体过程来 分析使用这个协

Android实现动画效果详解_Android

目前Android平台提供了两类动画一类是Tween动画,第二类就是 Frame动画,具体内容介绍请看下文: 一类是Tween动画,就是对场景里的对象不断的进行图像变化来产生动画效果(旋转.平移.放缩和渐变). 第二类就是 Frame动画,即顺序的播放事先做好的图像,与gif图片原理类似. 实现动画有两种方式:一种使用XML文件(文件放在res/anim),一种直接代码搞定  1.透明度控制动画效果alpha <!-- 透明度控制动画效果alpha 浮点型值: fromAlpha 动画起始时透明