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,希望能共同学习,非诚勿扰!
- android-downloader_一个带进度条的下载开源框架源码解析(雷惊风)
- Android之EventBus概述及源码解析(雷惊风)
- CircleImageView用法及源码解析(雷惊风)
- Android带下载进度条的通知栏
- 编译时注解Butterknife源码解析之深入篇(雷惊风)
- Android FTP 客户端 上传/下载 带进度条实战源码
- android自定义一个带进度条的button
- Android 编写一个带进度条的Webview
- Android深入源码分析理解Aidl整体调用流程(雷惊风)
- JVM内存分配过程与原理解析(雷惊风)
- 【android】带加载进度条的WebView (附demo下载)
- android 文件上传与下载(带进度条)
- 一个下载Android开源项目源码的网站
- Android okHttp文件下载并带进度条的demo(简单工具类)
- android的一个下载框架
- Android之Edittext禁止输入表情符号(雷惊风)
- Android打包apk实现原理与流程(雷惊风)
- Android基础之Jni开发流程详解(雷惊风)
- PHPnow开启PHP扩展里openssl支持的方法
- Volley源码理解之 一
- XCode升级之后插件无法使用的解决方案
- CSS规范 - 命名规则
- NOIP2011观光公交
- android-downloader_一个带进度条的下载开源框架源码解析(雷惊风)
- CSS规范 - 分类方法
- PSNR定义与计算
- Boost.Spirit x3学习笔记
- ctemplate简单使用test
- CoreLocation使用
- C++使用tinyxml来操作DOM对象(以svg格式为例,其他格式都类似操作)
- LeetCode || Permutation Sequence
- Java 内存溢出(java.lang.OutOfMemoryError)的常见情况和处理方式总结