1. 程式人生 > >不使用第三方框架編寫的多執行緒斷線續傳功能

不使用第三方框架編寫的多執行緒斷線續傳功能

  一、背景
  
  最近需要個斷線續傳功能,但是覺得一些框架不太適合,所以基於原理編寫了一個多執行緒斷線續傳功能
  
  支援技術分享,但是複製和轉發我的部落格時候請標明出處,謝謝 https://my.oschina.net/grkj/blog/2907188
  
  二、斷線續傳的個人理解:
  
  1、斷線續傳在個人理解,其實就是在出現正常下載流程之外的事情的時候,儲存好當前檔案下載的進度,然後點選繼續下載的時候,從上次的下載進度繼續進行下載。
  
  2、如何從上次下載進度繼續進行下載呢? 主要就是設定頭部資訊進行告知實現的
  
  setRequestProperty("Range", "bytes=" + progress + "-" + total);//設定下載範圍
  
  三、主要功能有
  
  1、支援多執行緒斷線續傳
  
  2、支援回撥事件拓展,使用泛型定義物件,支援更加靈活的去拓展物件
  
  3、如果要下載的資源在要儲存的資料夾中存在,那麼會自動進行下載位置校準和下載
  
  4、支援自定義資源請求的方式(GET和POST方式)和請求超時時間
  
  5、我編不下了,如果你發現了就幫我寫上去,謝謝....... 效果圖如下
  
  下載3只是裝飾,你可以換個地址和修改一下MainActivity的按鈕監控那塊的程式碼,抄下載下載1和下載2的程式碼即可 下載中 下載完畢
  
  後面我會完善個功能,只要連上網路就進行檢查,然後自動進行資源下載,如果有需要可以給我留言
  
  四、直接上原始碼講解
  
  篇幅太長,貼不了那麼多,只貼8點 程式碼下載地址為:點選下載 原始碼裡面DownLoadTask建構函式裡面有個地方寫錯了,寫死成了GET方式,如果下載原始碼的要使用,可以複製下面的DownLoadTask原始碼進去覆蓋掉就好了
  
  1、多執行緒例項,主要的內容都在這裡了
  
  //執行下載的執行緒
  
  public class DownLoadTask implements Runnable {
  
  private static final String TAG = "DownLoadTask";
  
  public static final int CACHE_SIZE = 4 * 1024;//緩衝區間,4應該足夠了
  
  public static final int DEFAULT_TIME_OUT = 5000;//單位是毫秒,預設是5秒,支援自定義
  
  //執行緒安全的資源列表,key是檔名稱,value是下載例項
  
  private static ConcurrentHashMap<String, DownLoadEntity> mResourceMap = new ConcurrentHashMap<String, DownLoadEntity>();
  
  /**
  
  * @Description 停止下載
  
  * [@author](https://my.oschina.net/arthor) 姚旭民
  
  * [@date](https://my.oschina.net/u/2504391) 2018/11/20 16:37
  
  */
  
  public static void stop(String key) throws NullPointerException {
  
  try {
  
  if (key == null)
  
  throw new NullPointerException();
  
  mResourceMap.get(key).setStop(true);
  
  } catch (Exception e) {
  
  Log.e(TAG, e.toString());
  
  }
  
  }
  
  /**
  
  * [@param](https://my.oschina.net/u/2303379) key 檔案憑證
  
  * @Description 資源刪除
  
  * @author 姚旭民
  
  * @date 2018/11/20 17:22
  
  */
  
  public static void remove(String key) throws NullPointerException {
  
  if (key == null || mResourceMap.get(key) == null)
  
  throw new NullPointerException("引數為null或者下載資料不存在");
  
  mResourceMap.get(key).setDelete(true);
  
  }
  
  //下載實體
  
  DownLoadEntity mDownLoadEntity;
  
  //回撥物件,只要進行實現,就可以獲得各種事件的觀察回撥,IDownLoadCallBack原始碼在 第2點 有貼出來
  
  IDownLoadCallBack mCallBack;
  
  //傳輸方式,是一個列舉型別,支援自定義傳輸
  
  TransmissionType mType;
  
  //下載的超時時間
  
  int mTimeout;
  
  public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack) {
  
  this(downLoadEntity, mCallBack, TransmissionType.TYPE_GET);
  
  }
  
  public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack, TransmissionType type) {
  
  this(downLoadEntity, mCallBack, type, DEFAULT_TIME_OUT);
  
  }
  
  public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack, TransmissionType type, int timeout) {
  
  this.mDownLoadEntity www.dfgjpt.com= downLoadEntity;
  
  this.mCallBack =www.gcyl152.com/ mCallBack;
  
  this.mType = type;
  
  this.mTimeout www.gcyL157.com= timeout;
  
  //資料儲存
  
  mResourceMap.put(downLoadEntity.getKey(), downLoadEntity);
  
  Log.v(TAG, "存放資料進入鍵值對,key:" + downLoadEntity.getKey() + ",downLoadEntity:" + downLoadEntity);
  
  }
  
  @Override
  
  public void run(www.michenggw.com) {
  
  //下載路徑
  
  String downUrl = mDownLoadEntity.getDownUrl();
  
  //儲存路徑
  
  String savePath = mDownLoadEntity.getSavePath();
  
  //已經下載的進度
  
  long progress = mDownLoadEntity.getProgress();//已經下載好的長度
  
  long total = mDownLoadEntity.getTotal();//檔案的總長度
  
  String key = mDownLoadEntity.getKey();
  
  HttpURLConnection connection = null;
  
  //有人可能覺得NIO 的 FileChannel 也可以的話,那麼你也可以替換掉
  
  RandomAccessFile randomAccessFile = null;
  
  try {
  
  //設定檔案寫入位置
  
  File file = new File(savePath);
  
  //父類資料夾是否存在
  
  File fileParent = file.getParentFile();
  
  if (!fileParent.exists(www.mcyllpt.com)) {//如果父類資料夾不存在,即建立資料夾
  
  Log.v(TAG, "父類資料夾:" + fileParent.getPath() + ",不存在,開始建立");
  
  fileParent.mkdirs();
  
  }
  
  if (file != null) {//這一步是針對於斷線續傳的檔案,用於比對資料庫和真實的資料,避免出現誤差
  
  long fileSize = file.length();
  
  if (progress != fileSize) {//如果檔案有問題,以實際下載的檔案大小為準
  
  Log.v(TAG, "檔案傳輸節點不一致,開始修復傳資料節點");
  
  progress = fileSize;
  
  mDownLoadEntity.setProgress(progress);
  
  }
  
  }
  
  int precent = (int) www.mhylpt.com ((float) progress / (float) total * 100);
  
  //開始下載之前先回調開始下載的進度
  
  mCallBack.onNext(key, precent);
  
  URL url = new URL(downUrl);
  
  connection = (HttpURLConnection) url.openConnection();
  
  //請求方式預設為GET
  
  connection.setRequestMethod(mType.getType());
  
  //超時時間
  
  connection.setConnectTimeout(mTimeout);
  
  //從上次下載完成的地方下載
  
  //設定下載位置(從伺服器上取要下載檔案的某一段)
  
  connection.setRequestProperty("Range", "bytes=" + progress + "-" + total);//設定下載範圍
  
  randomAccessFile = new RandomAccessFile(file, "rwd");
  
  //從檔案的某一位置開始寫入
  
  randomAccessFile.seek(progress);
  
  if (connection.getResponseCode() == 206) {//檔案部分下載,返回碼為206
  
  InputStream is = connection.getInputStream();
  
  byte[] buffer = new byte[CACHE_SIZE];
  
  //接收到的資源大小
  
  int len;
  
  while ((len =www.furong157.com is.read(buffer)) != -1) {
  
  //寫入檔案
  
  randomAccessFile.write(buffer, 0, len);
  
  progress += len;
  
  precent = (int) ((float) progress / (float) total * 100);
  
  //更新進度回撥
  
  mCallBack.onNext(key, precent);
  
  //停止下載
  
  if (mDownLoadEntity.isStop()) {
  
  mDownLoadEntity.setProgress(progress);
  
  mCallBack.onPause(mDownLoadEntity, key, precent, progress, total);
  
  return;
  
  }
  
  //取消下載
  
  if (mDownLoadEntity.isDelete()) {
  
  mResourceMap.remove(key);
  
  //檔案刪除
  
  file.delete();
  
  mCallBack.onDelete(mDownLoadEntity, key);
  
  return;
  
  }
  
  }
  
  }
  
  //資源刪除
  
  mResourceMap.remove(mDownLoadEntity.getFileName());
  
  //下載完成
  
  mCallBack.onSuccess(mDownLoadEntity, key);
  
  } catch (Exception e) {
  
  //資源刪除
  
  mResourceMap.remove(mDownLoadEntity.getFileName());
  
  mDownLoadEntity.setProgress(progress);
  
  //防止意外
  
  mDownLoadEntity.setStop(false);
  
  //失敗原因回撥
  
  mCallBack.onFail(mDownLoadEntity, key, e.toString());
  
  StringBuffer sb = new StringBuffer();
  
  Writer writer = new StringWriter();
  
  PrintWriter printWriter = new PrintWriter(writer);
  
  e.printStackTrace(printWriter);
  
  Throwable cause = e.getCause();
  
  while (cause != null) {
  
  cause.printStackTrace(printWriter);
  
  cause = cause.getCause();
  
  }
  
  printWriter.close();
  
  //異常的詳細內容
  
  String result = writer.toString();
  
  Log.e(TAG, result);
  
  } finally {
  
  if (connection != null) {
  
  connection.disconnect();
  
  }
  
  try {
  
  if (randomAccessFile != null) {
  
  randomAccessFile.close();
  
  }
  
  } catch (IOException e) {
  
  StringBuffer sb = new StringBuffer();
  
  Writer writer = new StringWriter();
  
  PrintWriter printWriter = new PrintWriter(writer);
  
  e.printStackTrace(printWriter);
  
  Throwable cause = e.getCause();
  
  while (cause != null) {
  
  cause.printStackTrace(printWriter);
  
  cause = cause.getCause();
  
  }
  
  printWriter.close();
  
  //異常的詳細內容
  
  String result = writer.toString();
  
  Log.e(TAG, result);
  
  }
  
  }
  
  }
  
  }
  
  2、IDownLoadCallBack原始碼,這裡的泛型主要是因為和公司一些業務有關,這裡沒有列出來,這裡的泛型其實可以去掉的,因為基本這裡沒什麼用的,T 都改成 DownLoadEntity例項即可
  
  public interface IDownLoadCallBack<T> {
  
  /**
  
  * @param key     下載的檔案的標識,主要用於顯示的時候辨別是哪個檔案在操作,由使用的人去定義
  
  * @param precent 已經下載的百分比 取值區間為 [0,100]
  
  * @Description
  
  * @author 姚旭民
  
  * @date 2018/11/20 9:46
  
  */
  
  public abstract void onNext(String key, int precent);
  
  /**
  
  * @param t            下載的檔案的實體封裝類
  
  * @param key          下載的檔案的標識,主要用於顯示的時候辨別是哪個檔案在操作,由使用的人去定義
  
  * @param precent      已經下載的百分比
  
  * @param downLoadSize 已經下載的長度
  
  * @param total        資源的總長度
  
  * @Description
  
  * @author 姚旭民
  
  * @date 2018/11/20 10:48
  
  */
  
  public abstract void onPause(T t, String key, int precent, long downLoadSize, long total);
  
  /**
  
  * @Description 刪除檔案回撥
  
  * @author 姚旭民
  
  * @date 2018/11/22 10:47
  
  *
  
  * @param t 操作的下載物件
  
  * @param key 檔案憑證
  
  */
  
  public abstract void onDelete(T t, String key);
  
  /**
  
  * @param t   自定義的值
  
  * @param key 下載的檔案的標識,主要用於顯示的時候辨別是哪個檔案在操作,由使用的人去定義
  
  * @Description
  
  * @author 姚旭民
  
  * @date 2018/11/20 9:46
  
  */
  
  public abstract void onSuccess(T t, String key);
  
  /**
  
  * @param t      自定義的值
  
  * @param key    下載的檔案的標識,主要用於顯示的時候辨別是哪個檔案在操作,由使用的人去定義
  
  * @param reason 失敗原因
  
  * @Description
  
  * @author 姚旭民
  
  * @date 2018/11/20 9:46
  
  */
  
  public abstract void onFail(T t, String key, String reason);
  
  3、IDownLoadCallBack包裝類繼承,包裝類用於包裝泛型物件,其實這一步可以不要的,只是有點別的考慮,所以這樣寫
  
  /**
  
  * @Description 包裝類
  
  * @author 姚旭民
  
  * @date 2018/11/20 13:57
  
  */
  
  public interface IResumeCallBack extends IDownLoadCallBack<ResumeEntity> {
  
  }
  
  4、ResumeEntity物件原始碼主要繼承了DownLoadEntity(第5點),其他沒什麼的
  
  public class ResumeEntity extends DownLoadEntity {
  
  public static enum STATUS {
  
  FAIL(-1),//下載失敗
  
  DOWNLOAD(0),//下載中
  
  SUCCESS(1);//下載成功,可以使用
  
  private int value;
  
  private STATUS(int value) {
  
  this.value = value;
  
  }
  
  public int getValue() {
  
  return value;
  
  }
  
  }
  
  ResumeEntity(builder builder) {
  
  this.fileName = builder.fileName;
  
  this.downUrl = builder.downUrl;
  
  this.savePath = builder.savePath;
  
  this.total = builder.total;
  
  this.progress = builder.progress;
  
  this.status = builder.status;
  
  this.key = builder.key;
  
  }
  
  //鏈式程式設計,防止物件不一致,用static修飾,避免被保留強引用
  
  public static class builder {
  
  private String fileName;
  
  private String downUrl;
  
  private String savePath;
  
  private long total;
  
  private long progress;
  
  private int status;
  
  private boolean stop;
  
  private String key;
  
  public builder fileName(String fileName) {
  
  this.fileName = fileName;
  
  return this;
  
  }
  
  public builder downUrl(String downUrl) {
  
  this.downUrl = downUrl;
  
  return this;
  
  }
  
  public builder savePath(String savePath) {
  
  this.savePath = savePath;
  
  return this;
  
  }
  
  public builder total(long total) {
  
  this.total = total;
  
  return this;
  
  }
  
  public builder progress(long progress) {
  
  this.progress = progress;
  
  return this;
  
  }
  
  public builder status(int status) {
  
  this.status = status;
  
  return this;
  
  }
  
  public builder stop(boolean stop) {
  
  this.stop = stop;
  
  return this;
  
  }
  
  public builder key(String key) {
  
  this.key = key;
  
  return this;
  
  }
  
  public ResumeEntity builder() {
  
  return new ResumeEntity(this);
  
  }
  
  }
  
  @Override
  
  public String toString() {
  
  return "{" +
  
  "fileName='" + fileName + '\'' +
  
  ", downUrl='" + downUrl + '\'' +
  
  ", savePath='" + savePath + '\'' +
  
  ", total=" + total +
  
  ", progress=" + progress +
  
  ", status=" + status +
  
  ", stop=" + stop +
  
  ", key='" + key + '\'' +
  
  '}';
  
  }
  
  }
  
  5、DownLoadEntity原始碼區域
  
  public class DownLoadEntity {
  
  //資原始檔的名稱
  
  protected String fileName;
  
  //資原始檔的下載路徑
  
  protected String downUrl;
  
  //資原始檔的儲存完整路徑
  
  protected String savePath;
  
  //下載的資源的總長度
  
  protected long total;
  
  //已經下載的進度
  
  protected long progress;
  
  //資源的狀態 //下載的狀況 1為下載成功,0為可下載, -1為下載失敗 預設為0
  
  protected int status;
  
  //是否暫停下載 true為暫停下載, false代表可以下載, 預設為false
  
  protected boolean stop;
  
  //下載的檔案的標識,讓使用者更加靈活的去定義如何識別正在下載的檔案
  
  protected String key;
  
  //是否刪除下載的檔案
  
  protected boolean delete;
  
  //這裡是各種set和get,不花費篇幅貼上了,直接用工具生成就好了
  
  }
  
  6、IDownLoadCallBack的實現類,我是不想每次都建立一個匿名類了,太長了也繁瑣,我直接用activity去實現IDownLoadCallBack,感覺也挺好的,這裡是隨便寫的activity,主要用來測試的,UI介面原始碼在第7點
  
  public class MainActivity extends AppCompatActivity implements View.OnClickListener, IResumeCallBack, INetCallBack {
  
  private static final String TAG = "MainActivity";
  
  //資料庫操作輔助類
  
  private ResumeDbHelper mHelper = ResumeDbHelper.getInstance();
  
  private ResumeService mResumeService = ResumeService.getInstance();
  
  private MainActivity mInstance = this;
  
  private Button downloadBtn1, downloadBtn2, downloadBtn3;
  
  private Button pauseBtn1, pauseBtn2, pauseBtn3;
  
  private Button cancelBtn1, cancelBtn2, cancelBtn3;
  
  private ProgressBar mProgress1, mProgress2, mProgress3;
  
  private String url1 = "http://192.168.1.103/2.bmp";
  
  private String url2 = "http://192.168.1.103/testzip.zip";
  
  private String url3 = "http://192.168.1.103/photo.png";
  
  @Override
  
  protected void onCreate(Bundle savedInstanceState) {
  
  try {
  
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  NetReceiver.setCallBack(this);
  
  downloadBtn1 = bindView(R.id.main_btn_down1);
  
  downloadBtn2 = bindView(R.id.main_btn_down2);
  
  downloadBtn3 = bindView(R.id.main_btn_down3);
  
  pauseBtn1 = bindView(R.id.main_btn_pause1);
  
  pauseBtn2 = bindView(R.id.main_btn_pause2);
  
  pauseBtn3 = bindView(R.id.main_btn_pause3);
  
  cancelBtn1 = bindView(R.id.main_btn_cancel1);
  
  cancelBtn2 = bindView(R.id.main_btn_cancel2);
  
  cancelBtn3 = bindView(R.id.main_btn_cancel3);
  
  mProgress1 = bindView(R.id.main_progress1);
  
  mProgress2 = bindView(R.id.main_progress2);
  
  mProgress3 = bindView(R.id.main_progress3);
  
  downloadBtn1.setOnClickListener(this);
  
  downloadBtn2.setOnClickListener(this);
  
  downloadBtn3.setOnClickListener(this);
  
  pauseBtn1.setOnClickListener(this);
  
  pauseBtn2.setOnClickListener(this);
  
  pauseBtn3.setOnClickListener(this);
  
  cancelBtn1.setOnClickListener(this);
  
  cancelBtn2.setOnClickListener(this);
  
  cancelBtn3.setOnClickListener(this);
  
  } catch (Exception e) {
  
  Log.e(TAG, e.toString());
  
  }
  
  }
  
  @Override
  
  public void onClick(View v) {
  
  try {
  
  switch (v.getId()) {
  
  case R.id.main_btn_down1:
  
  Log.d(TAG, "點選了下載1,url1:" + url1);
  
  ThreadUtils.exeMgThread3(new Runnable() {
  
  @Override
  
  public void run() {
  
  mResumeService.download("1", url1, FileConts.IMG_PATH, mInstance);
  
  }
  
  });
  
  break;
  
  case R.id.main_btn_down2:
  
  Log.d(TAG, "點選了下載2");
  
  ThreadUtils.exeMgThread3(new Runnable() {
  
  @Override
  
  public void run() {
  
  mResumeService.download("2", url2, FileConts.IMG_PATH, mInstance);
  
  }
  
  });
  
  break;
  
  case R.id.main_btn_down3:
  
  Log.d(TAG, "點選了下載3");
  
  break;
  
  case R.id.main_btn_pause1:
  
  ThreadUtils.exeMgThread3(new Runnable() {
  
  @Override
  
  public void run() {
  
  Log.v(TAG, "點選暫停1");
  
  ResumeService.getInstance().stop("1");
  
  }
  
  });
  
  break;
  
  case R.id.main_btn_pause2:
  
  ThreadUtils.exeMgThread3(new Runnable() {
  
  @Override
  
  public void run() {
  
  Log.v(TAG, "點選暫停2");
  
  ResumeService.getInstance().stop("2");
  
  }
  
  });
  
  break;
  
  case R.id.main_btn_pause3:
  
  //                ResumeService.getInstance().cancel(url3);
  
  break;
  
  case R.id.main_btn_cancel1:
  
  ThreadUtils.exeMgThread3(new Runnable() {
  
  @Override
  
  public void run() {
  
  ResumeService.getInstance().remove(url1, "1");
  
  }
  
  });
  
  break;
  
  case R.id.main_btn_cancel2:
  
  ThreadUtils.exeMgThread3(new Runnable() {
  
  @Override
  
  public void run() {
  
  ResumeService.getInstance().remove(url2, "2");
  
  }
  
  });
  
  break;
  
  case R.id.main_btn_cancel3:
  
  //                ResumeService.getInstance().cancel(url3);
  
  break;
  
  }
  
  } catch (Exception e) {
  
  StringBuffer sb = new StringBuffer();
  
  Writer writer = new StringWriter();
  
  PrintWriter printWriter = new PrintWriter(writer);
  
  e.printStackTrace(printWriter);
  
  Throwable cause = e.getCause();
  
  while (cause != null) {
  
  cause.printStackTrace(printWriter);
  
  cause = cause.getCause();
  
  }
  
  printWriter.close();
  
  //異常的詳細內容
  
  String result = writer.toString();
  
  Log.e(TAG, result);
  
  }
  
  }
  
  private <T extends View> T bindView(@IdRes int id) {
  
  View viewById = findViewById(id);
  
  return (T) viewById;
  
  }
  
  @Override
  
  protected void onDestroy() {
  
  super.onDestroy();
  
  Log.v(TAG, "onDestroy");
  
  }
  
  //IDownLoadCallBack 介面 的各種回撥 事件 開始
  
  //下載的進度回撥
  
  @Override
  
  public void onNext(String key, int precent) {
  
  if ("1".equals(key)) {
  
  mProgress1.setMax(100);
  
  mProgress1.setProgress(precent);
  
  } else if ("2".equals(key)) {
  
  mProgress2.setMax(100);
  
  mProgress2.setProgress(precent);
  
  }
  
  }
  
  //下載的停止回撥,同時會將暫停狀態儲存進入資料庫
  
  @Override
  
  public void onPause(ResumeEntity resumeEntity, String key, int precent, long downLoadSize, long total) {
  
  Log.v(TAG, "onNext| 下載 暫停 回撥方法,resumeEntity:" + resumeEntity);
  
  mHelper.update(resumeEntity);
  
  }
  
  //刪除檔案回撥
  
  @Override
  
  public void onDelete(ResumeEntity resumeEntity, String key) {
  
  Log.v(TAG, "onDelete| 下載 刪除 回撥方法,resumeEntity:" + resumeEntity);
  
  mHelper.delete(resumeEntity.getFileName());
  
  }
  
  //下載成功的回撥
  
  @Override
  
  public void onSuccess(ResumeEntity resumeEntity, String key) {
  
  Log.v(TAG, "onNext| 下載 成功 回撥方法,resumeEntity:" + resumeEntity);
  
  resumeEntity.setStatus(ResumeEntity.STATUS.SUCCESS.getValue());
  
  mHelper.update(resumeEntity);
  
  }
  
  //下載失敗的回撥
  
  @Override
  
  public void onFail(ResumeEntity resumeEntity, String key, String reason) {
  
  Log.v(TAG, "onFail| 下載 失敗 回撥方法,resumeEntity:" + resumeEntity + ",reason:" + reason);
  
  resumeEntity.setStatus(ResumeEntity.STATUS.FAIL.getValue());
  
  mHelper.update(resumeEntity);
  
  }
  
  //IDownLoadCallBack 介面 的各種回撥 事件 結束
  
  //網路狀態回撥區域,這裡是我用來接著編寫網路重連之後繼續下載的東西的
  
  public void onStatusChange(String msg) {
  
  Log.v(TAG, "onStatusChange| 網路狀態回撥,內容為:" + msg);
  
  }
  
  }
  
  7、UI介面
  
  <?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"
  
  tools:context="com.yxm.resume.activity.MainActivity">
  
  <LinearLayout
  
  android:layout_width="match_parent"
  
  android:layout_height="wrap_content"
  
  android:orientation="horizontal">
  
  <ProgressBar
  
  android:id="@+id/main_progress1"
  
  style="@style/Widget.AppCompat.ProgressBar.Horizontal"
  
  android:layout_width="0dp"
  
  android:layout_height="match_parent"
  
  android:layout_weight="1"
  
  android:progressDrawable="@drawable/progressbar" /> 進度條樣式在第8點
  
  <Button
  
  android:id="@+id/main_btn_down1"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="下載1" />
  
  <Button
  
  android:id="@+id/main_btn_pause1"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="暫停1" />
  
  <Button
  
  android:id="@+id/main_btn_cancel1"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="取消1" />
  
  </LinearLayout>
  
  <LinearLayout
  
  android:layout_width="match_parent"
  
  android:layout_height="wrap_content"
  
  android:orientation="horizontal">
  
  <ProgressBar
  
  android:id="@+id/main_progress2"
  
  style="@style/Widget.AppCompat.ProgressBar.Horizontal"
  
  android:layout_width="0dp"
  
  android:layout_height="match_parent"
  
  android:layout_weight="1" />
  
  <Button
  
  android:id="@+id/main_btn_down2"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="下載2" />
  
  <Button
  
  android:id="@+id/main_btn_pause2"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="暫停2" />
  
  <Button
  
  android:id="@+id/main_btn_cancel2"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="取消2" />
  
  </LinearLayout>
  
  <LinearLayout
  
  android:layout_width="match_parent"
  
  android:layout_height="wrap_content"
  
  android:orientation="horizontal">
  
  <ProgressBar
  
  android:id="@+id/main_progress3"
  
  style="@style/Widget.AppCompat.ProgressBar.Horizontal"
  
  android:layout_width="0dp"
  
  android:layout_height="match_parent"
  
  android:layout_weight="1" />
  
  <Button
  
  android:id="@+id/main_btn_down3"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="下載3" />
  
  <Button
  
  android:id="@+id/main_btn_pause3"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="暫停3" />
  
  <Button
  
  android:id="@+id/main_btn_cancel3"
  
  android:layout_width="wrap_content"
  
  android:layout_height="wrap_content"
  
  android:text="取消3" />
  
  </LinearLayout>
  
  </LinearLayout>
  
  8、進度條樣式
  
  <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
  
  <item android:id="@android:id/background">
  
  <shape>
  
  <corners android:radius="5dip" />
  
  <gradient
  
  android:angle="0"
  
  android:centerColor="#ff5a5d5a"
  
  android:centerY="0.75"
  
  android:endColor="#ff747674"
  
  android:startColor="#ff9d9e9d" />
  
  </shape>
  
  </item>
  
  <item android:id="@android:id/secondaryProgress">
  
  <clip>
  
  <shape>
  
  <corners android:radius="5dip" />
  
  <gradient
  
  android:angle="0"
  
  android:centerColor="#80ffb600"
  
  android:centerY="0.75"
  
  android:endColor="#a0ffcb00"
  
  android:startColor="#80ffd300" />
  
  </shape>
  
  </clip>
  
  </item>
  
  <item android:id="@android:id/progress">
  
  <clip>
  
  <shape>
  
  <corners android:radius="5dip" />
  
  <gradient
  
  android:angle="0"
  
  android:endColor="#8000ff00"
  
  android:startColor="#80ff0000" />
  
  </shape>
  
  </clip>
  
  </item>
  
  </layer-list>