Android版多线程下载 仿下载助手(最新)
首先声明一点: 这里的多线程下载并不是指多个线程下载一个 文件,而是每个线程负责一个文件,今天给大家分享一个多线程下载的 例子。先看一下效果,点击下载开始下载,同时显示下载进度,下载完成,变成程安装,点击安装提示安装应用。
界面效果图:
线程池ThreadPoolExecutor ,先简单学习下这个线程池的使用
ThreadPoolExecutor threadpool=new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler)
上面是 ThreadPoolExecutor的参数说明,
第一个参数 corePoolSize : 空闲时 存在的线程数目、
第二个参数 maximumPoolSize :允许同时存在的最大线程数、
第三个参数 keepAliveTime: 这个参数是 允许空闲线程存活的时间、
第四个参数 unit : 是 时间的单位 、
第五个参数 workQueue :这个是一个容器,它里面存放的是、 threadpool.execute(new Runnable()) 执行的线程.new Runnable()、
第六个参数 handler:当执行被阻塞时,该处理程序将被阻塞,因为线程的边界和队列容量达到了 。
工具类 ThreadManager
介绍完了 线程池参数,那我们就先创建一个线程管理的工具类 ThreadManager
public class ThreadManager {
public static final String DEFAULT_SINGLE_POOL_NAME = "DEFAULT_SINGLE_POOL_NAME";
private static ThreadPoolProxy mLongPool = null;
private static Object mLongLock = new Object();
private static ThreadPoolProxy mShortPool = null;
private static Object mShortLock = new Object();
private static ThreadPoolProxy mDownloadPool = null;
private static Object mDownloadLock = new Object();
private static Map<String, ThreadPoolProxy> mMap = new HashMap<String, ThreadPoolProxy>();
private static Object mSingleLock = new Object();
public static ThreadPoolProxy getDownloadPool() {
synchronized (mDownloadLock) {
if (mDownloadPool == null) {
mDownloadPool = new ThreadPoolProxy(3, 3, 5L);
}
return mDownloadPool;
}
}
public static ThreadPoolProxy getLongPool() {
synchronized (mLongLock) {
if (mLongPool == null) {
mLongPool = new ThreadPoolProxy(5, 5, 5L);
}
return mLongPool;
}
}
public static ThreadPoolProxy getShortPool() {
synchronized (mShortLock) {
if (mShortPool == null) {
mShortPool = new ThreadPoolProxy(2, 2, 5L);
}
return mShortPool;
}
}
public static ThreadPoolProxy getSinglePool() {
return getSinglePool(DEFAULT_SINGLE_POOL_NAME);
}
public static ThreadPoolProxy getSinglePool(String name) {
synchronized (mSingleLock) {
ThreadPoolProxy singlePool = mMap.get(name);
if (singlePool == null) {
singlePool = new ThreadPoolProxy(1, 1, 5L);
mMap.put(name, singlePool);
}
return singlePool;
}
}
public static class ThreadPoolProxy {
private ThreadPoolExecutor mPool;
private int mCorePoolSize;
private int mMaximumPoolSize;
private long mKeepAliveTime;
private ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
mCorePoolSize = corePoolSize;
mMaximumPoolSize = maximumPoolSize;
mKeepAliveTime = keepAliveTime;
}
public synchronized void execute(Runnable run) {
if (run == null) {
return;
}
if (mPool == null || mPool.isShutdown()) {
mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new AbortPolicy());
}
mPool.execute(run);
}
public synchronized void cancel(Runnable run) {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
mPool.getQueue().remove(run);
}
}
public synchronized boolean contains(Runnable run) {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
return mPool.getQueue().contains(run);
} else {
return false;
}
}
public void stop() {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
mPool.shutdownNow();
}
}
public synchronized void shutdown() {
if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
mPool.shutdownNow();
}
}
}
}
这个线程池工具类 主要就是 生成一个线程池, 以及 取消线程池中的任务,查询线程池中是否包含某一任务。
下载任务 DownloadTask
我们的现在线程 DownloadTask 就 通过 ThreadManager .getDownloadPool().execute() 方法 交给线程池去管理。
有了线程池管理我们的线程, 那我们下一步 就是 DownloadTask 这个类去下载了。
public class DownloadTask implements Runnable {
private DownloadInfo info;
public DownloadTask(DownloadInfo info) {
this.info = info;
}
@Override
public void run() {
info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
notifyDownloadStateChanged(info);
File file = new File(info.getPath());// 获取下载文件
HttpResult httpResult = null;
InputStream stream = null;
if (info.getCurrentSize() == 0 || !file.exists()
|| file.length() != info.getCurrentSize()) {
// 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
info.setCurrentSize(0);
file.delete();
}
httpResult = HttpHelper.download(info.getUrl());
// else {
// // //文件存在且长度和进度相等,采用断点下载
// httpResult = HttpHelper.download(info.getUrl() + "&range=" +
// info.getCurrentSize());
// }
if (httpResult == null
|| (stream = httpResult.getInputStream()) == null) {
info.setDownloadState(STATE_ERROR);// 没有下载内容返回,修改为错误状态
notifyDownloadStateChanged(info);
} else {
try {
skipBytesFromStream(stream, info.getCurrentSize());
} catch (Exception e1) {
e1.printStackTrace();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
int count = -1;
byte[] buffer = new byte[1024];
while (((count = stream.read(buffer)) != -1)
&& info.getDownloadState() == STATE_DOWNLOADING) {
// 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
fos.write(buffer, 0, count);
fos.flush();
info.setCurrentSize(info.getCurrentSize() + count);
notifyDownloadProgressed(info);// 刷新进度
}
} catch (Exception e) {
info.setDownloadState(STATE_ERROR);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);
file.delete();
} finally {
IOUtils.close(fos);
if (httpResult != null) {
httpResult.close();
}
}
// 判断进度是否和app总长度相等
if (info.getCurrentSize() == info.getAppSize()) {
info.setDownloadState(STATE_DOWNLOADED);
notifyDownloadStateChanged(info);
} else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
notifyDownloadStateChanged(info);
} else {
info.setDownloadState(STATE_ERROR);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);// 错误状态需要删除文件
file.delete();
}
}
mTaskMap.remove(info.getId());
}
}
下载的原理 很简单,就是通过目标的URL 拿到流,然后写到本地。
因为下载在 run()里面执行,这个DownloadTask 类 我们就看run() 方法的实现,所以 关键代码 就是下面一点点
fos = new FileOutputStream(file, true);
int count = -1;
byte[] buffer = new byte[1024];
while (((count = stream.read(buffer)) != -1)
&& info.getDownloadState() == STATE_DOWNLOADING) {
// 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
fos.write(buffer, 0, count);
fos.flush();
info.setCurrentSize(info.getCurrentSize() + count);
notifyDownloadProgressed(info);// 刷新进度
}
这个在我们刚接触Java 的时候 肯定都写过了。 这就是往本地写数据的代码。所以run()方法中的 前面 就是拿到 stream 输入流, 以及 把file 创建出来。
刷新进度,状态
关于控制 button中text 显示 暂停 ,下载,还是进度,就靠 notifyDownloadProgressed(info);和 notifyDownloadStateChanged(info)两个方法, 这两个方法 实际上调用的是两个接口,只要我们在我们需要改变界面的类里 实现这两个接口,就可以接收到 包含最新信息的info对象。而我们在哪个类里改变button 上面 显示的文字呢? 当然是在 我们的adapter 里面了,大家都知道 是在 adapter 的getView() 方法里面 加载的每一条数据的布局。
那就一起看下是不是这样子呢?
public class RecommendAdapter extends BaseAdapter implements
DownloadManager.DownloadObserver {
ArrayList<AppInfo> list;
private List<ViewHolder> mDisplayedHolders;
private FinalBitmap finalBitmap;
private Context context;
public RecommendAdapter(ArrayList<AppInfo> list, FinalBitmap finalBitmap,
Context context) {
this.list = list;
this.context = context;
this.finalBitmap = finalBitmap;
mDisplayedHolders = new ArrayList<ViewHolder>();
}
public void startObserver() {
DownloadManager.getInstance().registerObserver(this);
}
public void stopObserver() {
DownloadManager.getInstance().unRegisterObserver(this);
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final AppInfo appInfo = list.get(position);
final ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder(context);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.setData(appInfo);
mDisplayedHolders.add(holder);
return holder.getRootView();
}
@Override
public void onDownloadStateChanged(DownloadInfo info) {
refreshHolder(info);
}
@Override
public void onDownloadProgressed(DownloadInfo info) {
refreshHolder(info);
}
public List<ViewHolder> getDisplayedHolders() {
synchronized (mDisplayedHolders) {
return new ArrayList<ViewHolder>(mDisplayedHolders);
}
}
public void clearAllItem() {
if (list != null){
list.clear();
}
if (mDisplayedHolders != null) {
mDisplayedHolders.clear();
}
}
public void addItems(ArrayList<AppInfo> infos) {
list.addAll(infos);
}
private void refreshHolder(final DownloadInfo info) {
List<ViewHolder> displayedHolders = getDisplayedHolders();
for (int i = 0; i < displayedHolders.size(); i++) {
final ViewHolder holder = displayedHolders.get(i);
AppInfo appInfo = holder.getData();
if (appInfo.getId() == info.getId()) {
AppUtil.post(new Runnable() {
@Override
public void run() {
holder.refreshState(info.getDownloadState(),
info.getProgress());
}
});
}
}
}
public class ViewHolder {
public TextView textView01;
public TextView textView02;
public TextView textView03;
public TextView textView04;
public ImageView imageView_icon;
public Button button;
public LinearLayout linearLayout;
public AppInfo mData;
private DownloadManager mDownloadManager;
private int mState;
private float mProgress;
protected View mRootView;
private Context context;
private boolean hasAttached;
public ViewHolder(Context context) {
mRootView = initView();
mRootView.setTag(this);
this.context = context;
}
public View getRootView() {
return mRootView;
}
public View initView() {
View view = AppUtil.inflate(R.layout.item_recommend_award);
imageView_icon = (ImageView) view
.findViewById(R.id.imageview_task_app_cion);
textView01 = (TextView) view
.findViewById(R.id.textview_task_app_name);
textView02 = (TextView) view
.findViewById(R.id.textview_task_app_size);
textView03 = (TextView) view
.findViewById(R.id.textview_task_app_desc);
textView04 = (TextView) view
.findViewById(R.id.textview_task_app_love);
button = (Button) view.findViewById(R.id.button_task_download);
linearLayout = (LinearLayout) view
.findViewById(R.id.linearlayout_task);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("mState:173 "+mState);
if (mState == DownloadManager.STATE_NONE
|| mState == DownloadManager.STATE_PAUSED
|| mState == DownloadManager.STATE_ERROR) {
mDownloadManager.download(mData);
} else if (mState == DownloadManager.STATE_WAITING
|| mState == DownloadManager.STATE_DOWNLOADING) {
mDownloadManager.pause(mData);
} else if (mState == DownloadManager.STATE_DOWNLOADED) {
// tell2Server();
mDownloadManager.install(mData);
}
}
});
return view;
}
public void setData(AppInfo data) {
if (mDownloadManager == null) {
mDownloadManager = DownloadManager.getInstance();
}
String filepath= FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + data.getName() + ".apk";
boolean existsFile = FileUtil.isExistsFile(filepath);
if(existsFile){
int fileSize = FileUtil.getFileSize(filepath);
if(data.getSize()==fileSize){
DownloadInfo downloadInfo = DownloadInfo.clone(data);
downloadInfo.setCurrentSize(data.getSize());
downloadInfo.setHasFinished(true);
mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
}
// else if(fileSize>0){
// DownloadInfo downloadInfo = DownloadInfo.clone(data);
// downloadInfo.setCurrentSize(data.getSize());
// downloadInfo.setHasFinished(false);
// mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
// }
}
DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(data
.getId());
if (downloadInfo != null) {
mState = downloadInfo.getDownloadState();
mProgress = downloadInfo.getProgress();
} else {
mState = DownloadManager.STATE_NONE;
mProgress = 0;
}
this.mData = data;
refreshView();
}
public AppInfo getData() {
return mData;
}
public void refreshView() {
linearLayout.removeAllViews();
AppInfo info = getData();
textView01.setText(info.getName());
textView02.setText(FileUtil.FormetFileSize(info.getSize()));
textView03.setText(info.getDes());
textView04.setText(info.getDownloadNum() + "下载量);
finalBitmap.display(imageView_icon, info.getIconUrl());
if (info.getType().equals("0")) {
// mState = DownloadManager.STATE_READ;
textView02.setVisibility(View.GONE);
}else{
String path=FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + info.getName() + ".apk";
hasAttached = FileUtil.isValidAttach(path, false);
DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(info
.getId());
if (downloadInfo != null && hasAttached) {
if(downloadInfo.isHasFinished()){
mState = DownloadManager.STATE_DOWNLOADED;
}else{
mState = DownloadManager.STATE_PAUSED;
}
} else {
mState = DownloadManager.STATE_NONE;
if(downloadInfo !=null){
downloadInfo.setDownloadState(mState);
}
}
}
refreshState(mState, mProgress);
}
public void refreshState(int state, float progress) {
mState = state;
mProgress = progress;
switch (mState) {
case DownloadManager.STATE_NONE:
button.setText(R.string.app_state_download);
break;
case DownloadManager.STATE_PAUSED:
button.setText(R.string.app_state_paused);
break;
case DownloadManager.STATE_ERROR:
button.setText(R.string.app_state_error);
break;
case DownloadManager.STATE_WAITING:
button.setText(R.string.app_state_waiting);
break;
case DownloadManager.STATE_DOWNLOADING:
button.setText((int) (mProgress * 100) + "%");
break;
case DownloadManager.STATE_DOWNLOADED:
button.setText(R.string.app_state_downloaded);
break;
// case DownloadManager.STATE_READ:
// button.setText(R.string.app_state_read);
// break;
default:
break;
}
}
}
}
何时 注册 监听observer
里面代码有点多,那就看startObserver()方法做了什么。
public void startObserver() {
DownloadManager.getInstance().registerObserver(this);
}
这里 是 注册了observer, Observer 是什么东西?在DownloadManager 中我们定义了
public interface DownloadObserver {
public void onDownloadStateChanged(DownloadInfo info);
public void onDownloadProgressed(DownloadInfo info);
}
一个接口,里面有两个抽象方法 一个是 进度,另一个是下载状态。
那回过头来,屡一下, 我们在 下载的关键代码里面调用了
DownloadObserver onDownloadProgressed()
DownloadObserver.onDownloadStateChanged()
两个抽象方法,而我们在 adapter
@Override
public void onDownloadStateChanged(DownloadInfo info) {
refreshHolder(info);
}
@Override
public void onDownloadProgressed(DownloadInfo info) {
refreshHolder(info);
}
中实现了 这两个方法 就可以轻松的控制 去 刷新 和改变 下载状态了。
细心的朋友 或许 发现问题了,对,我们还没有注册Observer,就在 DownloadManager 中去调用了。
这里 在看下DownloadManager 中 调用的方法
/
** 当下载状态发送改变的时候回调 */
public void notifyDownloadStateChanged(DownloadInfo info) {
synchronized (mObservers) {
for (DownloadObserver observer : mObservers) {
observer.onDownloadStateChanged(info);
}
}
}
public void notifyDownloadProgressed(DownloadInfo info) {
synchronized (mObservers) {
for (DownloadObserver observer : mObservers) {
observer.onDownloadProgressed(info);
}
}
}
是的,这里我们遍历一个observer 容器,然后去刷新 ,所以我们还需要 把 Observer 对象 添加到 集合 mObservers 中,
所以肯定有这样一个方法 讲 observer 添加到集合中 。
public void unRegisterObserver(DownloadObserver observer) {
synchronized (mObservers) {
if (mObservers.contains(observer)) {
mObservers.remove(observer);
}
}
}
所以最后一步,因为 adapter 方法中有 startObserver, 所以 我们在 主界面 MainActivity 的类中调用 adapter.startObser() 将 实现了 接口的adapter 对象 添加到 Observer 容器中 就可以了。
OK。大功告成!
=============================================
DownloadManager 代码
这里 贴一下DownloadManager 代码
public class DownloadManager {
public static final int STATE_NONE = 0;
public static final int STATE_WAITING = 1;
public static final int STATE_DOWNLOADING = 2;
public static final int STATE_PAUSED = 3;
public static final int STATE_DOWNLOADED = 4;
public static final int STATE_ERROR = 5;
// public static final int STATE_READ = 6;
private static DownloadManager instance;
private DownloadManager() {
}
private Map<Long, DownloadInfo> mDownloadMap = new ConcurrentHashMap<Long, DownloadInfo>();
private List<DownloadObserver> mObservers = new ArrayList<DownloadObserver>();
private Map<Long, DownloadTask> mTaskMap = new ConcurrentHashMap<Long, DownloadTask>();
public static synchronized DownloadManager getInstance() {
if (instance == null) {
instance = new DownloadManager();
}
return instance;
}
public void registerObserver(DownloadObserver observer) {
synchronized (mObservers) {
if (!mObservers.contains(observer)) {
mObservers.add(observer);
}
}
}
public void unRegisterObserver(DownloadObserver observer) {
synchronized (mObservers) {
if (mObservers.contains(observer)) {
mObservers.remove(observer);
}
}
}
public void notifyDownloadStateChanged(DownloadInfo info) {
synchronized (mObservers) {
for (DownloadObserver observer : mObservers) {
observer.onDownloadStateChanged(info);
}
}
}
public void notifyDownloadProgressed(DownloadInfo info) {
synchronized (mObservers) {
for (DownloadObserver observer : mObservers) {
observer.onDownloadProgressed(info);
}
}
}
public synchronized void download(AppInfo appInfo) {
// 先判断是否有这个app的下载信息
DownloadInfo info = mDownloadMap.get(appInfo.getId());
if (info == null) {// 如果没有,则根据appInfo创建一个新的下载信息
info = DownloadInfo.clone(appInfo);
mDownloadMap.put(appInfo.getId(), info);
}
// 判断状态是否为STATE_NONE、STATE_PAUSED、STATE_ERROR。只有这3种状态才能进行下载,其他状态不予处理
if (info.getDownloadState() == STATE_NONE
|| info.getDownloadState() == STATE_PAUSED
|| info.getDownloadState() == STATE_ERROR) {
// 下载之前,把状态设置为STATE_WAITING,因为此时并没有产开始下载,只是把任务放入了线程池中,当任务真正开始执行时,才会改为STATE_DOWNLOADING
info.setDownloadState(STATE_WAITING);
notifyDownloadStateChanged(info);// 每次状态发生改变,都需要回调该方法通知所有观察者
DownloadTask task = new DownloadTask(info);// 创建一个下载任务,放入线程池
mTaskMap.put(info.getId(), task);
ThreadManager.getDownloadPool().execute(task);
}
}
public synchronized void pause(AppInfo appInfo) {
stopDownload(appInfo);
DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
if (info != null) {// 修改下载状态
info.setDownloadState(STATE_PAUSED);
notifyDownloadStateChanged(info);
}
}
public synchronized void cancel(AppInfo appInfo) {
stopDownload(appInfo);
DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
if (info != null) {// 修改下载状态并删除文件
info.setDownloadState(STATE_NONE);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);
File file = new File(info.getPath());
file.delete();
}
}
public synchronized void install(AppInfo appInfo) {
stopDownload(appInfo);
DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
if (info != null) {// 发送安装的意图
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.setDataAndType(Uri.parse("file://" + info.getPath()),
"application/vnd.android.package-archive");
AppUtil.getContext().startActivity(installIntent);
}
notifyDownloadStateChanged(info);
}
public synchronized void open(AppInfo appInfo) {
try {
Context context = AppUtil.getContext();
// 获取启动Intent
Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(appInfo.getPackageName());
context.startActivity(intent);
} catch (Exception e) {
}
}
private void stopDownload(AppInfo appInfo) {
DownloadTask task = mTaskMap.remove(appInfo.getId());// 先从集合中找出下载任务
if (task != null) {
ThreadManager.getDownloadPool().cancel(task);// 然后从线程池中移除
}
}
public synchronized DownloadInfo getDownloadInfo(long id) {
return mDownloadMap.get(id);
}
public synchronized void setDownloadInfo(long id, DownloadInfo info) {
mDownloadMap.put(id, info);
}
public class DownloadTask implements Runnable {
private DownloadInfo info;
public DownloadTask(DownloadInfo info) {
this.info = info;
}
@Override
public void run() {
info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
notifyDownloadStateChanged(info);
File file = new File(info.getPath());// 获取下载文件
HttpResult httpResult = null;
InputStream stream = null;
if (info.getCurrentSize() == 0 || !file.exists()
|| file.length() != info.getCurrentSize()) {
// 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
info.setCurrentSize(0);
file.delete();
}
httpResult = HttpHelper.download(info.getUrl());
// else {
// // //文件存在且长度和进度相等,采用断点下载
// httpResult = HttpHelper.download(info.getUrl() + "&range=" +
// info.getCurrentSize());
// }
if (httpResult == null
|| (stream = httpResult.getInputStream()) == null) {
info.setDownloadState(STATE_ERROR);// 没有下载内容返回,修改为错误状态
notifyDownloadStateChanged(info);
} else {
try {
skipBytesFromStream(stream, info.getCurrentSize());
} catch (Exception e1) {
e1.printStackTrace();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
int count = -1;
byte[] buffer = new byte[1024];
while (((count = stream.read(buffer)) != -1)
&& info.getDownloadState() == STATE_DOWNLOADING) {
// 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
fos.write(buffer, 0, count);
fos.flush();
info.setCurrentSize(info.getCurrentSize() + count);
notifyDownloadProgressed(info);// 刷新进度
}
} catch (Exception e) {
info.setDownloadState(STATE_ERROR);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);
file.delete();
} finally {
IOUtils.close(fos);
if (httpResult != null) {
httpResult.close();
}
}
// 判断进度是否和app总长度相等
if (info.getCurrentSize() == info.getAppSize()) {
info.setDownloadState(STATE_DOWNLOADED);
notifyDownloadStateChanged(info);
} else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
notifyDownloadStateChanged(info);
} else {
info.setDownloadState(STATE_ERROR);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);// 错误状态需要删除文件
file.delete();
}
}
mTaskMap.remove(info.getId());
}
}
public interface DownloadObserver {
public abstract void onDownloadStateChanged(DownloadInfo info);
public abstract void onDownloadProgressed(DownloadInfo info);
}
private long skipBytesFromStream(InputStream inputStream, long n) {
long remaining = n;
// SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
int SKIP_BUFFER_SIZE = 10000;
// skipBuffer is initialized in skip(long), if needed.
byte[] skipBuffer = null;
int nr = 0;
if (skipBuffer == null) {
skipBuffer = new byte[SKIP_BUFFER_SIZE];
}
byte[] localSkipBuffer = skipBuffer;
if (n <= 0) {
return 0;
}
while (remaining > 0) {
try {
long skip = inputStream.skip(10000);
nr = inputStream.read(localSkipBuffer, 0,
(int) Math.min(SKIP_BUFFER_SIZE, remaining));
} catch (IOException e) {
e.printStackTrace();
}
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
}
有两点需要说明,关于点击暂停后,再继续下载有两种方式可以实现
第一种 点击暂停的时候 记录下载了 多少,然后 再点击 继续下载 时,告诉服务器, 让服务器接着 上次的数据 往本地传递,
代码是我们 DownloadTask 下载时候,判断一下
// //文件存在且长度和进度相等,采用断点下载
httpResult = HttpHelper.download(info.getUrl() + "&range=" + info.getCurrentSize());
通过 range 来区分 当前的下载size.
服务器 处理的代码 也很简单 就是一句话
String range = req.getParameter(“range”); 拿到 range 判断 range 存在不存在。
如果不存在
FileInputStream stream = new FileInputStream(file);
int count = -1;
byte[] buffer = new byte[1024];
while ((count = stream.read(buffer)) != -1) {
SystemClock.sleep(20);
out.write(buffer, 0, count);
out.flush();
}
stream.close();
out.close();
如果存在那么跳过range 个字节
RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(Long.valueOf(range));
int count = -1;
byte[] buffer = new byte[1024];
while ((count = raf.read(buffer)) != -1) {
SystemClock.sleep(10);
out.write(buffer, 0, count);
out.flush();
}
raf.close();
out.close();
另一种方式是本地处理,这个demo 中就是本地处理的, 但是有一个问题, 因为 Java api的原因 ,inputStream.skip() 方法 并不能准确的 跳过多少个字节,
而是 小于你想要跳过的字节,所以 你要去遍历 一直到 满足你要跳过的字节 在继续写, 因为 这样的方法有一个缺点,就是在下载很大的文件,
比如文件大小20M ,当已经下载了15M 此时你去暂停,在继续下载,那么要跳过前面的15M 将会话费很多时间。
此实现方式还有很多缺陷,所以在实际中要下载大的文件,还是不能用。
--------------------------------------------------------------------- 改进版-------------------------------------------------------------------------------
先来介绍下这次改进的两点:
第一点 ,前面说过 项目 只适合学习,作为商用的话, 效率不高,是因为当时点击暂停 ,在点击下载继续下载时候,如果文件前面下载部分较大,会比较慢,因为java 的 inputstream的 skip(longsize) 跳过字节 这个方法 并不能按照你 想要跳过的字节,而是跳过的往往是比较小的,所以要不断遍历,直到返回满足条件 ,比较耗时。打个比方,文件大小30M ,你下载了20M,你点了暂停然后继续点下载,就要跳过这20M,但是你用skip 方法 可能每次跳过4096 字节,这样要跳过20M的时间 就会很长。这样应该好理解。
第二点,原来 项目中,你这一次下载没有完成,下次在下载是删除掉原来的从新 下载,这次改成继续上次的地方接着下载。
吐槽下,关于下载,我最近一周 一直在看 开源的download, 但是 无奈水平有限,收获甚微,往往是看到最后 脑袋短路。大哭
这次改的方式比较简单,只改动了 项目中 DownloadManager 这个类。在来看下 DownloadManager这个类 的run 方法,
@Override
public void run() {
info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
notifyDownloadStateChanged(info);
File file = new File(info.getPath());// 获取下载文件
HttpResult httpResult = null;
InputStream stream = null;
if (info.getCurrentSize() == 0 || !file.exists()
|| file.length() != info.getCurrentSize()) {
// 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
<span> </span>info.setCurrentSize(0);
file.delete();
}
httpResult = HttpHelper.download(info.getUrl());
if (httpResult == null
|| (stream = httpResult.getInputStream()) == null) {
info.setDownloadState(STATE_ERROR);// 没有下载内容返回,修改为错误状态
notifyDownloadStateChanged(info);
} else {
try {
skipBytesFromStream(stream, info.getCurrentSize());
} catch (Exception e1) {
e1.printStackTrace();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
int count = -1;
byte[] buffer = new byte[1024];
while (((count = stream.read(buffer)) != -1)
&& info.getDownloadState() == STATE_DOWNLOADING) {
// 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
fos.write(buffer, 0, count);
fos.flush();
info.setCurrentSize(info.getCurrentSize() + count);
notifyDownloadProgressed(info);// 刷新进度
}
} catch (Exception e) {
info.setDownloadState(STATE_ERROR);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);
file.delete();
} finally {
IOUtils.close(fos);
if (httpResult != null) {
httpResult.close();
}
}
<span> </span>// 判断进度是否和app总长度相等
if (info.getCurrentSize() == info.getAppSize()) {
info.setDownloadState(STATE_DOWNLOADED);
notifyDownloadStateChanged(info);
} else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
notifyDownloadStateChanged(info);
} else {
info.setDownloadState(STATE_ERROR);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);// 错误状态需要删除文件
file.delete();
}
}
mTaskMap.remove(info.getId());
}
从服务器 返回的数据流 stream 最终是在 HttpHelper 这个类中
HttpResponse response = httpClient.execute(requestBase, httpContext);//访问网络
通过 httpclient 去联网请求的 。
我没有试过 httpclient addHeader("Range", "bytes=" + begin + "-" + end); 可不可以进行继续下载。
而是改成了 通过 httpurlconnection 去请求数据
现在 的run()方法是这样的。
@Override
public void run() {
info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
notifyDownloadStateChanged(info);
File file = new File(info.getPath());// 获取下载文件
// try {
try {
URL url = new URL(info.getUrl());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
if (!file.exists()) {
info.setCurrentSize(0);
file.delete();
} else if (file.length() > info.getAppSize()) {
info.setCurrentSize(0);
file.delete();
} else if (file.length() == info.getAppSize()) {
} else if (file.length() < info.getAppSize()) {
info.setCurrentSize(file.length());
}
if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) {
// 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
info.setCurrentSize(0);
file.delete();
} else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) {
conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize());
}
int code = conn.getResponseCode();
RandomAccessFile raf = new RandomAccessFile(file, "rw");
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024 * 8];
int len = -1;
int total = 0;// 当前线程下载的总的数据的长度
if (code == 200) {
} else if (code == 206) {
raf.seek(file.length());
}
while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下载数据的过程。
raf.write(buffer, 0, len);
total += len;// 需要记录当前的数据。
info.setCurrentSize(info.getCurrentSize() + len);
notifyDownloadProgressed(info);// 刷新进度
}
is.close();
raf.close();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 判断进度是否和app总长度相等
// } catch (Exception e) {
// System.out.println(e.toString());
// info.setDownloadState(STATE_ERROR);
// info.setCurrentSize(0);
// file.delete();
// e.printStackTrace();
// }
if (info.getCurrentSize() == info.getAppSize()) {
info.setDownloadState(STATE_DOWNLOADED);
notifyDownloadStateChanged(info);
} else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
notifyDownloadStateChanged(info);
} else {
info.setDownloadState(STATE_ERROR);
notifyDownloadStateChanged(info);
info.setCurrentSize(0);// 错误状态需要删除文件
file.delete();
}
mTaskMap.remove(info.getId());
}
先判断文件存不存在,以及大小是否满足条件, 在这里做判断
if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) {
// 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
info.setCurrentSize(0);
file.delete();
} else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) {
conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize());
}
如果 文件当前大小为0,或者文件不存在,或者长度不等于当前长度,则重新下载,否则 设置 Range
下面 判断 code 正常情况下code =200 表示成功,如果 设置了Range 那么 code 返回 206 表示正常。这个时候我们通过RandomAccessFile
RandomAccessFile 这个 类实现了 RandomAccessFile implements DataInput, DataOutput,就是一个既可以读也可以写的类。
RandomAccessFile 这个类来 处理 跳过多少字节, 前面 我说过 inpuStream.skeep() 方法 不准确,但是 RandomAccessFile 这个类是可以的。
RandomAccessFile raf = new RandomAccessFile(file, "rw");
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024 * 8];
int len = -1;
int total = 0;// 当前线程下载的总的数据的长度
if (code == 200) {
} else if (code == 206) {
raf.seek(file.length());
}
通过 seek 方法 跳过 这些字节。
然后
while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下载数据的过程。
raf.write(buffer, 0, len);
total += len;// 需要记录当前的数据。
info.setCurrentSize(info.getCurrentSize() + len);
notifyDownloadProgressed(info);// 刷新进度
}
is.close();
raf.close();
很普通的代码,把数据写出去。不断刷新当前进度, 最后关闭流。
这样就可以保证快速的暂停继续下载,并且 本次下载 没有完成,点了暂停,下次进应用,继续下载的时候 会接着上一次下载,但是断网,或者你自己把网关掉 ,下次在恢复网络,或者 在点下载,我并没有处理,有需要的就自己处理下吧,应该是捕获异常 seckouttimeException,然后保存数据。自己动手试下就知道了。
本次就到这里,希望对大家学习Android版多线程下载 仿下载助手(最新)有所启发。
您可能感兴趣的文章:android中多线程下载实例android实现多线程下载文件(支持暂停、取消、断点续传)Android实现多线程下载文件的方法Android编程开发实现带进度条和百分比的多线程下载Android FTP 多线程断点续传下载\上传的实例Android多线程+单线程+断点续传+进度条显示下载功能Android多线程断点续传下载功能实现代码Android实现多线程断点下载的方法Android实现多线程下载图片的方法Android线程池控制并发数多线程下载
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341