Android原生實現(xiàn)多線程斷點下載實例代碼
各位父老鄉(xiāng)親,我單漢三又回來了,今天為大家?guī)硪粋€用原生的安卓寫的多線程斷點下載Demo。
通過本文你可以學習到:
- SQLite的基本使用,數(shù)據(jù)庫的增刪改查。
- Handler的消息處理與更新UI。
- Service(主要用于下載)的進階與使用。
- 原生的json文件解析(多層嵌套)。
- RandomAccessFile的基本使用,可以將文件分段。
- 基于HttpURLConnection的大文件下載。
- 上面內容結合,實現(xiàn)多線程,斷點下載。
Demo是在TV上運行的,圖片顯示的問題不要糾結了。

文件下載的Demo已完成,沒時間上傳與講解,今天為您展示并講解一下,純原生的東西來下載文件,希望可以幫你理解更多安卓比較基礎的問題。
我們的思路:建立一個數(shù)據(jù)庫,兩個表,一個用來保存網絡數(shù)據(jù),一個保存本地下載的進度等等。在點擊下載按鈕的時候啟動DownloadService,進行比對之后下載
先看一下Demo的目錄結構:

所有的步驟在代碼里有非常詳細的講解,一定要看代碼(下面是抽取的幾個重要的類講解)!
數(shù)據(jù)庫的建立與DAO
/**
* Created by Administrator on 2017/3/6 0006.
*/
public class DownLoadDBHelper extends SQLiteOpenHelper {
/**
* DownLoadDBHelper用于創(chuàng)建數(shù)據(jù)庫,如果不會使用原生的建庫的話
*
* 跟隨小司機我的腳步來一起練一練
* 建兩個表:
* download_info表存儲下載信息
* localdownload_info表存儲本地下載信息
* 之后對比兩個表進行繼續(xù)下載等等
*/
public static String DATABASE_NAME = "downloadFILES.db";
public static String TABLE_DOWNLOAD_INFO = "download_info";
public static String TABLE_LOCALDOWNLOAD_INFO = "localdownload_info";
private static int version = 1;
public DownLoadDBHelper(Context context) {
super(context, DATABASE_NAME, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
/*在此進行創(chuàng)建數(shù)據(jù)庫和表格,來一起動手寫一遍,就是兩個sqlite語句*/
db.execSQL("create table " + TABLE_DOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," +
"thread_id integer," + "start_position integer," + "end_position integer," + " completed_size integer," + "url varchar(100))");
db.execSQL("create table " + TABLE_LOCALDOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," + "name varchar(50)," +
"url varchar(100)," + "completedSize integer," + "fileSize integer," + "status integer)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
/*數(shù)據(jù)庫更新升級,檢測到版本的變化,發(fā)現(xiàn)版本號不一樣,就會自動調用onUpgrade函數(shù)
* 新版本號和老版本號都會作為onUpgrade函數(shù)的參數(shù)傳進來,便于開發(fā)者知道數(shù)據(jù)庫應該從哪個版本升級到哪個版本。
* */
String sql = "drop table if exists " + TABLE_DOWNLOAD_INFO + "";
String sqlOne = "drop table if exists " + TABLE_LOCALDOWNLOAD_INFO + "";
db.execSQL(sql);
db.execSQL(sqlOne);
onCreate(db);//刪除數(shù)據(jù)庫,重新創(chuàng)建。這里只是簡單的,并沒有添加或者減少數(shù)據(jù)庫中的其他字段
}
}
DAO對數(shù)據(jù)庫進行增刪改查
/**
* Created by ShanCanCan on 2017/3/6 0006.
*/
public class Dao {
/*
* DAO層主要是做數(shù)據(jù)持久層的工作,負責與數(shù)據(jù)庫進行聯(lián)絡的一些任務都封裝在此。
* DAO層所定義的接口里的方法都大同小異,這是由我們在DAO層對數(shù)據(jù)庫訪問的操作來決定的,
* 對數(shù)據(jù)庫的操作,我們基本要用到的就是新增,更新,刪除,查詢等方法。
* 因而DAO層里面基本上都應該要涵蓋這些方法對應的操作。
* */
private static Dao dao;
private static DownLoadDBHelper dbHelper;
public static final byte[] Lock = new byte[0]; //新建兩個字節(jié)作為對象鎖
public static final byte[] file_Lock = new byte[0];
public Dao() {//空構造方法,
}
public static synchronized Dao getInstance(Context context) {//本demo用單例模式中的懶漢模式+線程不安全 線程安全的代價是效率變低
if (dao == null) {
dao = new Dao();
dbHelper = new DownLoadDBHelper(context);
}
return dao;
}
/* public static synchronized Dao getInstance(Context context) {//本demo用單例模式中的懶漢模式+線程安全 線程安全的代價是效率變低,99%情況下不需要同步
if (dao == null) { //你可以在這兩個方法中隨便選擇一個
dao = new Dao();
dbHelper = new DownLoadDBHelper(context);
}
return dao;
}*/
/*************************************** 下方Dao層中對數(shù)據(jù)庫的增、刪、改、查 *********************************************************/
/**
* 檢查本地下載記錄,是否下載過
*
* @param url
* @return
*/
public boolean isExist(String url) {
SQLiteDatabase database = dbHelper.getReadableDatabase(); //獲取本app所創(chuàng)建的數(shù)據(jù)庫
String sql = "select count(*) from " + TABLE_LOCALDOWNLOAD_INFO + " where url=?"; //查詢語句,查詢總共有多少條的語句
Cursor cursor = database.rawQuery(sql, new String[]{url});
/**
*
* @Cursor
* Cursor 是每行的集合。
* 使用 moveToFirst() 定位第一行。
* 你必須知道每一列的名稱。
* 你必須知道每一列的數(shù)據(jù)類型。
* Cursor 是一個隨機的數(shù)據(jù)源。
* 所有的數(shù)據(jù)都是通過下標取得。
* Cursor按照我的理解就是一個箭頭,指到哪一行就是那一行的集合
* 比較重要的方法有:close(),moveToFirst(),moveToNext(),moveToLast(),moveToPrevious(),getColumnCount()等。
*
* @rawQuery
* rawQuery是直接使用SQL語句進行查詢的,也就是第一個參數(shù)字符串,
* 在字符串內的“?”會被后面的String[]數(shù)組逐一對換掉
* cursor用完之后要關閉,cursor用完之后要關閉,cursor用完之后要關閉。重要的事情說三遍?。。?
*
* */
cursor.moveToFirst();
int count = cursor.getInt(0);
cursor.close();
return count > 0;
}
/**
* 是否為首次下載
*
* @param url
* @return
*/
public boolean isFirstDownload(String url) {
SQLiteDatabase database = dbHelper.getReadableDatabase();
String sql = "select count(*) from " + TABLE_DOWNLOAD_INFO + " where url=?";
Cursor cursor = database.rawQuery(sql, new String[]{url});
cursor.moveToFirst();
int count = cursor.getInt(0);
cursor.close();
return count == 0;
}
/**
* 保存下載的具體信息 保存所下載的list集合中的數(shù)據(jù)
*
* @param infos
* @param context
*/
public void saveInfos(List<DownLoadInfo> infos, Context context) {
/**
* 事務(Transaction)是并發(fā)控制的單位,是用戶定義的一個操作序列。
* 這些操作要么都做,要么都不做,是一個不可分割的工作單位。
* 通過事務,SQL Server能將邏輯相關的一組操作綁定在一起,
* 以便保持數(shù)據(jù)的完整性。
*
* 事務具有四個特征:原子性( Atomicity )、一致性( Consistency )、
* 隔離性( Isolation )和持續(xù)性( Durability )。這四個特性簡稱為 ACID 特性。
*
* */
synchronized (Lock) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();//開啟事務
try {//如果有異常,在這里捕獲
for (DownLoadInfo info : infos) {//for循環(huán)將數(shù)據(jù)存入數(shù)據(jù)庫
String sql = "insert into " + TABLE_DOWNLOAD_INFO + "(thread_id,start_position, end_position, completed_size, url) values (?,?,?,?,?)";
Object[] bindArgs = {info.getThreadId(), info.getStartPosition(), info.getEndPosition(), info.getCompletedSize(), info.getUrl()};
database.execSQL(sql, bindArgs);
}
database.setTransactionSuccessful();//結束事務
} catch (SQLException e) {
e.printStackTrace();
} finally {
database.endTransaction();//關閉事務
}
}
}
/**
* 得到下載具體信息
*
* @param urlstr
* @return List<DownloadInfo> 一個下載器信息集合器,里面存放了每條線程的下載信息
*/
public List<DownLoadInfo> getInfos(String urlstr) {
List<DownLoadInfo> list = new ArrayList<DownLoadInfo>();
SQLiteDatabase database = dbHelper.getReadableDatabase();
String sql = "select thread_id, start_position, end_position, completed_size, url from " + TABLE_DOWNLOAD_INFO + " where url=?";
Cursor cursor = database.rawQuery(sql, new String[]{urlstr});
while (cursor.moveToNext()) {//通過cursor取到下載器信息,循環(huán)遍歷,得到下載器集合
DownLoadInfo info = new DownLoadInfo(cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getInt(3), cursor.getString(4));
list.add(info);
}
cursor.close();
return list;
}
/**
* 本地下載列表添加記錄,添加本地數(shù)據(jù)庫信息,完成度等等
*
* @param fileStatus
**/
public void insertFileStatus(FileStatus fileStatus) {
synchronized (file_Lock) {//異步加開啟事務,保證數(shù)據(jù)的完整性
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try {
String sql = "insert into " + TABLE_LOCALDOWNLOAD_INFO + " (name,url,completedSize,fileSize,status) values(?,?,?,?,?)";
Object[] bindArgs = {fileStatus.getName(), fileStatus.getUrl(), fileStatus.getCompletedSize(), fileStatus.getFileSize(), fileStatus.getStatus()};
database.execSQL(sql, bindArgs);
database.setTransactionSuccessful();
} catch (SQLException e) {
e.printStackTrace();
} finally {
database.endTransaction();
}
}
}
/**
* @param context
* @param compeletedSize
* @param threadId
* @param urlstr 這里是更新數(shù)據(jù)庫,建議在保存一個表格的時候就對另一個表格數(shù)據(jù)庫進行更新
*/
public void updataInfos(int threadId, int compeletedSize, String urlstr, Context context) {
synchronized (Lock) {
String sql = "update " + TABLE_DOWNLOAD_INFO + "set completed_size = ? where thread_id =? and url=?";
String localSql = "update " + TABLE_LOCALDOWNLOAD_INFO + "set completedSize = (select sum(completed_size) from " +
TABLE_DOWNLOAD_INFO + "where url=? group by url ) where url=?";
Object[] bindArgs = {compeletedSize, threadId, urlstr};
Object[] localArgs = {urlstr, urlstr};
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try {
database.execSQL(sql, bindArgs);
database.execSQL(localSql, localArgs);
database.setTransactionSuccessful();
} catch (SQLException e) {
e.printStackTrace();
} finally {
database.endTransaction();
}
}
}
/**
* @param url 更新文件的狀態(tài),0為正在下載,1為已經下載完成,2為下載出錯
**/
public void updateFileStatus(String url) {
synchronized (file_Lock) {
String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set status = ? where url = ?";
Object[] bindArgs = {1, url};
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try {
database.execSQL(sql, bindArgs);
database.setTransactionSuccessful();
} catch (SQLException e) {
e.printStackTrace();
} finally {
database.endTransaction();
}
}
}
/**
* @return List<FileStatus>
* 取出本地下載列表數(shù)據(jù),如在重新進入應用時,要重新把進度之類的設置好
**/
public List<FileStatus> getFileStatus() {
List<FileStatus> list = new ArrayList<FileStatus>();
SQLiteDatabase database = dbHelper.getReadableDatabase();
//String sql = "slect * from " + TABLE_LOCALDOWNLOAD_INFO + ""; //不能用,需要哪些條件就在語句中寫出哪些條件
String sql = "select name, url, status, completedSize, fileSize from " + TABLE_LOCALDOWNLOAD_INFO + "";
Cursor cursor = database.rawQuery(sql, null);
while (cursor.moveToNext()) {
FileStatus fileState = new FileStatus(cursor.getString(0), cursor.getString(1), cursor.getInt(2), cursor.getInt(3), cursor.getInt(4));
list.add(fileState);
}
cursor.close();
return list;
}
/**
* @param url
* @param completeSize
* @param status 更新文件的下載狀態(tài)
**/
public void updateFileDownStatus(int completeSize, int status, String url) {
synchronized (file_Lock) {
String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set completedSize = ?,status = ? where url = ?";
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try {
Object[] bindArgs = {completeSize, status, url};
database.execSQL(sql, bindArgs);
database.delete(TABLE_DOWNLOAD_INFO, "url = ?", new String[]{url});
database.setTransactionSuccessful();
} catch (SQLException e) {
e.printStackTrace();
} finally {
database.endTransaction();
}
}
}
/**
* @param url 獲取文件名稱
**/
public String getFileName(String url) {
String result = "";
String sql = "select name from " + TABLE_LOCALDOWNLOAD_INFO + " where url = ?";
SQLiteDatabase database = dbHelper.getReadableDatabase();
Cursor cursor = database.rawQuery(sql, new String[]{url});
if (cursor.moveToNext()) {
result = cursor.getString(0);
}
cursor.close();
return result;
}
/**
* 刪除文件之后,要刪除下載的數(shù)據(jù),一個是用戶可以重新下載
* 另一個是表再次添加一條數(shù)據(jù)的時候不出現(xiàn)錯誤
*
* @param url
*/
public void deleteFile(String url) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try {
database.delete(TABLE_DOWNLOAD_INFO, " url = ?", new String[]{url});
database.delete(TABLE_LOCALDOWNLOAD_INFO, " url = ?", new String[]{url});
database.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
database.endTransaction();
}
}
/**
* 關閉數(shù)據(jù)庫
*
* @close
*/
public void closeDB() {
dbHelper.close();
}
}
DownloadService 主要代碼
@SuppressLint("HandlerLeak")
public class DownloadService extends Service
{
public IBinder binder = new MyBinder();
public class MyBinder extends Binder
{
public DownloadService getService()
{
return DownloadService.this;
}
}
@Override
public IBinder onBind(Intent intent)
{
return binder;
}
public static int number = 0;
// 文件保存地址
public final String savePath = "/mnt/sdcard/MultiFileDownload/";
// 存放下載列表的引用
public static List<FileStatus> list = new ArrayList<FileStatus>();
public static Map<String, String> localDownList = new HashMap<String, String>();
// 保存每個文件下載的下載器
public static Map<String, Downloader> downloaders = new HashMap<String, Downloader>();
// 每個下載文件完成的長度
private Map<String, Integer> completeSizes = new HashMap<String, Integer>();
// 每個下載文件的總長度
private Map<String, Integer> fileSizes = new HashMap<String, Integer>();
private Downloader downloader;
private int threadCount = 5;
private Dao dao;
private DownLoadCallback loadCallback;
private FileStatus mFileStatus = null;
private Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
super.handleMessage(msg);
if (msg.what == 1)
{
String url = (String) msg.obj;
int length = msg.arg1;
int completeSize = completeSizes.get(url);
int fileSize = fileSizes.get(url);
completeSize += length;
completeSizes.put(url, completeSize);
synchronized (list)
{
for (int i = 0; i < list.size(); i++)
{
FileStatus fileStatus = list.get(i);
if (fileStatus.getUrl().equals(url))
{
if (completeSize == fileStatus.getFileSize())
{
System.out.println("-----------下載完成:"+fileStatus.getName()+":"+completeSize+"-----"+fileStatus.getFileSize());
list.set(i, new FileStatus(fileStatus.getName(), fileStatus.getUrl(), 1, completeSize, fileStatus.getFileSize()));
dao.updateFileDownStatus(completeSize, 1, url);
}
else
{
list.set(i, new FileStatus(fileStatus.getName(), fileStatus.getUrl(), 0, completeSize, fileStatus.getFileSize()));
}
mFileStatus = list.get(i);
}
}
this.postDelayed(new Runnable()
{
@Override
public void run()
{
if (loadCallback != null && mFileStatus != null)
{
loadCallback.refreshUI(mFileStatus);
}
}
}, 1000);
}
}
}
};
@Override
public void onCreate()
{
super.onCreate();
dao = Dao.getInstance(this);
list = dao.getFileStatus();
for (FileStatus fileStatus : list)
{
localDownList.put(fileStatus.getUrl(), fileStatus.getUrl());
}
Timer timer = new Timer();
timer.schedule(new TimerTask()
{
@Override
public void run()
{
number++;
}
}, 0, 1000);
}
public void download(final Button button, final String url, final String name, final Handler mHandler)
{
if (dao.isExist(url))
{
Toast.makeText(this, "別點了,已經在下載了", Toast.LENGTH_SHORT).show();
return;
}
final String fileName = name + url.substring(url.lastIndexOf("."));
new Thread(new Runnable()
{
@Override
public void run()
{
downloader = downloaders.get(url);
if (downloader == null)
{
downloader = new Downloader(url, savePath, fileName, threadCount, DownloadService.this, handler);
downloaders.put(url, downloader);
}
if (downloader.isDownloading())
return;
LoadInfo loadInfo = downloader.getDownloaderInfors();
if(loadInfo != null)
{
FileStatus fileStatus = new FileStatus(fileName, url, 0, loadInfo.getComplete(), loadInfo.getFileSize());
dao.insertFileStatus(fileStatus);
completeSizes.put(url, loadInfo.getComplete());
fileSizes.put(url, fileStatus.getFileSize());
list.add(fileStatus);
localDownList.put(url, url);
downloader.download();
Message msg = new Message();
msg.what = 1;
msg.obj = button;
mHandler.sendMessage(msg);
}
else
{
Message msg = new Message();
msg.what = 2;
msg.obj = button;
mHandler.sendMessage(msg);
}
}
}).start();
}
//暫停下載
public void Pause(Downloader downloader)
{
downloader.pause();
}
//繼續(xù)下載
public void reDownload(final Button button, final String url, final String name, final Handler mHandler)
{
new Thread(new Runnable()
{
@Override
public void run()
{
String fileName = dao.getFileName(url);
downloader = downloaders.get(url);
if (downloader == null)
{
downloader = new Downloader(url, savePath, fileName, threadCount, DownloadService.this, handler);
downloaders.put(url, downloader);
}
if (downloader.isDownloading())
return;
LoadInfo loadInfo = downloader.getDownloaderInfors();
if(loadInfo != null && !fileName.equals(""))
{
if(!completeSizes.containsKey(url))
{
completeSizes.put(url, loadInfo.getComplete());
}
if(!fileSizes.containsKey(url))
{
fileSizes.put(url, loadInfo.getFileSize());
}
downloader.download();
Message msg = new Message();
msg.what = 1;
msg.obj = button;
mHandler.sendMessage(msg);
}
else
{
Message msg = new Message();
msg.what = 2;
msg.obj = button;
mHandler.sendMessage(msg);
}
}
}).start();
}
public void delete(final String url)
{
Downloader down = downloaders.get(url);
if(down != null)
{
down.pause();
}
handler.postDelayed(new Runnable()
{
@Override
public void run()
{
dao.deleteFile(url);
for (int i = 0; i < list.size(); i++)
{
FileStatus fileStatus = list.get(i);
if (fileStatus.getUrl().equals(url))
{
list.remove(i);
}
}
localDownList.remove(url);
downloaders.remove(url);
completeSizes.remove(url);
fileSizes.remove(url);
if(loadCallback != null)
{
loadCallback.deleteFile(url);
}
}
}, 1000);
}
public interface DownLoadCallback
{
public void refreshUI(FileStatus fileStatus);
public void deleteFile(String url);
}
public void setLoadCallback(DownLoadCallback loadCallback)
{
this.loadCallback = loadCallback;
}
}
下載工具類DownLoadUtil
/**
* Created by ShanCanCan on 2017/3/7 0007.
*/
public class DownLoadUtil {
/**
* 此類的主要功能
* 1、檢查是否下載
* 2、下載文件,文件的下載采用httpurlconnection
*/
private String downPath;// 下載路徑
private String savePath;// 保存路徑
private String fileName;// 文件名稱
private int threadCount;// 線程數(shù)
private Handler mHandler;
private Dao dao;
private Context context;
private int fileSize;// 文件大小
private int range;
private List<DownLoadInfo> infos;// 存放下載信息類的集合
private int state = INIT;
private static final int INIT = 1;// 定義三種下載的狀態(tài):初始化狀態(tài),正在下載狀態(tài),暫停狀態(tài)
private static final int DOWNLOADING = 2;
private static final int PAUSE = 3;
/**
* 構造方法,獲取dao的對象
*
* @param downPath
* @param savePath
* @param fileName
* @param threadCount
* @param context
* @param mHandler
*/
public DownLoadUtil(String downPath, String savePath, String fileName, int threadCount, Handler mHandler, Context context) {
this.downPath = downPath;
this.savePath = savePath;
this.fileName = fileName;
this.threadCount = threadCount;
this.mHandler = mHandler;
this.context = context;
dao = Dao.getInstance(context);
}
/**
* 判斷是否PAUSE
**/
public boolean isPause() {
return state == PAUSE;
}
/**
* 判斷是否DOWNLOADING
*/
public boolean isDownloading() {
return state == DOWNLOADING;
}
/**
* @param url 判斷是否是第一次下載,利用dao查詢數(shù)據(jù)庫中是否有下載這個地址的記錄
*/
private boolean isFirst(String url) {
return dao.isFirstDownload(url);
}
/**
* 獲取要下載的東西
*/
public LoadItemInfo getDownloadInfos() {
if (isFirst(downPath)) {
if (initFirst()) {//如果是第一次下載的話,要進行初始化,1.獲得下載文件的長度 2.創(chuàng)建文件,設置文件的大小
range = this.fileSize / this.threadCount;
infos = new ArrayList<DownLoadInfo>();
//這里就是啟動多線程下載,看出來了嗎?配合RandomAccessFile。每一個DownLoadInfo就是RandomAccessFile文件的一部分
for (int i = 0; i < this.threadCount - 1; i++) {
DownLoadInfo info = new DownLoadInfo(i, i * range, (i + 1) * range - 1, 0, downPath);
infos.add(info);
}
DownLoadInfo info = new DownLoadInfo(this.threadCount - 1, (this.threadCount - 1) * range, this.fileSize, 0, downPath);
infos.add(info);
dao.saveInfos(infos, this.context);
//(String urlDownload, int completePercent, int fileSize)
LoadItemInfo loadInfo = new LoadItemInfo(this.downPath, 0, this.fileSize);
return loadInfo;
} else {
return null;
}
} else {
//不是第一次下載,我們應該怎么做呢?從數(shù)據(jù)庫里面取回來
infos = dao.getInfos(this.downPath);
if (infos != null && infos.size() > 0) {
int size = 0;
int completeSize = 0;
for (DownLoadInfo info : infos) {
completeSize += info.getCompletedSize();
size += info.getEndPosition() - info.getStartPosition() + this.threadCount - 1;
}
LoadItemInfo loadInfo = new LoadItemInfo(this.downPath, completeSize, size);
return loadInfo;
} else {
return null;
}
}
}
// 設置暫停
public void pause() {
state = PAUSE;
}
// 重置下載狀態(tài),將下載狀態(tài)設置為init初始化狀態(tài)
public void reset() {
state = INIT;
}
/**
* 基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream結合起來,再加上它自己的一些方法,
* 比如定位用的getFilePointer( ),在文件里移動用的seek( ),以及判斷文件大小的length( )、skipBytes()跳過多少字節(jié)數(shù)。
* 此外,它的構造函數(shù)還要一個表示以只讀方式("r"),還是以讀寫方式("rw")打開文件的參數(shù) (和C的fopen( )一模一樣)。它不支持只寫文件。
*/
private boolean initFirst() {
boolean result = true;
HttpURLConnection conn = null;
RandomAccessFile randomFile = null;
URL url = null;
try {
url = new URL(downPath);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
// 如果http返回的代碼是200或者206則為連接成功
if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) //狀態(tài)碼(206),表示服務器已經執(zhí)行完部分對資源的GET請求
{
fileSize = conn.getContentLength();// 得到文件的大小
if (fileSize <= 0) {
//("網絡故障,無法獲取文件大小");
return false;
}
File dir = new File(savePath);
// 如果文件目錄不存在,則創(chuàng)建
if (!dir.exists()) {
if (dir.mkdirs()) {
//("mkdirs success.");
}
}
File file = new File(this.savePath, this.fileName);
randomFile = new RandomAccessFile(file, "rwd");
randomFile.setLength(fileSize);// 設置保存文件的大小
randomFile.close();
conn.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
result = false;
} finally {
if (randomFile != null) {
try {
randomFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (conn != null) {
conn.disconnect();
}
}
return result;
}
/**
* 下面的這個方法就是開啟多線程進行下載了數(shù)據(jù)了
*/
public void downLoad() {
if (infos != null) {
if (state == DOWNLOADING) {
return;
}
state = DOWNLOADING;// 把狀態(tài)設置為正在下載
for (DownLoadInfo info : infos) {//為什么說我們是多線程呢?因為我們分別用新線程去下載剛才分割好的一個RandomAccessFile文件
new DownLoadThread(info.getThreadId(), info.getStartPosition(), info.getEndPosition(), info.getCompletedSize(), info.getUrl(), this.context).start();
}
}
}
/**
* 現(xiàn)在要創(chuàng)建線程用來下載了,這里采用內部類
*/
public class DownLoadThread extends Thread {
private int threadId;
private int startPostion;
private int endPostion;
private int compeletedSize;
private String url;
private Context context;
public static final int PROGRESS = 1;
public DownLoadThread(int threadId, int startPostion, int endPostion, int compeletedSize, String url, Context context) {//構造方法,傳入特定的參數(shù)
this.threadId = threadId;
this.startPostion = startPostion;
this.endPostion = endPostion;
this.compeletedSize = compeletedSize;
this.url = url;
this.context = context;
}
//開始下載
@Override
public void run() {
HttpURLConnection conn = null;
RandomAccessFile randomAccessFile = null;
InputStream inStream = null;
File file = new File(savePath, fileName);
URL url = null;
try {
url = new URL(this.url);
conn = (HttpURLConnection) url.openConnection();
constructConnection(conn);
if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) {
randomAccessFile = new RandomAccessFile(file, "rwd");
randomAccessFile.seek(this.startPostion + this.compeletedSize);//RandomAccessFile移動指針,到需要下載的塊
inStream = conn.getInputStream();
byte buffer[] = new byte[4096];//這個4096為么子呢?我也不知道,就是看阿里的人下載apk的時候都用4096,我也用
int length = 0;
while ((length = inStream.read(buffer, 0, buffer.length)) != -1) {
randomAccessFile.write(buffer, 0, length);
compeletedSize += length;
// 更新數(shù)據(jù)庫中的下載信息
dao.updataInfos(threadId, compeletedSize, this.url, this.context);
// 用消息將下載信息傳給進度條,對進度條進行更新
Message message = Message.obtain();
message.what = PROGRESS;
message.obj = this.url;
message.arg1 = length;
mHandler.sendMessage(message);// 給DownloadService發(fā)送消息
if (state == PAUSE) {
//("-----pause-----");
return;
}
}
// ("------------線程:" + this.threadId + "下載完成");
}
} catch (IOException e) {
e.printStackTrace();
//("-----下載異常-----"); 這里下載異常我就不處理了,你可以發(fā)一條重新下載的消息
} finally {//用完只后流要關閉,不然容易造成資源搶占,內存泄漏
try {
if (inStream != null) {
inStream.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
if (conn != null) {
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 構建請求連接時的參數(shù) 返回開始下載的位置
*
* @param conn
*/
private void constructConnection(HttpURLConnection conn) throws IOException {
conn.setConnectTimeout(5 * 1000);// 設置連接超時5秒
conn.setRequestMethod("GET");// GET方式提交,如果你是用post請求必須添加 conn.setDoOutput(true); conn.setDoInput(true);
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", this.url);
conn.setRequestProperty("Charset", "UTF-8");
int startPositionNew = this.startPostion + this.compeletedSize;
// 設置獲取實體數(shù)據(jù)的范圍
conn.setRequestProperty("Range", "bytes=" + startPositionNew + "-" + this.endPostion);
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();
}
}
}
Github地址:https://github.com/Shanlovana/DownLoadFiles/
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
微信瀏覽器彈出框滑動時頁面跟著滑動的實現(xiàn)代碼(兼容Android和IOS端)
小編在做微信開發(fā)的時候遇到微信瀏覽器彈出框滑動時頁面跟著滑動的效果,下面把關鍵代碼分享給大家,需要的朋友參考下2016-11-11
Android使用setContentView實現(xiàn)頁面的轉換效果
這篇文章主要介紹了Android如何使用setContentView實現(xiàn)頁面的轉換效果,幫助大家更好的利用Android進行開發(fā),感興趣的朋友可以了解下2021-01-01
Android自定義viewgroup 使用adapter適配數(shù)據(jù)(6)
這篇文章主要為大家詳細介紹了Android自定義viewgroup,使用adapter適配數(shù)據(jù),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12
Android編程實現(xiàn)仿美團或淘寶的多級分類菜單效果示例【附demo源碼下載】
這篇文章主要介紹了Android編程實現(xiàn)仿美團或淘寶的多級分類菜單效果,結合實例形式分析了Android多級菜單的實現(xiàn)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2017-01-01

