android系统浏览器下载流程

来源:互联网 发布:linux备份文件夹命令 编辑:程序博客网 时间:2024/06/04 18:20

android系统浏览器下载流程

标签: android browser download


简介

当我们用浏览器点开一个下载链接,然后去下载,从宏观上认识,有下载进度的实时更新和界面的跳转。整个过程中,主要涉及到以下过程。浏览器点击下载按钮,浏览器分发下去一个下载请求,跳转界面的同时在DownloadProvider进程中去真正的下载数据以及更新数据库,在界面上监听数据库的变化,去实时更新相关进度。全过程中,Browser进程负责分发下载请求,DownloadProvider进程负责真正的下载操作。

目前而言,主要有两种结构,C-S和B-S结构。对于Browser来说,主要在于对Webview这个控件的认识,底层的内核实现也是非常复杂,这里我们不做讨论。对于一个浏览器链接,webkit底层会去解析,同时也会判断这个链接属于什么类型。比如我们今天的这个下载链接,Browser就有专门的下载监听器去回调执行这个action,下面我们会详细分析。

WebView控件简单介绍

WebView控件提供了一个内嵌的浏览器试图,用于显示本地的html或网路上的网页。
并且比较强大的是,还可以直接跟js相互调用。
WebView有两个方法:setWebChromeClient和setWebClient
WebChromeClient:主要处理解析,渲染网页等浏览器做的事情,也是辅助WebView处理Javascript 的对话框,网站图标,网站title,加载进度等
WebViewClient :就是帮助WebView处理各种通知、请求事件的。

Browser下载的时序图。

这里写图片描述


下面来详细分析具体的代码实现细节,时序图是更加细节的步骤,这里我们着重分析下面的流程。


