【Android】多線程下載加斷點續傳

http://blog.csdn.net/smbroe/article/details/42270573java

       文件下載在App應用中也用到不少,通常版本更新時多要用的文件下載來進行處理,之前也有看過不少大神有過該方面的博客,今天我也本身來實踐一下,寫的通常,還請你們多提意見,共同進步。主要思路:android

        1.多線程下載:sql

               首先經過下載總線程數來劃分文件的下載區域:利用int range = fileSize / threadCount;獲得每一段下載量;每一段的位置是i * range到(i + 1) * rang  - 1,注意最後一段的位置是到filesize - 1;
數據庫

               經過Http協議的Range字段實現下載文件的分段;網絡

               經過Java類RandomAccessFile能夠實現文件的隨機訪問,利用seek方法定位的文件的指定位置;多線程

               由HttpUrlConnection獲取流來進行流的讀寫,實現文件的存儲;app

               在下載過程當中利用Handler來向外傳遞下載的信息。
dom

         2.斷點續傳:異步

               對於每個線程利用一個DownloadInfo類來保存下載的信息,每次在下載過程當中向數據庫更新信息(我也有想過只在下載暫停時進行更新,但那樣的話咱們的進程被殺掉時信息就沒法保存下來)。在進行下載以前去訪問數據庫是否有記錄存在,若是沒有執行第一次下載的初始化,若是存在記錄但下載文件不存在時,刪掉數據庫中的記錄以後進行第一次下載的初始化,若是有記錄且文件存在,則從數據庫中取出信息。ide

         實現的效果如圖,本身封裝的類提供了開始,暫停,刪除,以及從新下載的方法。還沒來得及將工程穿上CSDN,給你們一個百度雲盤的下載地址:http://pan.baidu.com/s/1dD1Xo8T 

                                                                                                             

             主要類的結構有:

                     1)DownloadHttpTool:利用Http協議進行網絡下載類

                     2)DownlaodSqlTool, DownLoadHelper:數據庫相關操做類

                     3)DownloadInfo:下載信息保存類

                     4)DownloadUtil:封裝下載方法並提供對外接口的類

 貼一下代碼:DownloadInfo類

package com.example.test;
public class DownloadInfo {
 /**
  * 保存每一個下載線程下載信息類
  * 
  */
 
 private int threadId;// 下載器id
 private int startPos;// 開始點
 private int endPos;// 結束點
 private int compeleteSize;// 完成度
 private String url;// 下載文件的URL地址
 
 public DownloadInfo(int threadId, int startPos, int endPos,
   int compeleteSize, String url) {
  this.threadId = threadId;
  this.startPos = startPos;
  this.endPos = endPos;
  this.compeleteSize = compeleteSize;
  this.url = url;
 }
 public DownloadInfo() {
 }
 public String getUrl() {
  return url;
 }
 public void setUrl(String url) {
  this.url = url;
 }
 public int getThreadId() {
  return threadId;
 }
 public void setThreadId(int threadId) {
  this.threadId = threadId;
 }
 public int getStartPos() {
  return startPos;
 }
 public void setStartPos(int startPos) {
  this.startPos = startPos;
 }
 public int getEndPos() {
  return endPos;
 }
 public void setEndPos(int endPos) {
  this.endPos = endPos;
 }
 public int getCompeleteSize() {
  return compeleteSize;
 }
 public void setCompeleteSize(int compeleteSize) {
  this.compeleteSize = compeleteSize;
 }
 @Override
 public String toString() {
  return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos
    + ", endPos=" + endPos + ", compeleteSize=" + compeleteSize
    + "]";
 }
}

DownLoadHelper類,建立咱們的數據庫文件

package com.example.test;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
 * 利用數據庫來記錄下載信息
 * @author acer
 */
public class DownLoadHelper extends SQLiteOpenHelper{
 private static final String SQL_NAME = "download.db";
 private static final int DOWNLOAD_VERSION=1;
 
 public DownLoadHelper(Context context) {
  super(context, SQL_NAME, null, DOWNLOAD_VERSION);
  // TODO Auto-generated constructor stub
 }
 
