Android多线程断点下载

     到华为后,信息管理特别严格,文件不能外发,所以好久都没写博客了,今天周日,老婆非要我学习,就闲来无事,写一篇博客,呵呵……

     前段时间,项目中提到了断点下载apk并静默安装的需求,本打算用应用市场成熟的经验,结果人家不给借用,就只能自己写了,在网上找了一些资源,并自己封装了一下,就成了今天这篇博客的内容。

     断点下载的主要实现就是用SQlite数据库记录下断点时下载的记录,然后再次下载时,查到数据库的记录,再从断点处开始下载,本例中用的是多线程,好了,废话不多说,直接上代码。

    这里需要多说两句,大家的项目中以后如果涉及到多线程的,请一定要多考虑一些场景,比如非UI线程中逻辑处理是否全面、各分支场景处理是否得当、下载过程中的各种状态是否可以控制、控制的是否正确等等,问题比较多,大家一定要控制好,否则测试时应该会发现许多问题。


package com.genius.download.logic;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBOpenHelper extends SQLiteOpenHelper {
	private static final String DBNAME = "itcast.db";
	private static final int VERSION = 1;

	public DBOpenHelper(Context context) {
		super(context, DBNAME, null, VERSION);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("DROP TABLE IF EXISTS filedownlog");
		onCreate(db);
	}

}

     DownloadHelper为下载的管理类,是本人封装后的,构建对象后就可以直接执行下载:

package com.genius.download.logic;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

public class DownloadHelper {
	
	private final String TAG = getClass().getSimpleName();
	/***
	 * 钱包的文件存储目录
	 */
	private String walletFileDir = Environment.getExternalStorageDirectory()
			+ File.separator + "HwWallet/app";

	private FileService fileService;

	/** 已下载文件长度 **/
	private int downloadSize = 0;

	/** 原始文件长度 **/
	private int fileSize = 0;

	/** 线程数 **/
	private DownloadThread[] threads = new DownloadThread[5];

	/** 本地保存文件 */
	private File saveFile;

	/** 缓存各线程下载的长度 */
	private Map
   
   
    
     data = new ConcurrentHashMap
    
    
     
     ();

	/** 每条线程下载的长度 */
	private int block;

	/** 下载路径 **/
	private String downloadUrl;

	private boolean isFinish = false;// 下载未完成

	public DownloadHelper(final Context context, final String downloadUrl) {
		this.downloadUrl = downloadUrl;
		fileService = new FileService(context);
	}

