android-downloader_一个带进度条的下载开源框架源码解析(雷惊风)

来源:互联网 发布:软件开发质量考核指标 编辑:程序博客网 时间:2024/05/08 00:33

在我们的开发过程中,经常会用到下载功能,也经常伴有显示下载进度的需求,什么下载文件啊,更新版本啊等等吧,今天给大家介绍一个带进度更新下载的开源框架—-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,希望能共同学习,非诚勿扰!

0 0
原创粉丝点击