android-downloader_一個帶進度條的下載開源框架源碼解析(雷驚風)

在我們的開發過程中,經常會用到下載功能,也經常伴有顯示下載進度的需求,什麼下載文件啊,更新版本啊等等吧,今天給大家介紹一個帶進度更新下載的開源框架—-android-downloader(下載地址,並跟大家一起看一下它的源碼,熟悉一下它的實現過程。

一、涉及到的類及作用。

1. DownloadTask.java

  封裝單個下載任務包含的信息,包括要下載文件的名稱、文件的下載地址、要下載到本地的路徑、開始結束時間、文件類型、文件大小、下載監聽(DownloadListener)等等,還有就是當前下載任務的開始,停止等方法。

2.DownloadListener.java

下載任務回調類,包括onAdd(DownloadTask task)、onDelete(DownloadTask task)、onStop(DownloadTask task)、onStart()、onProgressUpdate(Integer... values)、onSuccess(DownloadTask task)、onCancelled()、onError(Throwable thr)、onFinish()方法。

3.AsycDownloadTask.java

真正實現下載的類,繼承自AsyncTask類。

4.DownloadException.java

繼承自Exception類的自定義錯誤提示類。

5.DownloadStatus.java

下載中的各種狀態,包括STATUS_PENDING、STATUS_RUUUING、STATUS_STOPPED、STATUS_FINISHED、STATUS_FAILED、STATUS_DETELED幾種狀態。

6.DownloadType.java

下載文件的類型,包括Type_unknown、type_text、type_image、type_music、type_video、type_app幾種類型。

7.Isql.java、ISqlmpl.java、DatabaseConfigUtil.java、DatabaseHelper.java

OrmLite數據庫操作相關類,保存下載任務信息,這裏先不講,知道有這麼回事就行了,還是主要將下載更新相關實現。

二、源碼分析。

   至於怎麼用大家可以網上去查一下,可以結合Listview一起用,我在這裏只跟大家分析一下它的實現過程,,實現過程明白了,用起來也就得心應手了。很簡單,大致的過程就是我們需要自己去new一個DownloadListener並實現裏邊的方法(在這裏實現具體的更新操作),實例化DownLoadManager對象,當要建立下載任務時,每一個下載任務創建一個DownLoadTask對象並賦值(name、path、url等等),將當前對象與DownloadListener對象一起添加到DownLoadMagager中如下代碼:

DownloadTask task = new DownloadTask(KnowledgeDocListActivity.this);
                                task.setUrl(Settings.DOMAINNAME + docEntity.getUrl());
                                task.setName(docEntity.getDocName());
                                task.setPath(Settings.WORDFILE + docEntity.getDocName());
                                task.setId(docEntity.getId());
                                mDownloadManager.add(task, listener);
那麼,讓我們看一看DownloadManager的add方法中做了什麼:

/**
     * Add Task
     *
     * @param task DownloadTask
     * @return
     */
    public void add(DownloadTask task, DownloadListener listener) {
        Log.i("Add Task");
	//判斷task的可用性;
        if (task == null || !task.isValid()) {
            OnResult(POST_MESSAGE.ERROR, task, listener, DownloadException.DOWNLOAD_TASK_NOT_VALID);
            return;
        }

        if (task.getContext() == null) {
	//如果我們沒有對task的Context屬性賦值,在這裏賦值;
	//可以通過上邊代碼得知,在new DownloadTask(KnowledgeDocListActivity.this)的時候將Context傳過來的;
            task.setContext(context);
        }

        ISql iSql = new ISqlImpl(context);
        DownloadTask temptask = null;

        try {
		//根據傳入的task到OrmLite數據庫查找;
            temptask = iSql.queryDownloadTask(task);

            if (temptask == null || !temptask.isValid() || !temptask.isComplete()) {
		//判斷task的name、path、url屬性是否都存在;
                if (task.isComplete()) {
                    iSql.addDownloadTask(task);
			//發送添加task事件;
                    OnResult(POST_MESSAGE.ADD, task, listener, -1);
                    Log.i("The Task is stored in the sqlite.");
                } else {
		//如果沒有name等屬性,開啓異步任務去網上獲取,這裏只是獲取,沒有下載;
                    task.start(context, listener, true);
                }
            } else {
		//數據庫中已經存在這個下載任務;
                task.setDownloadTask(temptask);
                OnResult(POST_MESSAGE.ADD, task, listener, -1);
                Log.i("The Task is already stored in the sqlite.");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
看他的OnResult(Post_Message.*,task,listener,Integer)方法:

 /**
     * deal with the result
     *
     * @param message  POST_MESSAGE
     * @param listener DownloadListener
     * @param code     code
     */
    @SuppressWarnings("rawtypes")
    private void OnResult(final POST_MESSAGE message, final DownloadTask task, final DownloadListener listener, final Integer code) {
        if (context == null || !(context instanceof Activity)) {
            Log.w("The context is null or invalid!");
            return;
        }

        ((Activity) context).runOnUiThread(new Runnable() {

            public void run() {
                if (listener == null) {
                    return;
                }

                switch (message) {
                    case ADD:
                        listener.onAdd(task);
                        break;
                    case DELETE:
                        listener.onDelete(task);
                        break;
                    case START:
                        listener.onStart();
                        break;
                    case FINISH:
                        listener.onFinish();
                        break;
                    case STOP:
                        listener.onStop(task);
                        break;
                    case ERROR:
                        listener.onError(new DownloadException(code));
                        break;
                }
            }
        });
    }

可見他是通過message不同的狀態,調用了DownloadListener的不同方法,正好,也看一下我們這個最終操作UI的類吧:

private DownloadListener listener = new DownloadListener<Integer, DownloadTask>() {
        
        @Override
        public void onAdd(DownloadTask downloadTask) {
            super.onAdd(downloadTask);
            LogUtils.d("onAdd()");
            mDownloadTasklist.add(downloadTask);
            LogUtils.d("" + downloadTask);
            mDocListAdapter.notifyDataSetChanged();
        }

       
        @Override
        public void onDelete(DownloadTask downloadTask) {
            super.onDelete(downloadTask);
            LogUtils.d("onDelete()");
        }

      
        @Override
        public void onStop(DownloadTask downloadTask) {
            super.onStop(downloadTask);
            LogUtils.d("onStop()");
        }

        /**
         * Runs on the UI thread before doInBackground(Params...).
         */
        @Override
        public void onStart() {
            super.onStart();
            UIUtils.makeToast(KnowledgeDocListActivity.this, getResources().getString(R.string.start_download), AppMsg.STYLE_ALERT).show();
        }

        /**
         * Runs on the UI thread after publishProgress(Progress...) is invoked. The
         * specified values are the values passed to publishProgress(Progress...).
         *
         * @param values The values indicating progress.
         */
        @Override
        public void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            mDocListAdapter.notifyDataSetChanged();
            LogUtils.d("onProgressUpdate");
        }

        /**
         * Runs on the UI thread after doInBackground(Params...). The specified
         * result is the value returned by doInBackground(Params...). This method
         * won't be invoked if the task was cancelled.
         *
         * @param downloadTask The result of the operation computed by
         *                     doInBackground(Params...).
         */
        @Override
        public void onSuccess(DownloadTask downloadTask) {
            super.onSuccess(downloadTask);
            LogUtils.d("異步URL:" + downloadTask.getUrl());
            for (int i = 0; i < mDocList.size(); i++) {
                LogUtils.d("docListURL:" + i + ":" + mDocList.get(i).getUrl());
                if (downloadTask.getUrl().contains(mDocList.get(i).getUrl())) {
                    mDocList.get(i).setIsLocalHave(true);
                    break;
                }
            }
            UIUtils.makeToast(KnowledgeDocListActivity.this, getResources().getString(R.string.download_success), AppMsg.STYLE_ALERT).show();
            mDocListAdapter.notifyDataSetChanged();
            LogUtils.d("onSuccess()");
        }

        /**
         * Applications should preferably override onCancelled(Object). This method
         * is invoked by the default implementation of onCancelled(Object). Runs on
         * the UI thread after cancel(boolean) is invoked and
         * doInBackground(Object[]) has finished.
         */
        @Override
        public void onCancelled() {
            super.onCancelled();
            LogUtils.d("onCancelled()");
        }

        @Override
        public void onError(Throwable thr) {
            super.onError(thr);
            LogUtils.d("onError():" + thr.getMessage());
            UIUtils.makeToast(KnowledgeDocListActivity.this, getResources().getString(R.string.down_fail), AppMsg.STYLE_ALERT).show();
        }

        /**
         * Runs on the UI thread after doInBackground(Params...) when the task is
         * finished or cancelled.
         */
        @Override
        public void onFinish() {
            super.onFinish();
            LogUtils.d("onFinish()");
        }
    };
裏邊有一些我的操作,就不刪除了,可能會對大家理解有幫助,好,再看看我們DownLoadManager的Add方法中的其他操作:task.isComplete():

/**
     * url, name and path is not empty.
     *
     * @return true is valid,otherwise not.
     */
    public boolean isComplete() {
        return !TextUtils.isEmpty(url) && !TextUtils.isEmpty(name) && !TextUtils.isEmpty(path);
    }
task.start(context, listener, true):

/**
     * Start the Task
     *
     * @param context  Context
     * @param listener DownloadListener
     */
    @SuppressWarnings({
            "rawtypes", "unchecked"
    })
    protected void start(Context context, DownloadListener listener, boolean isOnlyGetHead) {
        //Get context,which will be used to communicate with sqlite.
        if (this.context == null && context != null) {
            this.context = context;
        }

        if (task != null) {
            task.cancel(false);
        }
<span style="white-space:pre">	</span>//AsycDownloadTask是繼承了AsyncTask的一個類,
<span style="white-space:pre">	</span>//所以在這裏他開啓了異步去獲取信息;
        task = new AsycDownloadTask(listener, isOnlyGetHead);
        task.execute(this);
    }
看一下AsycDownloadTask的doInBackground(DownloadTask ... tasks)方法,也是最主要的一個方法:

/**
     * TODO if error occurs,carry it out. if (listener != null) {
     * listener.onError(new Throwable()); }
     */
    protected DownloadTask doInBackground(DownloadTask... tasks) {
        if (tasks.length <= 0) {
            Log.e("There is no DownloadTask.");
            return null;
        }

        DownloadTask task = tasks[0];

        if (task == null || !task.isValid()) {
		//發送task異常錯誤信息;
            SendError(task, DownloadException.DOWNLOAD_TASK_NOT_VALID);

            Log.e("The task is not valid,or the url of the task is not valid.");
            return null;
        }

        String path = task.getPath();
        File file = new File(path);

        InputStream in = null;
        RandomAccessFile out = null;
        HttpURLConnection connection = null;

        try {
            long range = file.length();
            long size = task.getSize();
            long curSize = range;
            String filename = task.getName();
            String contentType = task.getMimeType();
		//本地文件信息與數據庫保存task信息一致,說明下載了這個任務;
            if (task.getStatus() == DownloadStatus.STATUS_FINISHED && size == range) {
                Log.i("The DownloadTask has already been downloaded.");
                return task;
            }
<span style="white-space:pre">	</span>	//根據url獲取網絡文件相關信息;
            String urlString = task.getUrl();
            String cookies = null;
            while (true) {
                URL url = new URL(urlString);
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestProperty("User-Agent", "Snowdream Mobile");
                connection.setRequestProperty("Connection", "Keep-Alive");
                if (cookies != null && cookies != "") {
                    connection.setRequestProperty("Cookie", cookies);
                }
                connection.setRequestMethod("GET");

                if (range > 0) {
                    connection.setRequestProperty("Range", "bytes=" + range +
                            "-");
                }

                //http auto redirection
                //see: http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/
                boolean redirect = false;
                boolean success = false;

                // normally, 3xx is redirect
                int status = connection.getResponseCode();
                Log.i("HTTP STATUS CODE: " + status);

                switch (status) {
                    case HttpURLConnection.HTTP_OK:
                    case HttpURLConnection.HTTP_PARTIAL:
                        success = true;

                        String transfer_encoding = connection.getHeaderField("Transfer-Encoding");
                        if (!TextUtils.isEmpty(transfer_encoding)
                                && transfer_encoding.equalsIgnoreCase("chunked")) {
                            mode = MODE_TRUNKED;
                            Log.i("HTTP MODE: TRUNKED");
                        } else {
                            mode = MODE_DEFAULT;
                            Log.i("HTTP MODE: DEFAULT");
                        }

                        String accept_ranges = connection.getHeaderField("Accept-Ranges");
                        if (!TextUtils.isEmpty(accept_ranges)
                                && accept_ranges.equalsIgnoreCase("bytes")) {
                            Log.i("Accept-Ranges: bytes");
                        } else {
                            range = 0;
                            Log.i("Accept-Ranges: none");
                        }
                        break;
                    case HttpURLConnection.HTTP_MOVED_TEMP:
                    case HttpURLConnection.HTTP_MOVED_PERM:
                    case HttpURLConnection.HTTP_SEE_OTHER:
                        redirect = true;
                        // get redirect url from "location" header field
                        urlString = connection.getHeaderField("Location");

                        // get the cookie if need, for login
                        cookies = connection.getHeaderField("Set-Cookie");

                        Log.i("Redirect Url : " + urlString);
                        break;
                    default:
                        success = false;
                        break;
                }

                if (!redirect) {
                    if (!success) {
                        SendError(task, DownloadException.DOWNLOAD_TASK_FAILED);

                        Log.e("Http Connection error. ");
                        return null;
                    }
                    Log.i("Successed to establish the http connection.Ready to download...");
                    break;
                }
            }

            if (range == 0) {
                //set the whole file size
                size = connection.getContentLength();
		//賦值文件大小字段;
                task.setSize(size);

                if (contentType != connection.getContentType()) {
                    contentType = connection.getContentType();
			//賦值類型;
                    task.setMimeType(contentType);
                }

                //auto get filename
                if (TextUtils.isEmpty(filename)) {
                    String disposition = connection.getHeaderField("Content-Disposition");
                    if (disposition != null) {
                        // extracts file name from header field
                        final String FILENAME = "filename=";
                        final int startIdx = disposition.indexOf(FILENAME);
                        final int endIdx = disposition.indexOf(';', startIdx);
                        filename = disposition.substring(startIdx + FILENAME.length(), endIdx > 0 ? endIdx : disposition.length());
                    } else {
                        // extracts file name from URL
                        filename = urlString.substring(urlString.lastIndexOf("/") + 1,
                                urlString.length());
                    }
			//賦值文件名稱;
                    task.setName(filename);
                }

                //auto get filepath
                if (TextUtils.isEmpty(path)) {
                    path = STORE_PATH + filename;

                    file = new File(path);
<span style="white-space:pre">			</span>//賦值path;
                    task.setPath(path);
                }

                task.setStartTime(System.currentTimeMillis());
<span style="white-space:pre">		</span>//下載任務添加到數據庫;
                SaveDownloadTask(task, task.getStatus());
                Log.i("The Task is stored in the sqlite.");
<span style="white-space:pre">		</span>//如果是隻獲取信息,不下載文件,則發送調用DownLoadLisener的add方法,
<span style="white-space:pre">		</span>//並不進行後續下載操作;
                if (isOnlyGetHead) {
                    SendAdd(task);
                    return null;
                }
            }

            File dir = file.getParentFile();

            if (!dir.exists() && !dir.mkdirs()) {
                SendError(task, DownloadException.DOWNLOAD_TASK_FAILED);
                Log.e("The directory of the file can not be created!");
                return null;
            }
            task.setStatus(DownloadStatus.STATUS_RUNNING);
            SaveDownloadTask(task, task.getStatus());

            Log.i("DownloadTask " + task);

            out = new RandomAccessFile(file, "rw");
            out.seek(range);

            in = new BufferedInputStream(connection.getInputStream());

            byte[] buffer = new byte[1024];
            int nRead = 0;
            int progress = -1;
            boolean isFinishDownloading = true;
		//下載操作;
            while ((nRead = in.read(buffer, 0, 1024)) > 0) {
                out.write(buffer, 0, nRead);

                curSize += nRead;

                if (size != 0) {
                    progress = (int) ((curSize * 100) / size);
                }
		//這裏更新了進度條,注意注意... ...
                publishProgress(progress);

                Log.i("cur size:" + (curSize) + "    total size:" + (size) + "    cur progress:" + (progress));

                if (isCancelled()) {
                    task.setStatus(DownloadStatus.STATUS_STOPPED);
                    isFinishDownloading = false;
                    break;
                }

                if (task.getStatus() != DownloadStatus.STATUS_RUNNING) {
                    isFinishDownloading = false;
                    break;
                }
            }

            if (!isFinishDownloading) {
                Log.w("The DownloadTask has not been completely downloaded.");
                SaveDownloadTask(task, task.getStatus());
                return null;
            }

            //when the mode is MODE_TRUNKED,set the latest size.
            if (size == 0 && curSize != 0) {
                task.setSize(curSize);
            }

            range = file.length();
            size = task.getSize();
            Log.i("range: " + range + " size: " + size);

            if (range != 0 && range == size) {
                Log.i("The DownloadTask has been successfully downloaded.");
                task.setFinishTime(System.currentTimeMillis());
                SaveDownloadTask(task, DownloadStatus.STATUS_FINISHED);
                return task;
            } else {
                Log.i("The DownloadTask failed to downloaded.");
                SendError(task, DownloadException.DOWNLOAD_TASK_FAILED);
                return null;
            }
        } catch (MalformedURLException e) {
            SendError(task, DownloadException.DOWNLOAD_TASK_FAILED);

            e.printStackTrace();
        } catch (ProtocolException e) {
            SendError(task, DownloadException.DOWNLOAD_TASK_FAILED);

            e.printStackTrace();
        } catch (FileNotFoundException e) {
            SendError(task, DownloadException.DOWNLOAD_TASK_FAILED);

            e.printStackTrace();
        } catch (IOException e) {
            SendError(task, DownloadException.DOWNLOAD_TASK_FAILED);

            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }

                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (connection != null) {
                connection.disconnect();
            }
        }
        return null;
    }
看一下當前類裏邊的SendError與SendAdd方法:

private void SendError(DownloadTask task, Integer code) {
        Log.e("Errors happen while downloading.");
        SaveDownloadTask(task, DownloadStatus.STATUS_FAILED);
<span style="white-space:pre">	</span>//調用了當前類裏邊的一個sHandler對象;
        sHandler.obtainMessage(
                MESSAGE_POST_ERROR,
                new AsyncTaskResult(this, task,
                        code)).sendToTarget();
    }

    private void SendAdd(DownloadTask task) {
        sHandler.obtainMessage(
                MESSAGE_POST_ADD,
                new AsyncTaskResult(this, task,
                        -1)).sendToTarget();
    }
看一下sHandler是個什麼東東:

private static class InternalHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;

            if (result == null || result.mTask == null || result.mTask.isCancelled()) {
                Log.i("The asyncTask is not valid or cancelled!");
                return;
            }

            switch (msg.what) {
                case MESSAGE_POST_ERROR:
		<span style="white-space:pre">	</span>//調用當前類的OnError方法;
                    ((AsycDownloadTask) result.mTask).OnError(result.mDownloadTask, result.mData);
                    break;
                case MESSAGE_POST_ADD:
			//調用當前類的OnAdd方法;
                    ((AsycDownloadTask) result.mTask).OnAdd(result.mDownloadTask);
                    break;
                default:
                    break;
            }
        }
    }
