Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 斷點續傳原理以及實現

Android 斷點續傳原理以及實現

編輯:關於Android編程

Android 斷點續傳原理以及實現

0.  前言

在Android開發中,斷點續傳聽起來挺容易,在下載一個文件時點擊暫停任務暫停,點擊開始會繼續下載文件。但是真正實現起來知識點還是蠻多的,因此今天有時間實現了一下,並進行記錄。

1.  斷點續傳原理

在本地下載過程中要使用數據庫實時存儲到底存儲到文件的哪個位置了,這樣點擊開始繼續傳遞時,才能通過HTTP的GET請求中的setRequestProperty()方法可以告訴服務器,數據從哪裡開始,到哪裡結束。同時在本地的文件寫入時,RandomAccessFile的seek()方法也支持在文件中的任意位置進行寫入操作。同時通過廣播將子線程的進度告訴Activity的ProcessBar。

2.  Activity的按鈕響應

當點擊開始按鈕時,將url寫在了FileInfo類的對象info中並通過Intent從Activity傳遞到了Service中。這裡使用setAction()來區分是開始按鈕還是暫停按鈕。

public class FileInfo implements Serializable{ 
  private String url; //URL 
  private int length; //長度或結束位置 
  private int start; //開始位置 
  private int now;//當前進度 
//構造方法,set/get略 
} 
//開始按鈕邏輯,停止邏輯大致相同 
strat.setOnClickListener(new View.OnClickListener() { 
   @Override 
   public void onClick(View view) { 
    Intent intent = new Intent(MainActivity.this,DownLoadService.class); 
    intent.setAction(DownLoadService.ACTION_START); 
    intent.putExtra("fileUrl",info); 
    startService(intent); 
  } 
}); 

3.  在Service中的子線程中獲取文件大小

在Service中的onStartCommand()中,將FileInfo對象從Intent中取出,如果是開始命令,則開啟一個線程,根據該url去獲得要下載文件的大小,將該大小寫入對象並通過Handler傳回Service,同時在本地創建一個相同大小的本地文件。暫停命令最後會講到。

public void run() { 
      HttpURLConnection urlConnection = null; 
      RandomAccessFile randomFile = null; 
      try { 
        URL url = new URL(fileInfo.getUrl()); 
        urlConnection = (HttpURLConnection) url.openConnection(); 
        urlConnection.setConnectTimeout(3000); 
        urlConnection.setRequestMethod("GET"); 
        int length = -1; 
        if (urlConnection.getResponseCode() == HttpStatus.SC_OK) { 
          //獲得文件長度 
          length = urlConnection.getContentLength(); 
        } 
        if (length <= 0) { 
          return; 
        } 
        //創建相同大小的本地文件 
        File dir = new File(DOWNLOAD_PATH); 
        if (!dir.exists()) { 
          dir.mkdir(); 
        } 
        File file = new File(dir, FILE_NAME); 
        randomFile = new RandomAccessFile(file, "rwd"); 
        randomFile.setLength(length); 
        //長度給fileInfo對象 
        fileInfo.setLength(length); 
        //通過Handler將對象傳遞給Service 
        mHandle.obtainMessage(0, fileInfo).sendToTarget(); 
      } catch (Exception e) { 
        e.printStackTrace(); 
      } finally { //流的回收邏輯略 
      } 
    } 
  } 

4.  數據庫操作封裝

在Service的handleMessage()方法中拿到有length屬性的FileInfo對象,並使用自定義的DownLoadUtil類進行具體的文件下載邏輯。這裡傳入上下文,因為數據庫處理操作需要用到。

downLoadUtil = new DownLoadUtil(DownLoadService.this,info);

downLoadUtil.download();

這裡有一個數據庫操作的接口ThreadDAO,內部有增刪改查等邏輯,用於記錄下載任務的信息。自定義一個ThreadDAOImpl類將這裡的邏輯實現,內部數據庫創建關於繼承SQLiteOpenHelper的自定義類的邏輯就不貼了,比較簡單,該類會在ThreadDAOImpl類的構造方法中創建實例。完成底層數據庫操作的封裝。

