Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android編程開發實現多線程斷點續傳下載器實例

Android編程開發實現多線程斷點續傳下載器實例

編輯:關於Android編程

本文實例講述了Android編程開發實現多線程斷點續傳下載器。分享給大家供大家參考,具體如下:

使用多線程斷點續傳下載器在下載的時候多個線程並發可以占用服務器端更多資源,從而加快下載速度,在下載過程中記錄每個線程已拷貝數據的數量,如果下載中斷,比如無信號斷線、電量不足等情況下,這就需要使用到斷點續傳功能,下次啟動時從記錄位置繼續下載,可避免重復部分的下載。這裡采用數據庫來記錄下載的進度。

效果圖

 

斷點續傳

1.斷點續傳需要在下載過程中記錄每條線程的下載進度
2.每次下載開始之前先讀取數據庫,查詢是否有未完成的記錄,有就繼續下載,沒有則創建新記錄插入數據庫
3.在每次向文件中寫入數據之後,在數據庫中更新下載進度
4.下載完成之後刪除數據庫中下載記錄

Handler傳輸數據

這個主要用來記錄百分比,每下載一部分數據就通知主線程來記錄時間

1.主線程中創建的View只能在主線程中修改,其他線程只能通過和主線程通信,在主線程中改變View數據
2.我們使用Handler可以處理這種需求

主線程中創建Handler,重寫handleMessage()方法

新線程中使用Handler發送消息,主線程即可收到消息,並且執行handleMessage()方法

動態生成新View

可實現多任務下載

1.創建XML文件,將要生成的View配置好
2.獲取系統服務LayoutInflater,用來生成新的View
復制代碼 代碼如下:LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
3.使用inflate(int resource, ViewGroup root)方法生成新的View
4.調用當前頁面中某個容器的addView,將新創建的View添加進來

示例

進度條樣式 download.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  >
  <LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    >
    <!--進度條樣式默認為圓形進度條,水平進度條需要配置style屬性,
    ?android:attr/progressBarStyleHorizontal -->
    <ProgressBar
      android:layout_width="fill_parent"
      android:layout_height="20dp"
      
      />
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:text="0%"
      />
  </LinearLayout>
  <Button
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:onClick="pause"
    android:text="||"
    />
</LinearLayout>

頂部樣式 main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:id="@+id/root"
  >
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="請輸入下載路徑"
    />
  <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="30dp"
    >
    <EditText
      android:id="@+id/path"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:singleLine="true"
      android:layout_weight="1"
      />
    <Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="下載"
      android:onClick="download"
      />
  </LinearLayout>
</LinearLayout>

MainActivity.java