繼續向下看對應的方法:

 /**
     * throw error
     *
     * @param task task
     * @param code The code of the exception
     */
    private void OnError(DownloadTask task, Integer code) {
        if (listener != null) {
            listener.onError(new DownloadException(code));
        }
    }

    /**
     * inform Add
     *
     * @param task task
     */
    private void OnAdd(DownloadTask task) {
        if (listener != null && listener instanceof DownloadListener) {
            ((DownloadListener) listener).onAdd(task);
        }
    }
可以發現到現在還是調用了DownloadListener對應的方法去進行相關操作;下面看一下下載過程中的更新方法:
publishProgress(progress);
protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
得到Handler對象發送消息,看getHandler方法:

 private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }
很簡單的單例,下邊看看這個Handler在哪:

 public InternalHandler() {
            super(Looper.getMainLooper());
        }
主線程啊,看看他發送消息後的操作吧:

private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
		   //最終在這裏調用了更新操作;
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
好了,到這裏添加向DownLoadMagager中添加task就說完了,其實下載的時候是調用了DownLoadMagager的start方法,在start方法中也是調用了DownloadTask的start方法跟DownLoadMagager的添加方法中調用DownloadTask的start方法差不多,只不過這次最後一個參數傳的是false,最後一個參數的含義就是是不是隻獲取文件信息,下邊看一下實現:

