一:關於斷點下載所涉及到的知識點
1.對SQLite的增刪改查(主要用來保存當前任務的一些信息)
2.HttpURLConnection的請求配置
HttpURLConnection connection = null;
//設置下載請求屬性
connection.setRequestProperty();
3.RandomAccessFile 對文件進行寫入
RandomAccessFile rwd = null;
//從文件的某一位置寫入
rwd.seek();
4.基本的I/O流操作,以及邏輯處理
二:第一步我們先來創建一張表用來保存我們的下載信息
public class DbHelper extends SQLiteOpenHelper {
public static String TABLE = "file";//表名
public DbHelper(Context context) {
super(context, "download.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
//文件名,下載地址,下載文件的總長度,當前下載完成長度
db.execSQL("create table file(fileName varchar,url varchar,length integer,finished integer)");
}
}
三:第二步同時既然是對數據庫的操作,那我們在DbHelper.class
中來寫好幾個公用方法
/**
* 插入一條下載信息
*/
public void insertData(SQLiteDatabase db, FileInfo info) {
ContentValues values = new ContentValues();
values.put("fileName", info.getFileName());
values.put("url", info.getUrl());
values.put("length", info.getLength());
values.put("finished", info.getFinished());
db.insert(TABLE, null, values);
}
/**
* 是否已經插入這條數據
*/
public boolean isExist(SQLiteDatabase db, FileInfo info) {
Cursor cursor = db.query(TABLE, null, "url = ?", new String[]{info.getUrl()}, null, null, null, null);
boolean exist = cursor.moveToNext();
cursor.close();
return exist;
}
/**
* 查詢已經存在的一條信息
*/
public FileInfo queryData(SQLiteDatabase db, String url) {
Cursor cursor = db.query(TABLE, null, "url = ?", new String[]{url}, null, null, null, null);
FileInfo info = new FileInfo();
if (cursor != null) {
while (cursor.moveToNext()) {
String fileName = cursor.getString(cursor.getColumnIndex("fileName"));
int length = cursor.getInt(cursor.getColumnIndex("length"));
int finished = cursor.getInt(cursor.getColumnIndex("finished"));
info.setStop(false);
info.setFileName(fileName);
info.setUrl(url);
info.setLength(length);
info.setFinished(finished);
}
cursor.close();
}
return info;
}
/**
* 恢復一條下載信息
*/
public void resetData(SQLiteDatabase db, String url) {
ContentValues values = new ContentValues();
values.put("finished", 0);
values.put("length", 0);
db.update(TABLE, values, "url = ?", new String[]{url});
}
3.從上面方法中可以看出來還有一個FileInfo對象,沒錯這是自己創建的一個下載任務實體類一起來看看吧
//保存下載任務信息
public class FileInfo {
private String fileName;//文件名
private String url;//下載地址
private int length;//文件大小
private int finished;//下載以已完成進度
private boolean isStop = false;//是否暫停下載
private boolean isDownLoading = false;//是否正在下載
//......
//剩下的都是對應的get and set 方法就不貼出來了,自動生成就好了
四:第三步我們創建一個類DownLoaderManger
來管理我們的下載任務包括、添加下載任務、開始下載、暫停下載、重新下載
public class DownLoaderManger {
public static String FILE_PATH = Environment.getExternalStorageDirectory() + "/azhong";//文件下載保存路徑
private DbHelper helper;//數據庫幫助類
private SQLiteDatabase db;
private OnProgressListener listener;//進度回調監聽
private Map<String, FileInfo> map = new HashMap<>();//保存正在下載的任務信息
private static DownLoaderManger manger;
private DownLoaderManger(DbHelper helper, OnProgressListener listener) {
this.helper = helper;
this.listener = listener;
db = helper.getReadableDatabase();
}
/**
* 單例模式
*
* @param helper 數據庫幫助類
* @param listener 下載進度回調接口
* @return
*/
public static synchronized DownLoaderManger getInstance(DbHelper helper, OnProgressListener listener) {
if (manger == null) {
synchronized (DownLoaderManger.class) {
if (manger == null) {
manger = new DownLoaderManger(helper, listener);
}
}
}
return manger;
}
/**
* 開始下載任務
*/
public void start(String url) {
db = helper.getReadableDatabase();
FileInfo info = helper.queryData(db, url);
map.put(url, info);
//開始任務下載
new DownLoadTask(map.get(url), helper, listener).start();
}
/**
* 停止下載任務
*/
public void stop(String url) {
map.get(url).setStop(true);
}
/**
* 重新下載任務
*/
public void restart(String url) {
stop(url);
try {
File file = new File(FILE_PATH, map.get(url).getFileName());
if (file.exists()) {
file.delete();
}
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
db = helper.getWritableDatabase();
helper.resetData(db, url);
start(url);
}
/**
* 獲取當前任務狀態
*/
public boolean getCurrentState(String url) {
return map.get(url).isDownLoading();
}
/**
* 添加下載任務
*
* @param info 下載文件信息
*/
public void addTask(FileInfo info) {
//判斷數據庫是否已經存在這條下載信息
if (!helper.isExist(db, info)) {
db = helper.getWritableDatabase();
helper.insertData(db, info);
map.put(info.getUrl(), info);
} else {
//從數據庫獲取最新的下載信息
db = helper.getReadableDatabase();
FileInfo o = helper.queryData(db, info.getUrl());
if (!map.containsKey(info.getUrl())) {
map.put(info.getUrl(), o);
}
}
}
}
五:上面代碼中OnProgressListener
接口,當然還有一個最最重要的DownLoadTask
了這裏面就是實現瞭如何斷點下載的,下面來一起看下里面的實現邏輯吧。。。
//下載進度接口
public interface OnProgressListener {
void updateProgress(int max, int progress);
}
六:重點–下載線程
/**
* 下載文件線程
* 從服務器獲取需要下載的文件大小
*/
public class DownLoadTask extends Thread {
private FileInfo info;
private SQLiteDatabase db;
private DbHelper helper;//數據庫幫助類
private int finished = 0;//當前已下載完成的進度
private OnProgressListener listener;//進度回調監聽
public DownLoadTask(FileInfo info, DbHelper helper, OnProgressListener listener) {
this.info = info;
this.helper = helper;
this.db = helper.getReadableDatabase();
this.listener = listener;
info.setDownLoading(true);
}
@Override
public void run() {
getLength();
HttpURLConnection connection = null;
RandomAccessFile rwd = null;
try {
URL url = new URL(info.getUrl());
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(3000);
//從上次下載完成的地方下載
int start = info.getFinished();
//設置下載位置(從服務器上取要下載文件的某一段)
connection.setRequestProperty("Range", "bytes=" + start + "-" + info.getLength());//設置下載範圍
//設置文件寫入位置
File file = new File(DownLoaderManger.FILE_PATH, info.getFileName());
rwd = new RandomAccessFile(file, "rwd");
//從文件的某一位置開始寫入
rwd.seek(start);
finished += info.getFinished();
if (connection.getResponseCode() == 206) {//文件部分下載,返回碼爲206
InputStream is = connection.getInputStream();
byte[] buffer = new byte[1024 * 4];
int len;
while ((len = is.read(buffer)) != -1) {
//寫入文件
rwd.write(buffer, 0, len);
finished += len;
info.setFinished(finished);
//更新界面顯示
Message msg = new Message();
msg.what = 0x123;
msg.arg1 = info.getLength();
msg.arg2 = info.getFinished();
handler.sendMessage(msg);
//停止下載
if (info.isStop()) {
info.setDownLoading(false);
//保存此次下載的進度
helper.updateData(db, info);
db.close();
return;
}
}
//下載完成
info.setDownLoading(false);
helper.updateData(db, info);
db.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
try {
if (rwd != null) {
rwd.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 首先開啓一個線程去獲取要下載文件的大小(長度)
*/
private void getLength() {
HttpURLConnection connection = null;
try {
//連接網絡
URL url = new URL(info.getUrl());
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(3000);
int length = -1;
if (connection.getResponseCode() == 200) {//網絡連接成功
//獲得文件長度
length = connection.getContentLength();
}
if (length <= 0) {
//連接失敗
return;
}
//創建文件保存路徑
File dir = new File(DownLoaderManger.FILE_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
info.setLength(length);
} catch (Exception e) {
e.printStackTrace();
} finally {
//釋放資源
try {
if (connection != null) {
connection.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 更新進度
*/
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0x123:
if (listener != null) {
listener.updateProgress(msg.arg1, msg.arg2);
}
break;
}
}
};
}
七:下載流程—>首先獲取要下載文件的總長度—>然後指定從上次結束的位置開始下載文件。客官閱讀需仔細哦,精華都在註釋裏面哦!個人認爲重點部分如下兩個:
//設置下載位置(從服務器上取要下載文件的某一段)
connection.setRequestProperty("Range", "bytes=" + start + "-" + info.getLength());//設置下載範圍
RandomAccessFile rwd = new RandomAccessFile(file, "rwd");
//從文件的某一位置開始寫入
rwd.seek(start);
八:上面做了一系列準備工作之後,就可以正式開始下載了讓我們一起來瞧瞧
1.給主佈局界面放兩個按鈕,來開始/暫停/重新下載
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.azhong.downloader.MainActivity">
<com.azhong.downloader.view.NumberProgressBar
android:id="@+id/pb"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="開始下載" />
<Button
android:id="@+id/restart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="重新下載" />
</LinearLayout>
2.關於NumberProgressBar的使用可以移駕至這裏
3.記得在清單文件中加入 網絡訪問和內存讀寫權限哦!
4.既然我們封裝了那麼久,那肯定用起來就會很簡單了
public class MainActivity extends AppCompatActivity implements OnProgressListener {
private NumberProgressBar pb;//進度條
private DownLoaderManger downLoader = null;
private FileInfo info;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (NumberProgressBar) findViewById(R.id.pb);
final Button start = (Button) findViewById(R.id.start);//開始下載
final Button restart = (Button) findViewById(R.id.restart);//重新下載
final DbHelper helper = new DbHelper(this);
downLoader = DownLoaderManger.getInstance(helper, this);
info = new FileInfo("Kuaiya482.apk", "http://downloadz.dewmobile.net/Official/Kuaiya482.apk");
downLoader.addTask(info);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (downLoader.getCurrentState(info.getUrl())) {
downLoader.stop(info.getUrl());
start.setText("開始下載");
} else {
downLoader.start(info.getUrl());
start.setText("暫停下載");
}
}
});
restart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downLoader.restart(info.getUrl());
start.setText("暫停下載");
}
});
}
@Override
public void updateProgress(final int max, final int progress) {
pb.setMax(max);
pb.setProgress(progress);
}
}