public class MainActivity extends Activity {
  private LayoutInflater inflater;
  private LinearLayout rootLinearLayout;
  private EditText pathEditText;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    //動態生成新View,獲取系統服務LayoutInflater,用來生成新的View
    inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    rootLinearLayout = (LinearLayout) findViewById(R.id.root);
    pathEditText = (EditText) findViewById(R.id.path);
    // 窗體創建之後, 查詢數據庫是否有未完成任務, 如果有, 創建進度條等組件, 繼續下載
    List<String> list = new InfoDao(this).queryUndone();
    for (String path : list)
      createDownload(path);
  }
  /**
   * 下載按鈕
   * @param view
   */
  public void download(View view) {
    String path = "http://192.168.1.199:8080/14_Web/" + pathEditText.getText().toString();
    createDownload(path);
  }
  /**
   * 動態生成新View
   * 初始化表單數據
   * @param path
   */
  private void createDownload(String path) {
    //獲取系統服務LayoutInflater,用來生成新的View
    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    LinearLayout linearLayout = (LinearLayout) inflater.inflate(R.layout.download, null);
    LinearLayout childLinearLayout = (LinearLayout) linearLayout.getChildAt(0);
    ProgressBar progressBar = (ProgressBar) childLinearLayout.getChildAt(0);
    TextView textView = (TextView) childLinearLayout.getChildAt(1);
    Button button = (Button) linearLayout.getChildAt(1);
    try {
      button.setOnClickListener(new MyListener(progressBar, textView, path));
      //調用當前頁面中某個容器的addView,將新創建的View添加進來
      rootLinearLayout.addView(linearLayout);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  private final class MyListener implements OnClickListener {
    private ProgressBar progressBar;
    private TextView textView;
    private int fileLen;
    private Downloader downloader;
    private String name;
    /**
     * 執行下載
     * @param progressBar //進度條
     * @param textView //百分比
     * @param path //下載文件路徑
     */
    public MyListener(ProgressBar progressBar, TextView textView, String path) {
      this.progressBar = progressBar;
      this.textView = textView;
      name = path.substring(path.lastIndexOf("/") + 1);
      downloader = new Downloader(getApplicationContext(), handler);
      try {
        downloader.download(path, 3);
      } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(getApplicationContext(), "下載過程中出現異常", 0).show();
        throw new RuntimeException(e);
      }
    }
    //Handler傳輸數據
    private Handler handler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
        switch (msg.what) {
          case 0:
            //獲取文件的大小
            fileLen = msg.getData().getInt("fileLen");
            //設置進度條最大刻度:setMax()
            progressBar.setMax(fileLen);
            break;
          case 1:
            //獲取當前下載的總量
            int done = msg.getData().getInt("done");
            //當前進度的百分比
            textView.setText(name + "\t" + done * 100 / fileLen + "%");
            //進度條設置當前進度:setProgress()
            progressBar.setProgress(done);
            if (done == fileLen) {
              Toast.makeText(getApplicationContext(), name + " 下載完成", 0).show();
              //下載完成後退出進度條
              rootLinearLayout.removeView((View) progressBar.getParent().getParent());
            }
            break;
        }
      }
    };
    /**
     * 暫停和繼續下載
     */
    public void onClick(View v) {
      Button pauseButton = (Button) v;
      if ("||".equals(pauseButton.getText())) {
        downloader.pause();
        pauseButton.setText("▶");
      } else {
        downloader.resume();
        pauseButton.setText("||");
      }
    }
  }
}

Downloader.java