**
     * Start Task
     *
     * @param task
     * @param listener
     * @return
     */
    @SuppressWarnings("rawtypes")
    public boolean start(DownloadTask task, DownloadListener listener) {
        Log.i("Start Task");

        boolean ret = false;

        if (task == null) {
            OnResult(POST_MESSAGE.ERROR, task, listener, DownloadException.DOWNLOAD_TASK_NOT_VALID);
            return ret;
        }

        if (task.getContext() == null) {
            task.setContext(context);
        }

        ISql iSql = new ISqlImpl(context);

        DownloadTask temptask = null;

        try {
            temptask = iSql.queryDownloadTask(task);

            if (temptask == null) {
                add(task, listener);
            } else if (!temptask.equals(task)) {
                task.setDownloadTask(temptask);
            }

            switch (task.getStatus()) {
                case DownloadStatus.STATUS_RUNNING:
                    OnResult(POST_MESSAGE.START, task, listener, -1);
                    OnResult(POST_MESSAGE.FINISH, task, listener, -1);
                    Log.i("The Task is already Running.");
                    break;
                default:
                    if (listener != null) {
			//這裏就跟添加task時一樣了,注意最後一個參數;
                        task.start(context, listener, false);
                    }
                    break;
            }

            ret = true;
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return ret;
    }
好,到現在爲止添加跟下載就說完了,還有stop方法等等,也不難,感興趣的可以自己看一下,我這裏就不再說了。上邊的實現代碼很簡單,我也加了註釋,希望這篇文章對大家瞭解android-downloader有幫助。




剛剛創建了132079212,希望能共同學習,非誠勿擾!

發佈了29 篇原創文章 · 獲贊 29 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章