	public void download(final DownloadListener listener) {
		new Thread() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				try {
					scheduleTaskForChildThread(listener);
					executeDownload(listener);
				} catch (Exception e) {
					Log.i(TAG, "occur exception in download.");
					listener.onDownloadFailed(-1);
					throw new RuntimeException("don't connection this url");
				}
			}

		}.start();
	}

	/**
	 * 为子线程分配任务 方法表述 void
	 */
	private void scheduleTaskForChildThread(DownloadListener listener) {
		try {
			File dir = new File(walletFileDir);
			if (!dir.exists()) {
				dir.mkdirs();
			}
			URL url = new URL(downloadUrl);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			fileSize = conn.getContentLength();// 根据响应获取文件大小
			if (fileSize <= 0) {
				listener.onDownloadFailed(-1);
				throw new RuntimeException("Unkown file size ");
			}
			listener.onGetFileSize(fileSize);
			Log.i(TAG, "file name is = " + getReallyFileName(downloadUrl));
			saveFile = new File(dir, getReallyFileName(downloadUrl));/* 保存文件 */
			Log.i(TAG, "save file path is " + saveFile.getPath());
			Map
     
     
      
       logdata = fileService.getData(downloadUrl);// 获取每条线程已经下载的文件长度
			if (logdata == null || logdata.size() == 0) {
				for (int i = 0; i < threads.length; i++) {
					data.put(i + 1, 0);
				}
			} else {
				for (Map.Entry
      
      
       
        entry : logdata.entrySet()) {
					data.put(entry.getKey(), entry.getValue());
				}
			}
			block = (fileSize % threads.length) == 0 ? fileSize
					/ threads.length : fileSize / threads.length + 1;
			Log.i(TAG, "this.block == " + block);
			if (data.size() == threads.length) {
				for (int i = 0; i < threads.length; i++) {
					downloadSize += data.get(i + 1);
				}
				Log.i(TAG, "completed download size is == " + downloadSize);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void executeDownload(DownloadListener listener) throws Exception {
		try {
			URL url = new URL(this.downloadUrl);
			if (this.data.size() != this.threads.length) {
				this.data.clear();
				for (int i = 0; i < this.threads.length; i++) {
					this.data.put(i + 1, 0);
				}
			}
			for (int i = 0; i < this.threads.length; i++) {
				int downLength = this.data.get(i + 1);
				if (downLength < this.block
						&& this.downloadSize < this.fileSize) { // 该线程未完成下载时,继续下载
					this.threads[i] = new DownloadThread(this, url,
							this.saveFile, this.block, this.data.get(i + 1),
							i + 1, listener);
					this.threads[i].setPriority(7);
					this.threads[i].start();
				} else {
					this.threads[i] = null;
				}
			}
			this.fileService.save(this.downloadUrl, this.data);
			while (!isFinish) {
				// 循环判断是否下载完毕
				Thread.sleep(900);
				isFinish = true;// 假定下载完成
				for (int i = 0; i < this.threads.length; i++) {
					if (this.threads[i] != null && !this.threads[i].isFinish()) {
						isFinish = false;// 下载没有完成
						if (this.threads[i].getDownLength() == -1) {// 如果下载失败,再重新下载
							this.threads[i] = new DownloadThread(this, url,
									this.saveFile, this.block,
									this.data.get(i + 1), i + 1, listener);
							this.threads[i].setPriority(7);
							this.threads[i].start();
						}
					}
				}
				if (listener != null) {
					listener.onDownloadSize(this.downloadSize);
				}
			}
			listener.onDownloadCompleted();
			fileService.delete(this.downloadUrl);
		} catch (RuntimeException e) {
			listener.onDownloadFailed(-1);
			throw new Exception("file download fail");
		}
		listener.onDownloadSize(this.downloadSize);
	}

	private String getReallyFileName(String url) {
		String filename = "";
		URL myURL;
		HttpURLConnection conn = null;
		if (url == null || url.length() < 1) {
			return "";
		}

		try {
			myURL = new URL(url);
			conn = (HttpURLConnection) myURL.openConnection();
			conn.connect();
			conn.getResponseCode();
			URL absUrl = conn.getURL();// 获得真实Url
			filename = matchFileName(conn);
			if (!TextUtils.isEmpty(filename)) {
				Log.i(TAG, "file name is = " + filename);
				return filename;
			}
			filename = conn.getHeaderField("Content-Disposition");// 通过Content-Disposition获取文件名,这点跟服务器有关,需要灵活变通
			if (filename == null || filename.length() < 1) {
				filename = absUrl.getFile();
			}
		} catch (MalformedURLException e) {
			Log.w(TAG, "MalformedURLException in download");
		} catch (IOException e) {
			Log.w(TAG, "IOException in download");
		} finally {
			if (conn != null) {
				conn.disconnect();
				conn = null;
			}
		}
		return filename;
	}

	/**
	 * 获取文件名
	 */
	private String matchFileName(HttpURLConnection conn) {
		String name = "";
		for (int i = 0;; i++) {
			String mine = conn.getHeaderField(i);
			if (mine == null)
				break;
			if ("content-disposition".equals(conn.getHeaderFieldKey(i)
					.toLowerCase())) {
				Matcher m = Pattern.compile(".*filename=(.*)").matcher(
						mine.toLowerCase());
				if (m.find()) {
					name = m.group(1);
					name = name.substring(1, name.length() - 1);
					Log.i(TAG, "matches, name is " + name);
					break;
				}
			}
		}
		return name;
	}

	/**
	 * 获取线程数
	 */
	public int getThreadSize() {
		return threads.length;
	}

	/**
	 * 获取文件大小
	 * 
	 * @return
	 */
	public int getFileSize() {
		return fileSize;
	}

	/**
	 * 累计已下载大小
	 * 
	 * @param size
	 */
	protected synchronized void append(int size) {
		downloadSize += size;
	}

	/**
	 * 更新指定线程最后下载的位置
	 * 
	 * @param threadId
	 *            线程id
	 * @param pos
	 *            最后下载的位置
	 */
	protected void update(int threadId, int pos) {
		this.data.put(threadId, pos);
	}

	/**
	 * 保存记录文件
	 */
	protected synchronized void saveLogFile() {
		this.fileService.update(this.downloadUrl, this.data);
	}

	public void cancelDownload() {
		for (DownloadThread thread : threads) {
			thread.cancelDownload();
		}
	}

	/**
	 * 获取下载的文件的保存路径 方法表述
	 * 
	 * @return String
	 */
	public String getSaveFilePath() {
		if (this.saveFile != null) {
			return this.saveFile.getPath();
		}
		return "";
	}
}

      
      
     
     
    
    
   
   

      DownloadThread即为管理线程任务的,网上找一下,应该可以找到很多类似的资源:
package com.genius.download.logic;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

import android.util.Log;

public class DownloadThread extends Thread {
	private final String TAG = getClass().getSimpleName();
	
	private static final int BUFFER_SIZE = 1024;

	private File saveFile;

	private URL downUrl;

	private int block;

	/* 下载开始位置 */
	private int threadId = -1;

	private int downLength;

	private boolean finish = false;
	private boolean cancel = false;

	private DownloadHelper mDownloadHelper;
	private DownloadListener listener;

	public DownloadThread(DownloadHelper downloader, URL downUrl,
			File saveFile, int block, int downLength, int threadId,
			DownloadListener listener) {
		this.mDownloadHelper = downloader;
		this.downUrl = downUrl;
		this.saveFile = saveFile;
		this.block = block;
		this.downLength = downLength;
		this.threadId = threadId;
		this.listener = listener;
	}

	@Override
	public void run() {
		if (downLength < block) {// 未下载完成
			try {
				HttpURLConnection http = (HttpURLConnection) downUrl
						.openConnection();
				http.setConnectTimeout(5 * 1000);
				http.setRequestMethod("GET");
				http.setRequestProperty(
						"Accept",
						"image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
				http.setRequestProperty("Accept-Language", "zh-CN");
				http.setRequestProperty("Referer", downUrl.toString());
				http.setRequestProperty("Charset", "UTF-8");
				int startPos = block * (threadId - 1) + downLength;// 开始位置

				int endPos = block * threadId - 1;// 结束位置
				// by ngj
				if (threadId == mDownloadHelper.getThreadSize()) {// 最后一个线程不要-1
					endPos += 1;
				}
				http.setRequestProperty("Range", "bytes=" + startPos + "-"
						+ endPos);// 设置获取实体数据的范围
				http.setRequestProperty(
						"User-Agent",
						"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
				http.setRequestProperty("Connection", "Keep-Alive");

				InputStream is = http.getInputStream();
				byte[] buffer = new byte[BUFFER_SIZE];
				int offset = 0;
				Log.i(TAG, "Thread " + this.threadId
						+ " start download from position " + startPos
						+ " end at " + endPos);
				RandomAccessFile raf = new RandomAccessFile(this.saveFile,
						"rwd");
				raf.seek(startPos);
				while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1) {
					if (cancel) {
						Log.i(TAG, "is canel download = " + cancel);
						break;
					}
					raf.write(buffer, 0, offset);
					downLength += offset;
					mDownloadHelper.update(this.threadId, downLength);
					mDownloadHelper.saveLogFile();
					mDownloadHelper.append(offset);
				}
				raf.close();
				is.close();
				http.disconnect();
				Log.i(TAG, "Thread " + this.threadId + " download finish");
				this.finish = true;
			} catch (IOException e) {
				Log.w(TAG, "occur ioexception.");
				listener.onDownloadFailed(-1);
				this.downLength = -1;
			}
		}
	}

	public void cancelDownload() {
		cancel = true;
	}

	/**
	 * 下载是否完成
	 * 
	 * @return
	 */
	public boolean isFinish() {
		return finish;
	}

	/**
	 * 已经下载的内容大小
	 * 
	 * @return 如果返回值为-1,代表下载失败
	 */
	public long getDownLength() {
		return downLength;
	}
}

DownloadListener就是下载时的同步回调,可以监测到目标文件大小、获取到当前下载的字节数、下载完成、下载失败的监听:
package com.genius.download.logic;

/**
 * **************************************************************** 文件名称 :
 * DownloadProgressListener.java 作 者 : mWX284740 创建时间 : 2015年8月19日 下午2:34:14
 * 文件描述 : 下载文件的回调接口 版权声明 : Copyright (C) 2008-2011 华为技术有限公司(Huawei Tech.Co.,Ltd)
 * 修改历史 : 2015年8月19日 1.00 初始版本
 ***************************************************************** 
 */
public interface DownloadListener {
	/**
	 * 下载了多少字节
	 * @param size
	 */
	public void onDownloadSize(int size);

	/**
	 * 下载完成
	 */
	public void onDownloadCompleted();

	/**
	 * 下载失败
	 * @param returnCode
	 */
	public void onDownloadFailed(int returnCode);

	/**
	 * 获取到目标文件大小
	 * @param size
	 */
	public void onGetFileSize(int size);
}

好了,就这么多了,有关静默安装的,见上也讲了很多,但是本人认为都不全面,只说了一点半点的,初步接触静默安装的人看了之后应该还是无法实现静默安装的功能,待下一博客再讲。
本代码中还是有一些问题,因为涉及到多线程,各种场景的考虑不够全面,以后再行优化,不对的地方,还请大家谅解,谢谢!
     项目源码


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红-旺永福

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值