  /**
     * 在download.db數據庫下建立一個download_info表存儲下載信息
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
                + "start_pos integer, end_pos integer, compelete_size integer,url char)");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

DownlaodSqlTool進行數據的插入更新刪除等操做

package com.example.test;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
/**
 * 
 * 數據庫操做工具類
 */
public class DownlaodSqlTool {
    private DownLoadHelper dbHelper;
    public DownlaodSqlTool(Context context) {
        dbHelper = new DownLoadHelper(context);
    }
    /**
     * 建立下載的具體信息
     */
    public void insertInfos(List<DownloadInfo> infos) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        for (DownloadInfo info : infos) {
            String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
            Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
                    info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
            database.execSQL(sql, bindArgs);
        }
    }
    /**
     * 獲得下載具體信息
     */
    public List<DownloadInfo> getInfos(String urlstr) {
        List<DownloadInfo> list = new ArrayList<DownloadInfo>();
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
        Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
        while (cursor.moveToNext()) {
            DownloadInfo info = new DownloadInfo(cursor.getInt(0),
                    cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
                    cursor.getString(4));
            list.add(info);
        }
        return list;
    }
    /**
     * 更新數據庫中的下載信息
     */
    public void updataInfos(int threadId, int compeleteSize, String urlstr) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "update download_info set compelete_size=? where thread_id=? and url=?";
        Object[] bindArgs = { compeleteSize, threadId, urlstr };
        database.execSQL(sql, bindArgs);
    }
    /**
     * 關閉數據庫
     */
    public void closeDb() {
        dbHelper.close();
    }
    /**
     * 下載完成後刪除數據庫中的數據
     */
    public void delete(String url) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        database.delete("download_info", "url=?", new String[] { url });
    }
}

DownloadHttpTool進行網絡下載的類

package com.example.test;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class DownloadHttpTool {
 /**
  * 
  * 利用Http協議進行多線程下載具體實踐類
  */
 private static final String TAG = DownloadHttpTool.class.getSimpleName();
 private int threadCount;//線程數量
 private String urlstr;//URL地址
 private Context mContext;
 private Handler mHandler;
 private List<DownloadInfo> downloadInfos;//保存下載信息的類
 private String localPath;//目錄
 private String fileName;//文件名
 private int fileSize;
 private DownlaodSqlTool sqlTool;//文件信息保存的數據庫操做類
 private enum Download_State {
  Downloading, Pause, Ready;//利用枚舉表示下載的三種狀態
 }
 private Download_State state = Download_State.Ready;//當前下載狀態
 private int globalCompelete = 0;//全部線程下載的總數
 public DownloadHttpTool(int threadCount, String urlString,
   String localPath, String fileName, Context context, Handler handler) {
  super();
  this.threadCount = threadCount;
  this.urlstr = urlString;
  this.localPath = localPath;
  this.mContext = context;
  this.mHandler = handler;
  this.fileName = fileName;
  sqlTool = new DownlaodSqlTool(mContext);
 }
 //在開始下載以前須要調用ready方法進行配置
 public void ready() {
  Log.w(TAG, "ready");
  globalCompelete = 0;
  downloadInfos = sqlTool.getInfos(urlstr);
  if (downloadInfos.size() == 0) {
   initFirst();
  } else {
   File file = new File(localPath + "/" + fileName);
   if (!file.exists()) {
    sqlTool.delete(urlstr);
    initFirst();
   } else {
    fileSize = downloadInfos.get(downloadInfos.size() - 1)
      .getEndPos();
    for (DownloadInfo info : downloadInfos) {
     globalCompelete += info.getCompeleteSize();
    }
    Log.w(TAG, "globalCompelete:::" + globalCompelete);
   }
  }
 }
 public void start() {
  Log.w(TAG, "start");
  if (downloadInfos != null) {
   if (state == Download_State.Downloading) {
    return;
   }
   state = Download_State.Downloading;
   for (DownloadInfo info : downloadInfos) {
    Log.v(TAG, "startThread");
    new DownloadThread(info.getThreadId(), info.getStartPos(),
      info.getEndPos(), info.getCompeleteSize(),
      info.getUrl()).start();
   }
  }
 }
 public void pause() {
  state = Download_State.Pause;
  sqlTool.closeDb();
 }
 
 public void delete(){
  compelete();
  File file = new File(localPath + "/" + fileName);
  file.delete();
 }
 
 public void compelete() {
  sqlTool.delete(urlstr);
  sqlTool.closeDb();
 }
 public int getFileSize() {
  return fileSize;
 }
 public int getCompeleteSize() {
  return globalCompelete;
 }
 //第一次下載初始化
 private void initFirst() {
  Log.w(TAG, "initFirst");
  try {
   URL url = new URL(urlstr);
   HttpURLConnection connection = (HttpURLConnection) url
     .openConnection();
   connection.setConnectTimeout(5000);
   connection.setRequestMethod("GET");
   fileSize = connection.getContentLength();
   Log.w(TAG, "fileSize::" + fileSize);
   File fileParent = new File(localPath);
   if (!fileParent.exists()) {
    fileParent.mkdir();
   }
   File file = new File(fileParent, fileName);
   if (!file.exists()) {
    file.createNewFile();
   }
   // 本地訪問文件
   RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
   accessFile.setLength(fileSize);
   accessFile.close();
   connection.disconnect();
  } catch (Exception e) {
   e.printStackTrace();
  }
  int range = fileSize / threadCount;
  downloadInfos = new ArrayList<DownloadInfo>();
  for (int i = 0; i < threadCount - 1; i++) {
   DownloadInfo info = new DownloadInfo(i, i * range, (i + 1) * range
     - 1, 0, urlstr);
   downloadInfos.add(info);
  }
  DownloadInfo info = new DownloadInfo(threadCount - 1, (threadCount - 1)
    * range, fileSize - 1, 0, urlstr);
  downloadInfos.add(info);
  sqlTool.insertInfos(downloadInfos);
 }
 //自定義下載線程
 private class DownloadThread extends Thread {
  private int threadId;
  private int startPos;
  private int endPos;
  private int compeleteSize;
  private String urlstr;
  private int totalThreadSize;
  public DownloadThread(int threadId, int startPos, int endPos,
    int compeleteSize, String urlstr) {
   this.threadId = threadId;
   this.startPos = startPos;
   this.endPos = endPos;
   totalThreadSize = endPos - startPos + 1;
   this.urlstr = urlstr;
   this.compeleteSize = compeleteSize;
  }
  @Override
  public void run() {
   HttpURLConnection connection = null;
   RandomAccessFile randomAccessFile = null;
   InputStream is = null;
   try {
    randomAccessFile = new RandomAccessFile(localPath + "/"
      + fileName, "rwd");
    randomAccessFile.seek(startPos + compeleteSize);
    URL url = new URL(urlstr);
    connection = (HttpURLConnection) url.openConnection();
    connection.setConnectTimeout(5000);
    connection.setRequestMethod("GET");
    connection.setRequestProperty("Range", "bytes="
      + (startPos + compeleteSize) + "-" + endPos);
    is = connection.getInputStream();
    byte[] buffer = new byte[1024];
    int length = -1;
    while ((length = is.read(buffer)) != -1) {
     randomAccessFile.write(buffer, 0, length);
     compeleteSize += length;
     Message message = Message.obtain();
     message.what = threadId;
     message.obj = urlstr;
     message.arg1 = length;
     mHandler.sendMessage(message);
     sqlTool.updataInfos(threadId, compeleteSize, urlstr);
     Log.w(TAG, "Threadid::" + threadId + "    compelete::"
       + compeleteSize + "    total::" + totalThreadSize);
     if (compeleteSize >= totalThreadSize) {
      break;
     }
     if (state != Download_State.Downloading) {
      break;
     }
    }
   } catch (Exception e) {
    e.printStackTrace();
   } finally {
    try {
     if (is != null) {
      is.close();
     }
     randomAccessFile.close();
     connection.disconnect();
    } catch (Exception e) {
     e.printStackTrace();
    }
   }
  }
 }
}

DownloadUtils提供下載向外接口方法類:

package com.example.test;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
 * 將下載方法封裝在此類
 * 提供下載,暫停,刪除,以及重置的方法
 */
public class DownloadUtil {
 private DownloadHttpTool mDownloadHttpTool;
 private OnDownloadListener onDownloadListener;
 private int fileSize;
 private int downloadedSize = 0;
 @SuppressLint("HandlerLeak")
 private Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   // TODO Auto-generated method stub
   super.handleMessage(msg);
   int length = msg.arg1;
   synchronized (this) {//加鎖保證已下載的正確性
    downloadedSize += length;
   }
   if (onDownloadListener != null) {
    onDownloadListener.downloadProgress(downloadedSize);
   }
   if (downloadedSize >= fileSize) {
    mDownloadHttpTool.compelete();
    if (onDownloadListener != null) {
     onDownloadListener.downloadEnd();
    }
   }
  }
 };
 public DownloadUtil(int threadCount, String filePath, String filename,
   String urlString, Context context) {
  mDownloadHttpTool = new DownloadHttpTool(threadCount, urlString,
    filePath, filename, context, mHandler);
 }
 //下載以前首先異步線程調用ready方法得到文件大小信息,以後調用開始方法
 public void start() {
  new AsyncTask<Void, Void, Void>() {
   @Override
   protected Void doInBackground(Void... arg0) {
    // TODO Auto-generated method stub
    mDownloadHttpTool.ready();
    return null;
   }
   @Override
   protected void onPostExecute(Void result) {
    // TODO Auto-generated method stub
    super.onPostExecute(result);
    fileSize = mDownloadHttpTool.getFileSize();
    downloadedSize = mDownloadHttpTool.getCompeleteSize();
    Log.w("Tag", "downloadedSize::" + downloadedSize);
    if (onDownloadListener != null) {
     onDownloadListener.downloadStart(fileSize);
    }
    mDownloadHttpTool.start();
   }
  }.execute();
 }
 public void pause() {
  mDownloadHttpTool.pause();
 }
 
 public void delete(){
  mDownloadHttpTool.delete();
 }
 public void reset(){
  mDownloadHttpTool.delete();
  start();
 }
 
 public void setOnDownloadListener(OnDownloadListener onDownloadListener) {
  this.onDownloadListener = onDownloadListener;
 }
 //下載回調接口
 public interface OnDownloadListener {
  public void downloadStart(int fileSize);
  public void downloadProgress(int downloadedSize);//記錄當前全部線程下總和
  public void downloadEnd();
 }
}

在MainActivity

package com.example.test;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.test.DownloadUtil.OnDownloadListener;
public class MainActivity extends FragmentActivity {
 private static final String TAG = MainActivity.class.getSimpleName();
 private ProgressBar mProgressBar;
 private Button start;
 private Button pause;
 private Button delete;
 private Button reset;
 private TextView total;
 private int max;
 private DownloadUtil mDownloadUtil;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
  start = (Button) findViewById(R.id.button_start);
  pause = (Button) findViewById(R.id.button_pause);
  delete = (Button) findViewById(R.id.button_delete);
  reset = (Button) findViewById(R.id.button_reset);
  total = (TextView) findViewById(R.id.textView_total);
  String urlString = "http://bbra.cn/Uploadfiles/imgs/20110303/fengjin/013.jpg";
  String localPath = Environment.getExternalStorageDirectory()
    .getAbsolutePath() + "/local";
  mDownloadUtil = new DownloadUtil(2, localPath, "abc.jpg", urlString,
    this);
  mDownloadUtil.setOnDownloadListener(new OnDownloadListener() {
   @Override
   public void downloadStart(int fileSize) {
    // TODO Auto-generated method stub
    Log.w(TAG, "fileSize::" + fileSize);
    max = fileSize;
    mProgressBar.setMax(fileSize);
   }
   @Override
   public void downloadProgress(int downloadedSize) {
    // TODO Auto-generated method stub
    Log.w(TAG, "Compelete::" + downloadedSize);
    mProgressBar.setProgress(downloadedSize);
    total.setText((int) downloadedSize * 100 / max + "%");
   }
   @Override
   public void downloadEnd() {
    // TODO Auto-generated method stub
    Log.w(TAG, "ENd");
   }
  });
  start.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub
    mDownloadUtil.start();
   }
  });
  pause.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub
    mDownloadUtil.pause();
   }
  });
  delete.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub
    mDownloadUtil.delete();
   }
  });
  reset.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    // TODO Auto-generated method stub
    mDownloadUtil.reset();
   }
  });
 }
}
相關文章
相關標籤/搜索