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
   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的函数中,我们会向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源码分析
    原文地址: https://blog.csdn.net/chaoy1116/article/details/22384841
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