public class Downloader {
  private int done;
  private InfoDao dao;
  private int fileLen;
  private Handler handler;
  private boolean isPause;
  public Downloader(Context context, Handler handler) {
    dao = new InfoDao(context);
    this.handler = handler;
  }
  /**
   * 多線程下載
   * @param path 下載路徑
   * @param thCount 需要開啟多少個線程
   * @throws Exception
   */
  public void download(String path, int thCount) throws Exception {
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    //設置超時時間
    conn.setConnectTimeout(3000);
    if (conn.getResponseCode() == 200) {
      fileLen = conn.getContentLength();
      String name = path.substring(path.lastIndexOf("/") + 1);
      File file = new File(Environment.getExternalStorageDirectory(), name);
      RandomAccessFile raf = new RandomAccessFile(file, "rws");
      raf.setLength(fileLen);
      raf.close();
      //Handler發送消息,主線程接收消息,獲取數據的長度
      Message msg = new Message();
      msg.what = 0;
      msg.getData().putInt("fileLen", fileLen);
      handler.sendMessage(msg);
      //計算每個線程下載的字節數
      int partLen = (fileLen + thCount - 1) / thCount;
      for (int i = 0; i < thCount; i++)
        new DownloadThread(url, file, partLen, i).start();
    } else {
      throw new IllegalArgumentException("404 path: " + path);
    }
  }
  private final class DownloadThread extends Thread {
    private URL url;
    private File file;
    private int partLen;
    private int id;
    public DownloadThread(URL url, File file, int partLen, int id) {
      this.url = url;
      this.file = file;
      this.partLen = partLen;
      this.id = id;
    }
    /**
     * 寫入操作
     */
    public void run() {
      // 判斷上次是否有未完成任務
      Info info = dao.query(url.toString(), id);
      if (info != null) {
        // 如果有, 讀取當前線程已下載量
        done += info.getDone();
      } else {
        // 如果沒有, 則創建一個新記錄存入
        info = new Info(url.toString(), id, 0);
        dao.insert(info);
      }
      int start = id * partLen + info.getDone(); // 開始位置 += 已下載量
      int end = (id + 1) * partLen - 1;
      try {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(3000);
        //獲取指定位置的數據,Range范圍如果超出服務器上數據范圍, 會以服務器數據末尾為准
        conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
        RandomAccessFile raf = new RandomAccessFile(file, "rws");
        raf.seek(start);
        //開始讀寫數據
        InputStream in = conn.getInputStream();
        byte[] buf = new byte[1024 * 10];
        int len;
        while ((len = in.read(buf)) != -1) {
          if (isPause) {
            //使用線程鎖鎖定該線程
            synchronized (dao) {
              try {
                dao.wait();
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
            }
          }
          raf.write(buf, 0, len);
          done += len;
          info.setDone(info.getDone() + len);
          // 記錄每個線程已下載的數據量
          dao.update(info);
          //新線程中用Handler發送消息,主線程接收消息
          Message msg = new Message();
          msg.what = 1;
          msg.getData().putInt("done", done);
          handler.sendMessage(msg);
        }
        in.close();
        raf.close();
        // 刪除下載記錄
        dao.deleteAll(info.getPath(), fileLen);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  //暫停下載
  public void pause() {
    isPause = true;
  }
  //繼續下載
  public void resume() {
    isPause = false;
    //恢復所有線程
    synchronized (dao) {
      dao.notifyAll();
    }
  }
}

Dao:

DBOpenHelper:

public class DBOpenHelper extends SQLiteOpenHelper {
  public DBOpenHelper(Context context) {
    super(context, "download.db", null, 1);
  }
  @Override
  public void onCreate(SQLiteDatabase db) {
    db.execSQL("CREATE TABLE info(path VARCHAR(1024), thid INTEGER, done INTEGER, PRIMARY KEY(path, thid))");
  }
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  }
}

InfoDao:

public class InfoDao {
  private DBOpenHelper helper;
  public InfoDao(Context context) {
    helper = new DBOpenHelper(context);
  }
  public void insert(Info info) {
    SQLiteDatabase db = helper.getWritableDatabase();
    db.execSQL("INSERT INTO info(path, thid, done) VALUES(?, ?, ?)", new Object[] { info.getPath(), info.getThid(), info.getDone() });
  }
  public void delete(String path, int thid) {
    SQLiteDatabase db = helper.getWritableDatabase();
    db.execSQL("DELETE FROM info WHERE path=? AND thid=?", new Object[] { path, thid });
  }
  public void update(Info info) {
    SQLiteDatabase db = helper.getWritableDatabase();
    db.execSQL("UPDATE info SET done=? WHERE path=? AND thid=?", new Object[] { info.getDone(), info.getPath(), info.getThid() });
  }
  public Info query(String path, int thid) {
    SQLiteDatabase db = helper.getWritableDatabase();
    Cursor c = db.rawQuery("SELECT path, thid, done FROM info WHERE path=? AND thid=?", new String[] { path, String.valueOf(thid) });
    Info info = null;
    if (c.moveToNext())
      info = new Info(c.getString(0), c.getInt(1), c.getInt(2));
    c.close();
    return info;
  }
  public void deleteAll(String path, int len) {
    SQLiteDatabase db = helper.getWritableDatabase();
    Cursor c = db.rawQuery("SELECT SUM(done) FROM info WHERE path=?", new String[] { path });
    if (c.moveToNext()) {
      int result = c.getInt(0);
      if (result == len)
        db.execSQL("DELETE FROM info WHERE path=? ", new Object[] { path });
    }
  }
  public List<String> queryUndone() {
    SQLiteDatabase db = helper.getWritableDatabase();
    Cursor c = db.rawQuery("SELECT DISTINCT path FROM info", null);
    List<String> pathList = new ArrayList<String>();
    while (c.moveToNext())
      pathList.add(c.getString(0));
    c.close();
    return pathList;
  }
}

希望本文所述對大家Android程序設計有所幫助。

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