Step 1:Tab.setWebView

      void setWebView(WebView w, boolean restore) {        ....        mMainView = w;        // attach the WebViewClient, WebChromeClient and DownloadListener        if (mMainView != null) {            mMainView.setWebViewClient(mWebViewClient);            mMainView.setWebChromeClient(mWebChromeClient);            mMainView.setDownloadListener(mDownloadListener);            ....        }    }

这个方法定义在packages/apps/Browser/src/com/android/browser/Tab.java
浏览器是用过Webview来显示UI。这里设置了一个WebView对象,然后setWebViewClient和setWebChromeClient主要设置了对页面加载以及js的处理。这里我们只分析setDownloadListener这个监听,首先要理解一点,对于WebView上的一个下载按钮,它的事件是怎么处理的,浏览器如何判断这个是下载?以上其实浏览器内核已经处理,浏览器内核是根据指定的url判断该链接是否是一个下载链接,如果点击的是一个下载链接,那么最终会回调到该监听器中去处理,具体底层实现比较复杂,暂不作讨论。

 Tab(WebViewController wvcontroller, WebView w, Bundle state) {        /// M: add for save page        ....        mDownloadListener = new BrowserDownloadListener() {            public void onDownloadStart(String url, String userAgent,                    String contentDisposition, String mimetype, String referer,                    long contentLength) {                /// M: add for fix download page url                mCurrentState.mIsDownload = true;                mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,                        mimetype, referer, contentLength);            }        };        ....        setWebView(w);        ....    }

这个方法定义在packages/apps/Browser/src/com/android/browser/Tab.java
分析Tab的构造方法,这里主要看BrowserDownloadListener这个对象。当点击了下载按钮,则会去回调BrowserDownloadListener的onDownloadStart方法,这个最终是委托给了mWebViewController去处理。

Step 2:WebViewController.onDownloadStart

    @Override    public void onDownloadStart(Tab tab, String url, String userAgent,            String contentDisposition, String mimetype, String referer,            long contentLength) {        ....        DownloadHandler.onDownloadStart(mActivity, url, userAgent,                contentDisposition, mimetype, referer, false, contentLength);        ...    }

这个方法定义在packages/apps/Browser/src/com/android/browser/Controller.java
WebViewController是一个接口,Controller是它的具体实现,在onDownloadStart方法中,实现比较简单,直接是将参数委托给DownloadHandler的静态方法onDownloadStart去进一步处理。
在这里,参数:
url下载的网址链接
userAgent浏览器userAgent信息
mimetype下载内容的type类型
contentLength下载内容大小

Step 3:DownloadHandler.onDownloadStart

这个方法定义在packages/apps/Browser/src/com/android/browser/DownloadHandler.java
实现很简单,直接将参数继续传递到onDownloadStartNoStream方法。

Step 4:DownloadHandler.onDownloadStartNoStream

    /*package */    public static void onDownloadStartNoStream(Activity activity,            String url, String userAgent, String contentDisposition,            String mimetype, String referer, boolean privateBrowsing, long contentLength) {        ....        // 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 = new DownloadManager.Request(uri);        request.setMimeType(mimetype);        // 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);        request.setUserAgent(userAgent);        final DownloadManager manager = (DownloadManager)                           activity.getSystemService(Context.DOWNLOAD_SERVICE);        new Thread("Browser download") {                public void run() {                    manager.enqueue(request);                }        }.start();        /// M: Add to start Download activity. @{        Intent pageView = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);        pageView.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);        activity.startActivity(pageView);        /// @}    }

这个方法定义在packages/apps/Browser/src/com/android/browser/DownloadHandler.java
在该方法中,主要做了三件事
1.将下载信息url,minetype等封装成一个Request对象,供后续使用。
2.获取一个DownloadManager对象,将前面封装的Request对象,安排到下载队列
3.开始下载的同时,去跳转UI界面,同步显示UI信息。
这里我们重点分析数据流程这块,接下来分析enqueue这个方法的具体实现。

Step 5:DownloadManager.enqueue

    public long enqueue(Request request) {        ContentValues values = request.toContentValues(mPackageName);        Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);        if (downloadUri != null) {            long id = Long.parseLong(downloadUri.getLastPathSegment());            return id;        }        return -1;    }

这个方法定义在frameworks/base/core/java/android/app/DownloadManager.java
首先toContentValues将Request的信息要存数据库的字段转化为一个ContentValues对象,以上几步都是在Browser进程中进行的,接下来insert方法,通过uri开始最终跨进程请求去插入数据。这里Downloads.Impl.CONTENT_URI为content://downloads/my_downloads,从pacakges/providers/DownloadProvider的清单文件中很容易知道最终是调用了DownloadProvider的insert方法去插入数据。
pacakges/providers/DownloadProvider的清单文件如下:

  ....  <provider android:name=".DownloadProvider"                  android:authorities="downloads" android:exported="true">  ....

Step 6:DownloadProvider.insert

    @Override    public Uri insert(final Uri uri, final ContentValues values) {        ....       long rowID = db.insert(DB_TABLE, null, filteredValues);        if (rowID == -1) {            Log.d(Constants.TAG, "couldn't insert into downloads database");            return null;        }        insertRequestHeaders(db, rowID, values);        notifyContentChanged(uri, match);        // Always start service to handle notifications and/or scanning        final Context context = getContext();        context.startService(new Intent(context, DownloadService.class));        return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);    }

这个方法定义在packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadProvider.java
insert方法即是往DB_TABLE(downloads)表中插入了一条数据。接下来在insert方法最后启动DownloadService,这几步都是在DownloadProvider进程中进行的。接下来会有两条主线。
1,在DownloadProvider进程中启动的这个DownloadService继续执行。
2,返回到Step 4 Browser进程的中的DownloadHandler.onDownloadStartNoStream方法中去跳转界面。
这里我们不讨论UI界面,接下来分析DownloadService的操作。

Step 7:DownloadService.onCreate

   @Override    public void onCreate() {        super.onCreate();        ....        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);        ....    }

这个方法定义在packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadService.java
第一次启动,首次执行onCreate方法,创建一个HandlerThread工作线程,并注册了一个监听数据库改变的一个DownloadManagerContentObserver对象,监听的uri为”content://downloads/all_downloads”,第2个参数为true,表示可以同时匹配其派生的Uri。接下来进入onStartCommand方法,在onStartCommand方法中继续执行enqueueUpdate方法。

    public void enqueueUpdate() {        if (mUpdateHandler != null) {            mUpdateHandler.removeMessages(MSG_UPDATE);            mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget();        }    }

这个方法执行很简单,首先是移除掉之前所有的MSG_UPDATE消息,然后再重新发送一个MSG_UPDATE消息,接下来分析Handler这个消息的回调实现。

  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 (msg.what == MSG_FINAL_UPDATE) {                mNotifier.dumpSpeeds();            }            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;        }    };

这个方法处理的逻辑比较多,先整体上认识这个,主要有updateLocked方法主要负责具体的下载实现,它的返回值是一个boolean类型,用以判断当前下载是否是激活状态,也就是是否有下载任务。接下来如果判断isActive为true,则会去执行enqueueFinalUpdate方法。

  private void enqueueFinalUpdate() {        mUpdateHandler.removeMessages(MSG_FINAL_UPDATE);        mUpdateHandler.sendMessageDelayed(                mUpdateHandler.obtainMessage(MSG_FINAL_UPDATE, mLastStartId, -1),                5 * MINUTE_IN_MILLIS);    }

从这里我们可以看出,这个回调其实是当有下载任务的时候,会一直的循环执行下去,用以保证下载的任务的连续性,如果有中断,则会重新启动。
下面我们来分析updateLocked的具体实现,是如何将下载任务放入线程中去执行的,又是怎么知道有哪些下载任务的。

Step 8 :DownloadService.updateLocked

   private boolean updateLocked() {        ...        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);            while (cursor.moveToNext()) {                final long id = cursor.getLong(idColumn);                staleIds.remove(id);                DownloadInfo info = mDownloads.get(id);                if (info != null) {                    updateDownload(reader, info, now);                } else {                    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);                    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);        }        ...        return isActive;    }

这个方法的实现分为几步:
1.查询downloads表中的所有记录,接着将其封装成一个DownloadInfo对象。
2.显然第一次DownloadInfo的info是空值,接下来insertDownloadLocked会根据Cursor去新建一个DownloadInfo信息。
3.DownloadInfo缓存的管理,将DownloadInfo缓存至mDownloads中管理。这里有个小的判断分支,如果info.mDeleted为true,则删除掉这条下载记录,并且对应的文件也将被删除,其实属于逻辑控制,跟下载无太大关系,不用太纠结。
4.对于一个新的下载,info.mDeleted显然是false,所以会进入到到else语句,调用DownloadInfo的startDownloadIfReady方法开始下载。
我们先分析insertDownloadLocked新建一个下载任务DownloadInfo的流程

    private DownloadInfo insertDownloadLocked(DownloadInfo.Reader reader, long now) {        final DownloadInfo info = reader.newDownloadInfo(this, mSystemFacade, mNotifier);        mDownloads.put(info.mId, info);        if (Constants.LOGVV) {            Log.v(Constants.TAG, "processing inserted download " + info.mId);        }        return info;    }

这个方法中,调用DownloadInfo.Reader去新建一个下载任务,从前面可以看出,这个reader对象是由数据库Cursor进行封装的,具体分析reader.newDownloadInfo方法

       public DownloadInfo newDownloadInfo(                Context context, SystemFacade systemFacade, DownloadNotifier notifier)             final DownloadInfo info = new DownloadInfo(context, systemFacade, notifier);            updateFromDatabase(info);            readRequestHeaders(info);            return info;        }

这个方法定义在packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadInfo.java
Reader是DownloadInfo的一个静态内部类,这个方法中,首先是new了一个DownloadInfo对象,然后调用updateFromDatabase去更新DownloadInfo的一些属性值。实现比较简单,就是根据前面的Cursor对象,获取数据库的一些字段值保存在DownloadInfo中。

从这里我们可以看出,数据库中所有的信息都会封装成一个下载DownloadInfo,那么它是通过什么来判断当前数据是否是需要下载的任务呢?显然如果这个url对应的任务已经被下载完成了,那么肯定是不需要再次下载的。接下来我们继续往下走,进入到startDownloadIfReady这个方法。

Step 9:DownloadInfo.startDownloadIfReady

public boolean .startDownloadIfReady(ExecutorService executor) {        synchroized (this) {            final boolean isReady = isReadyToDownload();            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, mNotifier, this);                mSubmittedTask = executor.submit(mTask);            }            return isReady;        }    }

这个方法定义在packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadInfo.java
我们先分析isReadyToDownload这个方法。

   private boolean isReadyToDownload() {        ....        switch (mStatus) {            case 0: // status hasn't been initialized yet, this is a new download            case Downloads.Impl.STATUS_PENDING: // download is explicit marked as ready to start            case Downloads.Impl.STATUS_RUNNING: // download interrupted (process killed etc) while                                                // running, without a chance to update the database                return true;            case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:            case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:                return checkCanUseNetwork(mTotalBytes) == NetworkState.OK;            case Downloads.Impl.STATUS_WAITING_TO_RETRY:                // download was waiting for a delayed restart                final long now = mSystemFacade.currentTimeMillis();                return restartTime(now) <= now;            case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:                // is the media mounted?                return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);            /// M: Because OMA DL spec, if insufficient memory, we            /// will show to user but not retry.            //case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:                // should check space to make sure it is worth retrying the download.                // but thats the first thing done by the thread when it retries to download                // it will fail pretty quickly if there is no space.                // so, it is not that bad to skip checking space availability here.                //return true;            /// M: Add for fix alp00406729, file already exist but user do not operation. @{            case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:                return false;            /// @}        }        return false;    }

一切都明白了,这里就是根据mStatus这个字段,来判断这个任务是否需要下载,也解决了我们之前的疑问,返回值为true的才会去执行下载,我们可以回头看看Browser里面当时insert一条下载记录的时候,是没有插入mStatus这个字段的,所以对于一个新任务这里mStatus为默认值即0,整个返回值为true。
接下来分析isActive这个boolean值,它主要用来标识当前DownloadInfo是否在线程中去执行了,保证一个DownloadInfo只执行一次,对于新任务,显然初始化的时候mSubmittedTask为null。

接下来进入if语句,先update数据库中的COLUMN_STATUS字段置为STATUS_RUNNING。然后新建一个DownloadThread,放入到ExecutorService线程池中去执行,这样一个下载链接就正式开始下载了。接下来分析下载读写文件以及更新数据库的动作。

Step 10:new DownloadThread

  @Override    public void run() {        ....        if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mId)                == Downloads.Impl.STATUS_SUCCESS) {            logDebug("Already finished; skipping");            return;        }        ....        executeDownload();        ....    }

这个方法定义在
DownloadThread是一个Runnable对象,这里我们关注构造方法中的第4个参数,即DownloadInfo,将DownloadInfo这个对象的信息,传给DownloadThread的成员变量,还有DownloadInfoDelta对象,最后用于更新下载进度数据库信息,我们后续分析。这样就完全得到了这条下载信息的内容。接下来去执行DownloadThread的run方法,在新的线程中进行下载。在run方法的实现中,首先是再次确认这个任务是需要下载的,否则直接return,线程结束,然后如果需要下载则去调用executeDownload方法去执行。

  private void executeDownload() throws StopRequestException {        .....        URL url;        try {            // TODO: migrate URL sanity checking into client side of API            url = new URL(mInfoDelta.mUri);        } catch (MalformedURLException e) {            throw new StopRequestException(STATUS_BAD_REQUEST, e);        }        int redirectionCount = 0;        while (redirectionCount++ < Constants.MAX_REDIRECTS) {            // Open connection and follow any redirects until we have a useful            // response with body.            HttpURLConnection conn = null;            try {                checkConnectivity();                conn = (HttpURLConnection) url.openConnection();                conn.setInstanceFollowRedirects(false);                conn.setConnectTimeout(DEFAULT_TIMEOUT);                conn.setReadTimeout(DEFAULT_TIMEOUT);                addRequestHeaders(conn, resuming);                final int responseCode = conn.getResponseCode();                switch (responseCode) {                    case HTTP_OK:                        ....                        /// @}                        transferData(conn);                        return;             .....        }        throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects");    }

在executeDownload方法中根据url创建一个HttpURLConnection连接。然后判断getResponseCode网络端返回值。这里我们分析HTTP_OK的情况。在HTTP_OK:接下来调用transferData(conn);传入的参数为这个HttpURLConnection这个连接。

Step 11:DownloadThread.transferData

   private void transferData(HttpURLConnection conn) throws StopRequestException {        ....        DrmManagerClient drmClient = null;        ParcelFileDescriptor outPfd = null;        FileDescriptor outFd = null;        InputStream in = null;        OutputStream out = null;        try {            try {                in = conn.getInputStream();            } catch (IOException e) {                throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e);            }            try {                outPfd = mContext.getContentResolver()                        .openFileDescriptor(mInfo.getAllDownloadsUri(), "rw");                outFd = outPfd.getFileDescriptor();                if (DownloadDrmHelper.isDrmConvertNeeded(mInfoDelta.mMimeType)) {                    drmClient = new DrmManagerClient(mContext);                    out = new DrmOutputStream(drmClient, outPfd, mInfoDelta.mMimeType);                } else {                    out = new ParcelFileDescriptor.AutoCloseOutputStream(outPfd);                }                // Pre-flight disk space requirements, when known                if (mInfoDelta.mTotalBytes > 0) {                    final long curSize = Os.fstat(outFd).st_size;                    final long newBytes = mInfoDelta.mTotalBytes - curSize;                    StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes);                    // We found enough space, so claim it for ourselves                    Os.posix_fallocate(outFd, 0, mInfoDelta.mTotalBytes);                }                // Move into place to begin writing                Os.lseek(outFd, mInfoDelta.mCurrentBytes, OsConstants.SEEK_SET);            } catch (ErrnoException e) {                throw new StopRequestException(STATUS_FILE_ERROR, e);            } catch (IOException e) {                throw new StopRequestException(STATUS_FILE_ERROR, e);            }            // Start streaming data, periodically watch for pause/cancel            // commands and checking disk space as needed.            transferData(in, out, outFd);            ....    }

这个方法定义在packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadThread.java
在这个方法中,获取一个该url对应的网络输入流对象InputStream,同时根据uri构造一个文件描述符,进而构建一个输出流OutputStream对象,最后到重载的transferData方法,将输入输出流,以及文件描述符传入transferData开始存储文件。

  private void transferData(InputStream in, OutputStream out, FileDescriptor outFd)            throws StopRequestException {        final byte buffer[] = new byte[Constants.BUFFER_SIZE];        while (true) {            checkPausedOrCanceled();            int len = -1;            try {                len = in.read(buffer);            } catch (IOException e) {                throw new StopRequestException(                        STATUS_HTTP_DATA_ERROR, "Failed reading response: " + e, e);            }            if (len == -1) {                break;            }            try {                // When streaming, ensure space before each write                if (mInfoDelta.mTotalBytes == -1) {                    final long curSize = Os.fstat(outFd).st_size;                    final long newBytes = (mInfoDelta.mCurrentBytes + len) - curSize;                    StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes);                }                out.write(buffer, 0, len);                mMadeProgress = true;                mInfoDelta.mCurrentBytes += len;                updateProgress(outFd);            } catch (ErrnoException e) {                throw new StopRequestException(STATUS_FILE_ERROR, e);            } catch (IOException e) {                throw new StopRequestException(STATUS_FILE_ERROR, e);            }        }       .....    }

真正开始下载都是在这段code中,首先checkPausedOrCanceled方法检查是否有取消下载请求,如果有直接进入catch语句跳过,下载结束。如果没有取消,则执行while语句,执行输入输出流的读写操作。每一次读写的同时都会执行updateProgress方法,显然该方法是用来更新进度的,下面具体来分析。

Step 12:DownloadThread.updateProgress

    private void updateProgress(FileDescriptor outFd) throws IOException, StopRequestException {     ....        final long bytesDelta = currentBytes - mLastUpdateBytes;        final long timeDelta = now - mLastUpdateTime;        if (bytesDelta > Constants.MIN_PROGRESS_STEP && timeDelta > Constants.MIN_PROGRESS_TIME) {            // fsync() to ensure that current progress has been flushed to disk,            // so we can always resume based on latest database information.            outFd.sync();            //mInfoDelta.writeToDatabaseOrThrow();            mInfoDelta.writeToDatabaseWithoutModifyTime();            mLastUpdateBytes = currentBytes;            mLastUpdateTime = now;        }    }

这个方法定义在packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadThread.java
总共做了两件事,第一,调用outFd.sync强制所有系统缓冲区与基础设备同步,第二调用mInfoDelta的writeToDatabaseWithoutModifyTime去更新数据库操作,即将当前进度,下载了多少update到数据库。

Step 13:DownloadInfoDelta.writeToDatabaseWithoutModifyTime

    public void writeToDatabaseWithoutModifyTime() throws StopRequestException {            final ContentValues values = new ContentValues();            values.put(Downloads.Impl.COLUMN_URI, mUri);            values.put(Downloads.Impl._DATA, mFileName);            values.put(Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);            values.put(Downloads.Impl.COLUMN_STATUS, mStatus);            values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, mNumFailed);            values.put(Constants.RETRY_AFTER_X_REDIRECT_COUNT, mRetryAfter);            values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, mTotalBytes);            values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, mCurrentBytes);            values.put(Constants.ETAG, mETag);            values.put(Downloads.Impl.COLUMN_ERROR_MSG, mErrorMsg);            if (mContext.getContentResolver().update(mInfo.getAllDownloadsUri(),                    values, Downloads.Impl.COLUMN_DELETED + " == '0'", null) == 0) {                throw new StopRequestException(STATUS_CANCELED, "Download deleted or missing!");            }        }    }

这个方法定义在packages/providers/DownloadProvider/src/com/android/providers/downloads/DownloadThread.java
DownloadInfoDelta是DownloadThread的一个内部类,主要用于更新数据库进度操作,这个方法中此时uri为”content://downloads/all_downloads/id”,对应DownloadProvider的update方法去更新数据库,而此时又会回调至DowbloadService中的DownloadManagerContentObserver监听中,因为此时对应uri数据库内容已经改变。至此,整个updateLocked方法执行完毕。

简单分析DownloadManagerContentObserver内容,可以看出这个目的还是保证了下载的连续性,只要每次有下载数据更新,则会循环检测,以确保下载任务的连续性。

 private class DownloadManagerContentObserver extends ContentObserver {        public DownloadManagerContentObserver() {            super(new Handler());        }        @Override        public void onChange(final boolean selfChange) {            enqueueUpdate();        }    }

至此,整个下载过程已经结束,至于UI界面的更新情况,则只需要监听数据库中的数据变化,或者在有下载任务时候,间隔一段时间去数据库查询进度信息,更新进度即可。

对于下载界面,自4.4之后,都是BrowserActivity->DownloadList->DocumentActivity,而且对于DocumentUI正是采用的一段时间查询数据库,更新的方式,这里我们也不讨论了。

2 1