實踐是檢驗真理的唯一標(biāo)準(zhǔn)捡多,對于編程來說蓖康,理解了不一定會做铐炫,所以還是敲一遍讓身體也記住它吧。
現(xiàn)在讓我們來做一款簡單的單線程下載器吧钓瞭。
本文的DEMO示例github下載地址:
https://github.com/liaozhoubei/Download
本文還有后續(xù)多線程下載器:http://www.reibang.com/p/c23b0c10c919
下載原理
對于Android來說驳遵,其下載器的原理非常簡單,僅僅是I/O流的實現(xiàn)而已山涡,只要了解I/O流就能夠?qū)懙贸龅探幔旅孢@個是一個簡單java項目的下載代碼:
try {
// strUrl 下載的網(wǎng)絡(luò)地址
URL url = new URL(strUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10 * 1000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if (code == 200) {
System.out.println("下載開始");
File file = new File("e:\\" + strUrl.substring((strUrl.lastIndexOf("/") + 1)));
long length = conn.getContentLength();
if (length > 1024) {
long size = length / (1024 * 1024);
System.out.println("下載大小" + size + "mb");
}
InputStream is = conn.getInputStream();
byte[] bt = new byte[1024];
int len = 0;
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.setLength(length);
while ((len = is.read(bt)) != -1) {
raf.write(bt, 0, len);
}
System.out.println("下載完成");
is.close();
raf.close();
}
} catch (Exception e) {
e.printStackTrace();
}
代碼很簡單,用支持http協(xié)議的網(wǎng)絡(luò)地址進行下載鸭丛,然后使用I/O流下載竞穷,或許大家不熟悉的只有RandomAccessFile這個API了,這是一個支持任意位置下載的一個API鳞溉,同時它有個setLength()方法瘾带,可以直接設(shè)置RandomAccessFile文件,的長度熟菲。還有個seek()方法看政,可以直接設(shè)定從文件的哪個位置開始寫入文件。
RandomAccessFile是個很重要的API抄罕,對于斷點下載而言允蚣。
直接下載可以了,那么如何斷點下載呢呆贿?
所謂斷點下載嚷兔,就是在停止下載文件的時候記住停止時的下載位置,等下次繼續(xù)下載的時候從這個位置繼續(xù)下載做入。
這個時候我們只需設(shè)置一個停止位置冒晰,然后用RandomAccessFile的seek()方法讀取這個位置就可以了。
所以這時我們要分兩步走
1竟块、初始化下載線程壶运,獲取文件的信息,如文件的大小等
2浪秘、開始下載文件前弯,如果文件信息已存在,則查詢先前下載到哪一個位置秫逝。
代碼斷點續(xù)傳代碼如下:
private static void mutilDownload(String path) {
HttpURLConnection conn = null;
try {
URL url = new URL(path);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10 * 1000);
conn.setRequestMethod("GET");
conn.setReadTimeout(5 * 1000);
int code = conn.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) {
File file = new File("e:\\" + path.substring(path.lastIndexOf("/") + 1));
long filelength = conn.getContentLength();
RandomAccessFile randomFile = new RandomAccessFile(file, "rwd");
randomFile.setLength(filelength);
randomFile.close();
long endposition = filelength;
new newThreadDown(path, endposition).start();
}
} catch (Exception e) {
} finally {
conn.disconnect();
}
}
public static class newThreadDown extends Thread {
private String urlstr;
private long lastPostion;
private long endposition;
public newThreadDown(String urlstr, long endposition) {
this.urlstr = urlstr;
this.endposition = endposition;
}
@Override
public void run() {
HttpURLConnection conn = null;
try {
URL url = new URL(urlstr);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10 * 1000);
conn.setRequestMethod("GET");
conn.setReadTimeout(10 * 1000);
long startposition = 0;
// 創(chuàng)建記錄緩存文件
File tempfile = new File("e:\\" + 1 + ".txt");
if (tempfile.exists()) {
InputStreamReader isr = new InputStreamReader(new FileInputStream(tempfile));
BufferedReader br = new BufferedReader(isr);
String lastStr = br.readLine();
lastPostion = Integer.parseInt(lastStr);
conn.setRequestProperty("Range", "bytes=" + lastPostion + "-" + endposition);
br.close();
} else {
lastPostion = startposition;
conn.setRequestProperty("Range", "bytes=" + lastPostion + "-" + endposition);
}
if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
System.out.println(206 + "請求成功");
InputStream is = conn.getInputStream();
RandomAccessFile accessFile = new RandomAccessFile(new File("e:\\" + path.substring(path.lastIndexOf("/") + 1)),
"rwd");
accessFile.seek(lastPostion);
System.out.println("開始位置" + lastPostion);
byte[] bt = new byte[1024 * 200];
int len = 0;
long total = 0;
while ((len = is.read(bt)) != -1) {
total += len;
accessFile.write(bt, 0, len);
long currentposition = startposition + total;
File cachefile = new File("e:\\" + 1 + ".txt");
RandomAccessFile rf = new RandomAccessFile(cachefile, "rwd");
rf.write(String.valueOf(currentposition).getBytes());
rf.close();
}
System.out.println("下載完畢");
is.close();
accessFile.close();
}
} catch (Exception e) {
e.printStackTrace();
}
super.run();
}
}
這些都是java項目,可以在eclipse中直接運行測試询枚。
下載的原理已經(jīng)梳理清楚了违帆,剩下的只要把下載程序移植到Android項目中去就好了。
Android下載器實現(xiàn)
作為一個實戰(zhàn)項目金蜀,我們要盡可能的完善刷后,盡可能的使用Android中的控件的畴,所以我們不做自己將上面的代碼復(fù)制到項目中,然后用ProgressBar更新UI的事情尝胆,我們要盡可能的復(fù)制丧裁!
我們的口號是:不做簡單活!
知識要點
- Android四大組件之Service
- Android四大組件之Broadcast
- 數(shù)據(jù)存儲SQLiteDatabase
現(xiàn)在讓我們開始完善這個單線程的下載器吧
下載器的布局
做一個簡單的界面含衔,我們用到開始下載按鍵煎娇,停止下載按鍵,一個Progressbar贪染,以及一個TextView顯示文件名缓呛。
如此簡單的布局就不寫代碼了,詳情可以下載我的Github項目研究杭隙。
封裝實體對象
在本項目中有個兩個實體類對象哟绊,即FileInfo類和ThreadInfo類,他們之中的變量都擁有g(shù)et和set方法痰憎,F(xiàn)ileInfo類需要實現(xiàn)序列化票髓,詳細代碼請查看項目地址
FileInfo類代碼(略):
public class FileInfo implements Serializable {
private int id;
private String url;
private String fileName;
private int length;
private int finished;
public FileInfo() {
super();
}
/**
*
* @param id 文件的ID
* @param url 文件的下載地址
* @param fileName 文件的名字
* @param length 文件的總大小
* @param finished 文件已經(jīng)完成了多少
*/
public FileInfo(int id, String url, String fileName, int length, int finished) {
super();
this.id = id;
this.url = url;
this.fileName = fileName;
this.length = length;
this.finished = finished;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
ThreadInfo類代碼(略):
public class ThreadInfo {
private int id;
private String url;
private int start;
private int end;
private int finished;
public ThreadInfo() {
super();
}
/**
* @param id 綫程的ID
* @param url 下載文件的網(wǎng)絡(luò)地址
* @param start 綫程下載的開始位置
* @param end 綫程下載的結(jié)束位置
* @param finished 綫程已經(jīng)下載到哪個位置
*/
public ThreadInfo(int id, String url, int start, int end, int finished) {
super();
this.id = id;
this.url = url;
this.start = start;
this.end = end;
this.finished = finished;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
創(chuàng)建數(shù)據(jù)庫
使用SQLiteDatabase,我們首先要實現(xiàn)一個數(shù)據(jù)庫的幫助類铣耘,然后創(chuàng)建一個操作數(shù)據(jù)庫的接口類洽沟,最后實現(xiàn)這個接口的數(shù)據(jù)庫操作類。
使用數(shù)據(jù)庫是用于保存ThreadInfo對象的信息涡拘,并且實時更新下載進度玲躯,但需要斷點續(xù)傳的時候從數(shù)據(jù)庫中取出保存的信息,繼續(xù)下載鳄乏。
這里提示一下跷车,保存斷點信息可以不使用數(shù)據(jù)庫,試用SharedPreference也是可以起到同樣的作用橱野,具體方法請讀著自己摸索朽缴。
數(shù)據(jù)庫幫助類代碼如下:
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "download.db";
private static final int VERSION = 1;
private static final String SQL_CREATE = "create table thread_info(_id integer primary key autoincrement, "
+ "thread_id integer, url text, start integer, end integer, finished integer)";
private static final String SQL_DROP = "drop table if exists thread_info";
public DBHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DROP);
db.execSQL(SQL_CREATE);
}
}
操作數(shù)據(jù)庫的接口類代碼:
public interface ThreadDAO {
// 插入綫程
public void insertThread(ThreadInfo info);
// 刪除綫程
public void deleteThread(String url, int thread_id);
// 更新綫程
public void updateThread(String url, int thread_id, int finished);
// 查詢綫程
public List<ThreadInfo> queryThreads(String url);
// 判斷綫程是否存在
public boolean isExists(String url, int threadId);
}
實現(xiàn)接口的數(shù)據(jù)庫工具類:
public class ThreadDAOImple implements ThreadDAO {
private DBHelper dbHelper = null;
public ThreadDAOImple(Context context) {
super();
this.dbHelper = new DBHelper(context);
}
// 插入綫程
@Override
public void insertThread(ThreadInfo info) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
ContentValues values = new ContentValues();
values.put("thread_id", info.getId());
values.put("url", info.getUrl());
values.put("start", info.getStart());
values.put("end", info.getEnd());
values.put("finished", info.getFinished());
db.insert("thread_info", null, values);
db.close();
}
// 刪除綫程
@Override
public void deleteThread(String url, int thread_id) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
db.delete("thread_info", "url = ? and thread_id = ?", new String[] { url, thread_id + "" });
db.close();
}
// 更新綫程
@Override
public void updateThread(String url, int thread_id, int finished) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
db.execSQL("update thread_info set finished = ? where url = ? and thread_id = ?",
new Object[]{finished, url, thread_id});
db.close();
}
// 查詢綫程
@Override
public List<ThreadInfo> queryThreads(String url) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
List<ThreadInfo> list = new ArrayList<ThreadInfo>();
Cursor cursor = db.query("thread_info", null, "url = ?", new String[] { url }, null, null, null);
while (cursor.moveToNext()) {
ThreadInfo thread = new ThreadInfo();
thread.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
thread.setUrl(cursor.getString(cursor.getColumnIndex("url")));
thread.setStart(cursor.getInt(cursor.getColumnIndex("start")));
thread.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
thread.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));
list.add(thread);
}
cursor.close();
db.close();
return list;
}
// 判斷綫程是否爲(wèi)空
@Override
public boolean isExists(String url, int thread_id) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query("thread_info", null, "url = ? and thread_id = ?", new String[] { url, thread_id + "" },
null, null, null);
boolean exists = cursor.moveToNext();
db.close();
db.close();
return exists;
}
}
從Activity中向Service傳參
很好,前期的準(zhǔn)備工作已經(jīng)做好了水援,需要從Activity中啟動線程密强,并且將Activity中獲得的關(guān)于下載文件的信息傳遞到Service中去,我們只需要用Intent便可以將FileInfo對象傳遞過去蜗元。在這里要注意的是如果FileInfo沒有序列化或渤,繼承Serializable接口,那么Intent無法將FileInfo對象傳送出去奕扣。
首先創(chuàng)建一個DownloadService服務(wù)類薪鹦,繼承自Service,定義ACITON_START和ACTION_STOP兩個常量,重新onStartCommand方法池磁,代碼如下:
public class DownloadService extends Service {
public static final String ACTION_START = "ACTION_START";
public static final String ACTION_STOP = "ACTION_STOP";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 獲得Activity穿來的參數(shù)
if (ACTION_START.equals(intent.getAction())) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
Log.i("test", "START" + fileInfo.toString());
} else if (ACTION_STOP.equals(intent.getAction())) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
}
return super.onStartCommand(intent, flags, startId);
}
}
然后修改MainActivity中的代碼:
定義Intent常量
定義FileInfo常量
在onCreate方法中初始化兩個常量:
fileInfo = new FileInfo(0, urlstr, getfileName(urlstr), 0, 0);
intent = new Intent(MainActivity.this, DownloadService.class);
設(shè)置點擊事件:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_button:
// 開啟服務(wù)
fileName.setText(getfileName(urlstr));
intent.setAction(DownloadService.ACTION_START);
intent.putExtra("fileInfo", fileInfo);
startService(intent);
break;
case R.id.stop_button:
intent.setAction(DownloadService.ACTION_STOP);
intent.putExtra("fileInfo", fileInfo);
startService(intent);
break;
}
}
相親我們已經(jīng)可以啟動服務(wù)了奔害,點擊按鍵啟動服務(wù)之后,就能調(diào)用DownloadService中的onStartCommand方法地熄,接收到從MainActivity傳過來的fileInfo對象华临。
從DownloadService中初始化線程
在剛才從MainActivity傳過來的fileInfo對象中只有下載的URL地址以及文件名,但是我們還不知道文件的長度端考,也沒有設(shè)定好文件的保存位置等信息雅潭,初始化線程就是為了配置好這些信息。
從初始化線程中配置好fileInfo對象之后跛梗,需要將它傳遞給Handler寻馏,然后在Handler啟動真正的下載任務(wù),
Handler代碼如下:
// 從InitThread綫程中獲取FileInfo信息核偿,然後開始下載任務(wù)
Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_INIT:
FileInfo fileInfo = (FileInfo) msg.obj;
Log.i("test", "INIT:" + fileInfo.toString());
// 獲取FileInfo對象诚欠,開始下載任務(wù)
mTask = new DownloadTask(DownloadService.this, fileInfo);
mTask.download();
break;
}
};
};
InitThread內(nèi)部類在完成初始化線程之后,將fileInfo傳遞給Handler漾岳,代碼如下:
// 初始化下載綫程轰绵,獲得下載文件的信息
class InitThread extends Thread {
private FileInfo mFileInfo = null;
public InitThread(FileInfo mFileInfo) {
super();
this.mFileInfo = mFileInfo;
}
@Override
public void run() {
HttpURLConnection conn = null;
RandomAccessFile raf = null;
try {
URL url = new URL(mFileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
int length = -1;
if (code == HttpURLConnection.HTTP_OK) {
length = conn.getContentLength();
}
//如果文件長度為小于0,表示獲取文件失敗尼荆,直接返回
if (length <= 0) {
return;
}
// 判斷文件路徑是否存在左腔,不存在這創(chuàng)建
File dir = new File(DownloadPath);
if (!dir.exists()) {
dir.mkdir();
}
// 創(chuàng)建本地文件
File file = new File(dir, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.setLength(length);
// 設(shè)置文件長度
mFileInfo.setLength(length);
// 將FileInfo對象傳遞給Handler
Message msg = Message.obtain();
msg.obj = mFileInfo;
msg.what = MSG_INIT;
mHandler.sendMessage(msg);
msg.setTarget(mHandler);
} catch (Exception e) {
e.printStackTrace();
} finally {
conn.disconnect();
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.run();
}
}
然后修改onStartConnand方法,在點擊開啟服務(wù)的時候初始化線程
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 獲得Activity穿來的參數(shù)
if (ACTION_START.equals(intent.getAction())) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
Log.i("test", "START" + fileInfo.toString());
new InitThread(fileInfo).start();
} else if (ACTION_STOP.equals(intent.getAction())) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
Log.i("test", "STOP" + fileInfo.toString());
}
return super.onStartCommand(intent, flags, startId);
}
開啟下載任務(wù)
終于輪到真正的下載任務(wù)了捅儒,這就是最后一步了液样。
在之前的代碼中,即使用Handler接收InitThread中傳遞過來的fileInfo對象時巧还,有一段代碼還沒有實現(xiàn)鞭莽,這段代碼是正在的下載邏輯:
// 獲取FileInfo對象,開始下載任務(wù)
mTask = new DownloadTask(DownloadService.this, fileInfo);
mTask.download();
現(xiàn)在我們開始實現(xiàn)DownloadTask下載任務(wù)這個類吧麸祷。
DownloadTask類有以下成員變量:
private Context mComtext = null;
private FileInfo mFileInfo = null;
private ThreadDAO mDao = null;
private int mFinished = 0;
public boolean mIsPause = false;
mComtext就不做介紹了澎怒,mFileInfo是封裝了下載文件的信息對象;
mDAO是對數(shù)據(jù)庫進行操作的工具類阶牍,它將會引用實現(xiàn)了它的接口的ThreadDAOImple類喷面。
mFinished用于臨時存儲文件下載的進度。
mIsPause則用于判斷文件是否在下載狀態(tài)又或者停止?fàn)顟B(tài)走孽。
設(shè)定好成員變量惧辈,在創(chuàng)建DownloadTask的構(gòu)造函數(shù),將成員變量初始化
public DownloadTask(Context comtext, FileInfo fileInfo) {
super();
this.mComtext = comtext;
this.mFileInfo = fileInfo;
this.mDao = new ThreadDAOImple(mComtext);
}
下面開始的便是下載線程的代碼實現(xiàn)磕瓷,將之前的代碼原理搬過來盒齿,改一改就好了,這里還是展示給大家看吧,代碼如下:
class DownloadThread extends Thread {
private ThreadInfo threadInfo = null;
public DownloadThread(ThreadInfo threadInfo) {
super();
this.threadInfo = threadInfo;
}
@Override
public void run() {
// 如果數(shù)據(jù)庫不存在下載信息县昂,添加下載信息
if (!mDao.isExists(threadInfo.getUrl(), threadInfo.getId())) {
mDao.insertThread(threadInfo);
}
HttpURLConnection conn = null;
RandomAccessFile raf = null;
InputStream is = null;
try {
URL url = new URL(mFileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
int start = threadInfo.getStart() + threadInfo.getFinished();
// 設(shè)置下載文件開始到結(jié)束的位置
conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
File file = new File(DownloadService.DownloadPath, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
mFinished += threadInfo.getFinished();
int code = conn.getResponseCode();
if (code == HttpURLConnection.HTTP_PARTIAL) {
is = conn.getInputStream();
byte[] bt = new byte[1024];
int len = -1;
// 定義UI刷新時間
long time = System.currentTimeMillis();
while ((len = is.read(bt)) != -1) {
raf.write(bt, 0, len);
mFinished += len;
// 設(shè)置爲(wèi)500毫米更新一次
if (System.currentTimeMillis() - time > 500) {
time = System.currentTimeMillis();
Intent intent = new Intent(DownloadService.ACTION_UPDATE);
intent.putExtra("finished", mFinished * 100 / mFileInfo.getLength());
Log.i("test", mFinished * 100 / mFileInfo.getLength() + "");
// 發(fā)送廣播給Activity
mComtext.sendBroadcast(intent);
}
if (mIsPause) {
mDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), mFinished);
return;
}
}
}
// 下載完成后,刪除數(shù)據(jù)庫信息
mDao.deleteThread(threadInfo.getUrl(), threadInfo.getId());
} catch (Exception e) {
e.printStackTrace();
} finally {
conn.disconnect();
try {
is.close();
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.run();
}
}
在這段代碼中我們還要在下載的時候發(fā)送廣播給Activity陷舅,用于更新Progressbar的進度倒彰,但更新不易過頻,以免影響UI效果莱睁,所以設(shè)置為每500毫米更新一下待讳,根據(jù)系統(tǒng)時間設(shè)定。
我們在下載邏輯中仰剿,還要判斷當(dāng)前下載的文件是否在數(shù)據(jù)庫中存在创淡,如果不存在就添加,如果存在就要從數(shù)據(jù)庫中獲取當(dāng)前下載位置南吮,然后繼續(xù)下載琳彩,所以增加以下方法:
public void download(){
// 從數(shù)據(jù)庫中獲取到下載的信息
List<ThreadInfo> list = mDao.queryThreads(mFileInfo.getUrl());
ThreadInfo info = null;
if (list.size() == 0) {
info = new ThreadInfo(0, mFileInfo.getUrl(), 0, mFileInfo.getLength(), 0);
}else{
info= list.get(0);
}
new DownloadThread(info).start();
}
好了,整個的下載任務(wù)類已經(jīng)完成了部凑,下面我們繼續(xù)完善我們的代碼吧露乏。
完善Service和MainActivity代碼
在之前,我們雖然把初始化下載線程InitThread寫好了涂邀,然后通過初始化線程獲取FileInfo對象瘟仿,將其傳遞給Handler,在Handler中開啟真正的下載任務(wù)比勉。但是當(dāng)時并沒有調(diào)用這個InitThread類劳较,現(xiàn)在再次修改DownloadService中的onStartCommand方法來啟動InitThread任務(wù)吧
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 獲得Activity穿來的參數(shù)
if (ACTION_START.equals(intent.getAction())) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
Log.i("test", "START" + fileInfo.toString());
new InitThread(fileInfo).start();
} else if (ACTION_STOP.equals(intent.getAction())) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
Log.i("test", "STOP" + fileInfo.toString());
if (mTask != null) {
mTask.mIsPause = true;
}
}
return super.onStartCommand(intent, flags, startId);
}
我們通過MainActivity的點擊事件開啟服務(wù),如果Intent中傳過來的值為ACTION_START的時候浩聋,開啟初始化線程观蜗,獲得FileInfo對象,然后將其傳遞給Handler啟動下載任務(wù)赡勘。
如果MainActivity傳遞過來的值為ACTION_STOP嫂便,就判斷當(dāng)前是否有下載任務(wù),如果有下載任務(wù)闸与,就將DownloadTask中的成員變量mIsPause設(shè)置為true毙替,這時就更新數(shù)據(jù)庫中的下載進度了。
然后我們在修改MainActivity中代碼践樱,添加一個廣播接收者的內(nèi)部類厂画,它接收從DownloadTask中傳過來的廣播--下載進度,然后實時更新ProgressBar拷邢。代碼如下:
// 從DownloadTadk中獲取廣播信息袱院,更新進度條
class UIRecive extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (DownloadService.ACTION_UPDATE.equals(intent.getAction())) {
int finished = intent.getIntExtra("finished", 0);
downloadProgress.setProgress(finished);
}
}
}
這是一個自己手動撰寫的廣播,因此需要動態(tài)注冊,在MainActivity中的創(chuàng)建一個成員變量廣播接收者對象mRecive忽洛,在onCreate方法注冊廣播接收者:
// 從DownloadTadk中獲取廣播信息腻惠,更新進度條
class UIRecive extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (DownloadService.ACTION_UPDATE.equals(intent.getAction())) {
int finished = intent.getIntExtra("finished", 0);
downloadProgress.setProgress(finished);
}
}
}
最后不要忘記在Activity的onDestry方法中注銷廣播!
最后的最后就是千萬記得在Androidmainfest中獲取網(wǎng)絡(luò)欲虚、存儲卡讀取/寫入權(quán)限等等集灌。
總結(jié)
這是一個單線程的斷點續(xù)傳下載,也僅僅是一個下載DEMO复哆,但是它包含了Android中各種組件的混合使用欣喧,對于Android新手全面的了解Android項目很有好處。
但是要注意的是這個項目仍然有許多BUG等著大家自己去修復(fù)梯找,如在下載的時候再次點擊下載唆阿,你會發(fā)現(xiàn)又多了一個新的下載線程,導(dǎo)致進度條跳來跳去锈锤。解決這個問題也簡單驯鳖,只需要在開啟之前判斷一下是否已經(jīng)有這個文件了,如果有就直接跳牙咏。
其次還有個bug臼隔,那就是在本項目中存儲進度的數(shù)據(jù)類型是int類型,如果你說下載的文件過大妄壶,如超過30M的時候摔握,你會發(fā)現(xiàn)你的進度條下載到一半就消失了。這是因為下載數(shù)據(jù)超過int的數(shù)據(jù)范圍丁寄,導(dǎo)致內(nèi)存泄漏氨淌。這個問題只需要將數(shù)據(jù)類型修改為long類型就好了。
但是伊磺,不管怎么說這是一個很好的練習(xí)項目盛正。
最后做一下自來水,這個項目在慕課網(wǎng)中有教程屑埋,哈哈豪筝。
如果有哪位大神看到本文有什么錯誤之處,還請不吝賜教~~
本文的DEMO示例github下載地址:
https://github.com/liaozhoubei/Download
本文后續(xù)多線程下載器:http://www.reibang.com/p/c23b0c10c919