public interface ThreadDAO { 
  //插入一條數據 
  public void insert(FileInfo info); 
  //根據URL刪除一條數據 
  public void delete(String url); 
  //根據URL更新一條進度 
  public void update(String url,int finished); 
  //根據URL找到一條數據 
  public List<FileInfo> get(String url); 
  //是否存在 
  public boolean isExits(String url); 
} 

5.  具體的文件下載邏輯

public class DownLoadUtil { 
  //構造方法略 
  public void download(){ 
    List<FileInfo> lists = threadDAO.get(fileInfo.getUrl()); 
    FileInfo info = null; 
    if(lists.size() == 0){ 
      //第一次下載,創建子線程下載 
      new MyThread(fileInfo).start(); 
    }else{ 
      //中間開始的 
      info = lists.get(0); 
      new MyThread(info).start(); 
    } 
  } 
 
  class MyThread extends Thread{ 
    private FileInfo info = null; 
    public MyThread(FileInfo threadInfo) { 
      this.info = threadInfo; 
    } 
    @Override 
    public void run() { 
      //向數據庫添加線程信息 
      if(!threadDAO.isExits(info.getUrl())){ 
        threadDAO.insert(info); 
      } 
      HttpURLConnection urlConnection = null; 
      RandomAccessFile randomFile =null; 
      InputStream inputStream = null; 
      try { 
        URL url = new URL(info.getUrl()); 
        urlConnection = (HttpURLConnection) url.openConnection(); 
        urlConnection.setConnectTimeout(3000); 
        urlConnection.setRequestMethod("GET"); 
        //設置下載位置 
        int start = info.getStart() + info.getNow(); 
        urlConnection.setRequestProperty("Range","bytes=" + start + "-" + info.getLength()); 
 
        //設置文件寫入位置 
        File file = new File(DOWNLOAD_PATH,FILE_NAME); 
        randomFile = new RandomAccessFile(file, "rwd"); 
        randomFile.seek(start); 
 
        //向Activity發廣播 
        Intent intent = new Intent(ACTION_UPDATE); 
        finished += info.getNow(); 
 
        if (urlConnection.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) { 
          //獲得文件流 
          inputStream = urlConnection.getInputStream(); 
          byte[] buffer = new byte[512]; 
          int len = -1; 
          long time = System.currentTimeMillis(); 
          while ((len = inputStream.read(buffer))!= -1){ 
            //寫入文件 
            randomFile.write(buffer,0,len); 
            //把進度發送給Activity 
            finished += len; 
            //看時間間隔,時間間隔大於500ms再發 
            if(System.currentTimeMillis() - time >500){ 
              time = System.currentTimeMillis(); 
              intent.putExtra("now",finished *100 /fileInfo.getLength()); 
              context.sendBroadcast(intent); 
            } 
            //判斷是否是暫停狀態 
            if(isPause){ 
              threadDAO.update(info.getUrl(),finished); 
              return; //結束循環 
            } 
          } 
          //刪除線程信息 
          threadDAO.delete(info.getUrl()); 
        } 
      }catch (Exception e){ 
        e.printStackTrace(); 
      }finally {//回收工作略 
      } 
    } 
  } 
} 

上面也講到使用自定義的DownLoadUtil類進行具體的文件下載邏輯,這也是最關鍵的部分了,在該類的構造方法中進行ThreadDAOImpl實例的創建。並在download()中通過數據庫查詢的操作,判斷是否是第一次開始下載任務,如果是,則開啟一個子線程MyThread進行下載任務,否則將進度信息從數據庫中取出,並將該信息傳遞給MyThread。

在MyThread中,通過info.getStart() + info.getNow()設置開始下載的位置,如果是第一次下載兩個數將都是0,如果是暫停後再下載,則info.getNow()會取出非0值,該值來自數據庫存儲。使用setRequestProperty告知服務器從哪裡開始傳遞數據,傳遞到哪裡結束,本地使用RandomAccessFile的seek()方法進行數據的本地存儲。使用廣播將進度的百分比傳遞給Activity,Activity再改變ProcessBar進行UI調整。

這裡很關鍵的一點是在用戶點擊暫停後會在Service中調用downLoadUtil.isPause = true,因此上面while循環會結束,停止下載並通過數據庫的update()保存進度值。從而在續傳時取出該值,重新對服務器發起文件起始點的下載任務請求,同時也在本地文件的相應位置繼續寫入操作。

6.  效果如下所示


感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved