当前位置: 代码迷 >> Android >> android中提供多线程上载思路
  详细解决方案

android中提供多线程上载思路

热度:98   发布时间:2016-05-01 18:16:05.0
android中提供多线程下载思路

android中提供多线程下载思路
2010年12月13日
        单线程下载HTTP文件对我们来说是一件非常简单的事。那么,多线程断点下载的难点在哪里?1.多线程下载,2.支持断点。
  多线程下载:    
  如何才能从文件的指定位置处开始下载文件?(比如从50MB开始)这一点我们可以通过HTTP请求信息头来设置,还记得HTTP请求信息头的“Range”属性吗?
  断点:
  首要问题(多线程下载)已经被我们解决了,支持断点下载想必大家也已经想到了。就是将下载的进度保存到文件中,但在Android中却不能这么做。在Android平台中,我们需要向文件中写出下载的文件数据,还需要向另一个文件中写出下载进度,这样会出错。这样会导致有一个文件的内容没有被写出。所以我们就不能以文件的方式来保存下载进度,但可以通过数据库的方式保存下载进度。这两大问题我们已经有了解决思路,那么就开始动手编写吧!(以下源码仅供学习)
  1.创建Android工程
  Project name:MulThreadDownloader
  BuildTarget:Android2.1
  Application name:多线程断点下载
  Package name:com.changcheng.download
  Create Activity:MulThreadDownloader
  Min SDK Version:7
  2.AndroidManifest.xml
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
        
  
  3.strings.xml
  
  
  Hello World, DownloadActivity!
  多线程断点下载
  下载路径
  下载
  SDCard不存在或者写保护
  
  4.main.xml
  
  
  
  
  
  
  
  
  
     
  
  
  5.MulThreadDownloader
  package com.changcheng.download;
  import java.io.File;
  import com.changcheng.net.download.DownloadProgressListener;
  import com.changcheng.net.download.FileDownloader;
  import com.changcheng.download.R;
  import android.app.Activity;
  import android.os.Bundle;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.Message;
  import android.view.View;
  import android.widget.Button;
  import android.widget.EditText;
  import android.widget.ProgressBar;
  import android.widget.TextView;
  import android.widget.Toast;
  public class MulThreadDownloader extends Activity {
  private EditText pathText;
  private ProgressBar progressBar;
  private TextView resultView;
  private Handler handler = new Handler(){
[email protected]
  public void handleMessage(Message msg) {
  if(!Thread.currentThread().isInterrupted()){
  switch (msg.what) {
  case 1:
  // 获取当前文件下载的进度
  int size = msg.getData().getInt("size");
  progressBar.setProgress(size);
  int result = (int)(((float)size/(float)progressBar.getMax()) * 100);
  resultView.setText(result+ "%");
  if(progressBar.getMax() == size){
  Toast.makeText(MulThreadDownloader.this, "文件下载完成", 1).show();
  }
  break;
  case -1:
  String error = msg.getData().getString("error");
  Toast.makeText(MulThreadDownloader.this, error, 1).show();
  break;
  }                         
  }
  super.handleMessage(msg);
  }                
  };      
[email protected]
  public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  pathText = (EditText)this.findViewById(R.id.path);
  progressBar = (ProgressBar)this.findViewById(R.id.downloadbar);
  resultView = (TextView)this.findViewById(R.id.resultView);
  Button button = (Button)this.findViewById(R.id.button);
  button.setOnClickListener(new View.OnClickListener() {                          
[email protected]
  public void onClick(View v) {
  String path = pathText.getText().toString();
  if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
  //下载文件需要很长的时间,主线程是不能够长时间被阻塞,如果主线程被长时间阻塞, 那么Android被回收应用
  download(path, Environment.getExternalStorageDirectory());
  }else{
  Toast.makeText(MulThreadDownloader.this, R.string.sdcarderror, 1).show();
  }
  }
  });
  }
  /**
  * 下载文件
  * @param path 下载路径
  * @param saveDir 文件保存目录
  */
  //对于Android的UI控件,只能由主线程负责显示界面的更新,其他线程不能直接更新UI控件的显示
  public void download(final String path, final File saveDir){
  new Thread(new Runnable() {                        
[email protected]
  public void run() {
  FileDownloader downer = new FileDownloader(MulThreadDownloader.this, path, saveDir, 3);
  progressBar.setMax(downer.getFileSize());//设置进度条的最大刻度
  try {
  downer.download(new DownloadProgressListener(){
[email protected]
  public void onDownloadSize(int size) {
  Message msg = new Message();
  msg.what = 1;
  msg.getData().putInt("size", size);
  handler.sendMessage(msg);//发送消息
  }});
  } catch (Exception e) {
  Message msg = new Message();
  msg.what = -1;
  msg.getData().putString("error", "下载失败");
  handler.sendMessage(msg);
  }
  }
  }).start();
  }
  }
  6.FileDownload
  package com.changcheng.net.download;
  import java.io.File;
  import java.io.RandomAccessFile;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.util.LinkedHashMap;
  import java.util.Map;
  import java.util.UUID;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  import com.changcheng.download.service.FileService;
  import android.content.Context;
  import android.util.Log;
  /**
  * 文件下载器
  * @author [email protected]
  *
  */
  public class FileDownloader {
  private Context context;
  private FileService fileService;      
  private static final String TAG = "FileDownloader";
  /* 已下载文件大小 */
  private int downloadSize = 0;
  /* 原始文件大小 */
  private int fileSize = 0;
  /* 线程数 */
  private DownloadThread[] threads;
  /* 下载路径  */
  private URL url;
  /* 本地保存文件 */
  private File saveFile;
  /* 下载记录文件 */
  private File logFile;
  /* 缓存各线程最后下载的位置*/
  private Map data = new ConcurrentHashMap();
  /* 每条线程下载的大小 */
  private int block;
  private String downloadUrl;//下载路径
  /**
  * 获取线程数
  */
  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);
  }
  /**
  * 构建文件下载器
  * @param downloadUrl 下载路径
  * @param fileSaveDir 文件保存目录
  * @param threadNum 下载线程数
  */
  public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {
  try {
  this.context = context;
  this.downloadUrl = downloadUrl;
  fileService = new FileService(context);
  this.url = new URL(downloadUrl);
  if(!fileSaveDir.exists()) fileSaveDir.mkdirs();
  this.threads = new DownloadThread[threadNum];                                       
  HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  conn.setConnectTimeout(6*1000);
  conn.setRequestMethod("GET");
  conn.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, */*");
  conn.setRequestProperty("Accept-Language", "zh-CN");
  conn.setRequestProperty("Referer", downloadUrl);
  conn.setRequestProperty("Charset", "UTF-8");
  conn.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)");
  conn.setRequestProperty("Connection", "Keep-Alive");
  conn.connect();
  printResponseHeader(conn);
  if (conn.getResponseCode()==200) {
  this.fileSize = conn.getContentLength();//根据响应获取文件大小
  if (this.fileSize  logdata = fileService.getData(downloadUrl);
  if(logdata.size()>0){
  data.putAll(logdata);
  }
  this.block = this.fileSize / this.threads.length + 1;
  if(this.data.size()==this.threads.length){
  for (int i = 0; i 0) randOut.setLength(this.fileSize);                              
  randOut.seek(this.data.get(i+1));
  this.threads = new DownloadThread(this, this.url, randOut, this.block, this.data.get(i+1), i+1);
  this.threads.setPriority(7);
  this.threads.start();
  }else{
  this.threads = null;
  }
  }
  this.fileService.save(this.downloadUrl, this.data);
  boolean notFinish = true;//下载未完成
  while (notFinish) {// 循环判断是否下载完毕
  Thread.sleep(900);
  notFinish = false;//假定下载完成
  for (int i = 0; i  getHttpResponseHeader(HttpURLConnection http) {
  Map header = new LinkedHashMap();
  for (int i = 0;; i++) {
  String mine = http.getHeaderField(i);
  if (mine == null) break;
  header.put(http.getHeaderFieldKey(i), mine);
  }
  return header;
  }
  /**
  * 打印Http头字段
  * @param http
  */
  public static void printResponseHeader(HttpURLConnection http){
  Map header = getHttpResponseHeader(http);
  for(Map.Entry entry : header.entrySet()){
  String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";
  print(key+ entry.getValue());
  }
  }
  private static void print(String msg){
  Log.i(TAG, msg);
  }
  }
  7.DownloadProgressListener
  package com.changcheng.net.download;
  public interface DownloadProgressListener {
  public void onDownloadSize(int size);
  }
  8.FileService
  package com.changcheng.download.service;
  import java.util.HashMap;
  import java.util.Map;
  import android.content.Context;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
  /**
  * 业务bean
  *
  */
  public class FileService {
  private DBOpenHelper openHelper;
  public FileService(Context context) {
  openHelper = new DBOpenHelper(context);
  }
  /**
  * 获取线程最后下载位置
  * @param path
  * @return
  */
  public Map getData(String path){
  SQLiteDatabase db = openHelper.getReadableDatabase();
  Cursor cursor = db.rawQuery("select threadid, position from filedown where downpath=?", new String[]{path});
  Map data = new HashMap();
  while(cursor.moveToNext()){
  data.put(cursor.getInt(0), cursor.getInt(1));
  }
  cursor.close();
  db.close();
  return data;
  }
  /**
  * 保存下载线程初始位置
  * @param path
  * @param map
  */
  public void save(String path,  Map map){//int threadid, int position
  SQLiteDatabase db = openHelper.getWritableDatabase();
  db.beginTransaction();
  try{
  for(Map.Entry entry : map.entrySet()){
  db.execSQL("insert into filedown(downpath, threadid, position) values(?,?,?)",
  new Object[]{path, entry.getKey(), entry.getValue()});
  }
  db.setTransactionSuccessful();
  }finally{
  db.endTransaction();
  }
  db.close();
  }
  /**
  * 实时更新线程的最后下载位置
  * @param path
  * @param map
  */
  public void update(String path, Map map){
  SQLiteDatabase db = openHelper.getWritableDatabase();
  db.beginTransaction();
  try{
  for(Map.Entry entry : map.entrySet()){
  db.execSQL("update filedown set position=? where downpath=? and threadid=?",
  new Object[]{entry.getValue(), path, entry.getKey()});
  }
  db.setTransactionSuccessful();
  }finally{
  db.endTransaction();
  }
  db.close();
  }
  /**
  * 当文件下载完成后,清掉该文件对应的下载记录
  * @param path
  */
  public void delete(String path){
  SQLiteDatabase db = openHelper.getWritableDatabase();
  db.execSQL("delete from filedown where downpath=?", new Object[]{path});
  db.close();
  }
  }
  9.DownloadThread
  package com.changcheng.net.download;
  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 static final String TAG = "DownloadThread";
  private RandomAccessFile saveFile;
  private URL downUrl;
  private int block;
  /* 下载开始位置  */
  private int threadId = -1;
  private int startPos;
  private int downLength;
  private boolean finish = false;
  private FileDownloader downloader;
  public DownloadThread(FileDownloader downloader, URL downUrl, RandomAccessFile saveFile, int block, int startPos, int threadId) {
  this.downUrl = downUrl;
  this.saveFile = saveFile;
  this.block = block;
  this.startPos = startPos;
  this.downloader = downloader;
  this.threadId = threadId;
  this.downLength = startPos - (block * (threadId - 1));
  }
[email protected]
  public void run() {
  if(downLength 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 inStream = http.getInputStream();
  int max = block>1024 ? 1024 : (block>10 ? 10 : 1);
  byte[] buffer = new byte[max];
  int offset = 0;
  print("线程 " + this.threadId + "从位置"+ this.startPos+ "开始下载 ");
  while (downLength < block && (offset = inStream.read(buffer, 0, max)) != -1) {
  saveFile.write(buffer, 0, offset);
  downLength += offset;
  downloader.update(this.threadId, block * (threadId - 1) + downLength);
  downloader.saveLogFile();
  downloader.append(offset);
  int spare = block-downLength;//求剩下的字节数
  if(spare < max) max = (int) spare;
  }
  saveFile.close();
  inStream.close();                  
  print("线程 " + this.threadId + "完成下载 ");
  this.finish = true;
  this.interrupt();
  } catch (Exception e) {
  this.downLength = -1;
  print("线程"+ this.threadId+ ":"+ e);
  }
  }
  }
  private static void print(String msg){
  Log.i(TAG, msg);
  }
  /**
  * 下载是否完成
  * @return
  */
  public boolean isFinish() {
  return finish;
  }
  /**
  * 已经下载的内容大小
  * @return 如果返回值为-1,代表下载失败
  */
  public long getDownLength() {
  return downLength;
  }
  }
  11.DBOpenHelper
  package com.changcheng.download.service;
  import android.content.Context;
  import android.database.sqlite.SQLiteDatabase;
  import android.database.sqlite.SQLiteOpenHelper;
  public class DBOpenHelper extends SQLiteOpenHelper {
  private static final String DBNAME = "download.db";
  private static final int VERSION = 2;
  public DBOpenHelper(Context context) {
  super(context, DBNAME, null, VERSION);
  }
[email protected]
  public void onCreate(SQLiteDatabase db) {
  db.execSQL("CREATE TABLE IF NOT EXISTS filedown (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, position INTEGER)");
  }
[email protected]
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  db.execSQL("DROP TABLE IF EXISTS filedown");
  onCreate(db);
  }
  }
  相关解决方案