Android實戰(zhàn):簡易斷點續(xù)傳下載器實現(xiàn)

實踐是檢驗真理的唯一標(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摘能,一起剝皮案震驚了整個濱河市续崖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌团搞,老刑警劉巖严望,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異逻恐,居然都是意外死亡像吻,警方通過查閱死者的電腦和手機峻黍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拨匆,“玉大人姆涩,你說我怎么就攤上這事〔衙浚” “怎么了阵面?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洪鸭。 經(jīng)常有香客問我,道長仑扑,這世上最難降的妖魔是什么览爵? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮镇饮,結(jié)果婚禮上蜓竹,老公的妹妹穿的比我還像新娘。我一直安慰自己储藐,他們只是感情好俱济,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钙勃,像睡著了一般蛛碌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辖源,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蔚携,我揣著相機與錄音,去河邊找鬼克饶。 笑死酝蜒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矾湃。 我是一名探鬼主播亡脑,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼邀跃!你這毒婦竟也來了霉咨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坞嘀,失蹤者是張志新(化名)和其女友劉穎躯护,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丽涩,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡棺滞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年裁蚁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片继准。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡枉证,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出移必,到底是詐尸還是另有隱情室谚,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布崔泵,位于F島的核電站秒赤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏憎瘸。R本人自食惡果不足惜入篮,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幌甘。 院中可真熱鬧潮售,春花似錦、人聲如沸锅风。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽皱埠。三九已至肮帐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間边器,已是汗流浹背泪姨。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饰抒,地道東北人肮砾。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像袋坑,于是被迫代替她去往敵國和親仗处。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容