Android 多线程断点续传下载

1:前言

在Android应用开发中,下载功能应该用的很多,网上也有很多的开源框架,用起来很方便。但我们也要自己熟悉下载技术,今天就分享一个多线程断点续传下载的demo.

2:技术要点和原理

断点续传的原理大家应该都很清楚,就是利用数据库保存各个线程的下载信息,现在前查询线程信息,现在完删除 线程信息。注意多线程的时候,数据库一定使用单例模式。

3:写好线程和文件实体类,文件要通过intent传递,所以要序列化

public class FileInfo implements Serializable {

    private int id;
    private String url;
    private String fileName;
    private int length;
    private int finished;

    public FileInfo() {
    }

    public FileInfo(int id, String url, String fileName, int length, int finished) {
        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;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public int getFinished() {
        return finished;
    }

    public void setFinished(int finished) {
        this.finished = finished;
    }

    @Override
    public String toString() {
        return "fileInfo: id=" + id + " url=" + url + " fileName=" + fileName + " length=" + length + " finished=" + finished;
    }
}
线程信息

public class ThreadInfo {

    private int id;
    private String url;
    private int start;
    private int ended;
    private long finished;

    public ThreadInfo() {
    }

    public ThreadInfo(int id, String url, int start, int ended, long finished) {
        this.id = id;
        this.url = url;
        this.start = start;
        this.ended = ended;
        this.finished = finished;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getEnded() {
        return ended;
    }

    public void setEnded(int ended) {
        this.ended = ended;
    }

    public long getFinished() {
        return finished;
    }

    public void setFinished(int finished) {
        this.finished = finished;
    }

    @Override
    public String toString() {
        return "threadInfo: id=" + id + " url=" + url + " start=" + start + " ended=" + ended + " finished=" + finished;
    }
}

4:下载类(防止线程开销过大,利用java的线程池技术)

public class DownloadTask {
   private Context mContext;
   private FileInfo mFileInfo;
   private ThreadDAOImpl threadDAO;
   private long mFinished = 0;
   public boolean isPause = false;
   private int mThreadCount = 1;
   private List<DownloadThread> mThreadList = null;

   public static ExecutorService mExecutorService = Executors.newCachedThreadPool();

   public DownloadTask(Context context, FileInfo fileInfo, int mThreadCount) {
      this.mContext = context;
      this.mFileInfo = fileInfo;
      this.threadDAO = new ThreadDAOImpl(context);
      this.mThreadCount = mThreadCount;
   }

   public void download() {
      List<ThreadInfo> threads = threadDAO.getThread(mFileInfo.getUrl());
      if (threads.size() == 0) {
         int length = mFileInfo.getLength() / mThreadCount;
         for (int i = 0; i < mThreadCount; i++) {
            ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), i * length, (i + 1) * length - 1, 0);
            if (i == mThreadCount + 1) {
               threadInfo.setEnded(mFileInfo.getLength());
            }
            threads.add(threadInfo);
            threadDAO.insertThread(threadInfo);
         }
      }
      mThreadList = new ArrayList<DownloadThread>();
      for (ThreadInfo thread : threads) {
         DownloadThread downThread = new DownloadThread(thread);
         DownloadTask.mExecutorService.execute(downThread);
         mThreadList.add(downThread);
      }


   }

   private synchronized void checkAllThreadsFinished() {
      boolean allFinished = true;
      for (DownloadThread thread : mThreadList) {
         if (!thread.isFinished) {
            allFinished = false;
            break;
         }
      }
      if (allFinished) {
         threadDAO.deleteThread(mFileInfo.getUrl());
         Intent intent = new Intent(DownloadService.ACTION_FINISH);
         intent.putExtra("fileInfo", mFileInfo);
         mContext.sendBroadcast(intent);
      }
   }

   class DownloadThread extends Thread {
      private ThreadInfo mThreadInfo;
      public boolean isFinished = false;

      public DownloadThread(ThreadInfo threadInfo) {
         this.mThreadInfo = threadInfo;
      }

      @Override
      public void run() {
         HttpURLConnection connection = null;
         RandomAccessFile raf = null;
         InputStream inputStream = null;

         try {
            Intent intent = new Intent(DownloadService.ACTION_UPDATE);
            URL url = new URL(mThreadInfo.getUrl());
            connection = (HttpURLConnection) url.openConnection();
            connection.setConnectTimeout(5 * 1000);
            connection.setRequestMethod("GET");
            long start = mThreadInfo.getStart() + mThreadInfo.getFinished();
            connection.setRequestProperty("Range", "bytes=" + start + "-" + mThreadInfo.getEnded());
            File file = new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName());
            raf = new RandomAccessFile(file, "rwd");
            raf.seek(start);
            long time = System.currentTimeMillis();
            mFinished += mThreadInfo.getFinished();
            if (connection.getResponseCode() == 206) {
               inputStream = connection.getInputStream();
               byte[] buffer = new byte[1024 * 4];
               int length = 0;
               int totalLength = mFileInfo.getLength();
               while ((length = inputStream.read(buffer)) != -1) {
                  raf.write(buffer, 0, length);
                  mFinished += length;
                  mThreadInfo.setFinished((int) mThreadInfo.getFinished() + length);
                  if (System.currentTimeMillis() - time > 1000) {
                     time = System.currentTimeMillis();
                     int progress = (int) (mFinished * 100 / totalLength);
                     intent.putExtra("finished", progress);
                     intent.putExtra("id", mFileInfo.getId());
                   
                     if (progress > 0)
                        mContext.sendBroadcast(intent);
                  }

                  if (isPause) {
                     threadDAO.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mThreadInfo.getFinished());
                     return;
                  }
               }
            }

            isFinished = true;
            checkAllThreadsFinished();
         } catch (Exception e) {
            e.printStackTrace();
            Log.e("m_tag", "下载出现异常:" + e.toString());
         } finally {
            try {
               connection.disconnect();
               inputStream.close();
               raf.close();
            } catch (Exception e) {
               e.printStackTrace();
            }
         }

      }
   }

}
5:数据库的创建和增删改查(需要用到单利模式)

public class DBHelper extends SQLiteOpenHelper {
   private static DBHelper mHelper;
   private static final String DB_NAME = "download.db";
   private static final String CREATE_TABLE = "create table thread_info(_id integer primary key autoincrement, thread_id integer, url text, start integer, ended integer, finished integer)";
   private static final String DROP_TABLE = "drop table if exists thread_info";
   private static final int VERSION = 1;

   private DBHelper(Context context) {
      super(context, DB_NAME, null, VERSION);
   }

   @Override
   public void onCreate(SQLiteDatabase db) {
      db.execSQL(CREATE_TABLE);
   }

   @Override
   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      db.execSQL(DROP_TABLE);
      db.execSQL(CREATE_TABLE);
   }

   public synchronized static DBHelper getDBHelper(Context context) {
      if (mHelper == null){
         mHelper = new DBHelper(context);
      }
      return mHelper;
   }
}
public class ThreadDAOImpl implements ThreadDAO {
   private DBHelper mDbHelper;

   public ThreadDAOImpl(Context context) {
      mDbHelper = DBHelper.getDBHelper(context);
   }

   @Override
   public synchronized void insertThread(ThreadInfo threadInfo) {
      SQLiteDatabase db = mDbHelper.getWritableDatabase();
      db.execSQL("insert into thread_info(thread_id, url, start, ended, finished) values(?, ?, ?, ?, ?)",
            new Object[]{threadInfo.getId(), threadInfo.getUrl(), threadInfo.getStart(), threadInfo.getEnded(), threadInfo.getFinished()});
      db.close();
   }

   @Override
   public synchronized void deleteThread(String url) {
      SQLiteDatabase db = mDbHelper.getWritableDatabase();
      db.execSQL("delete from thread_info where url = ?", new Object[]{url});
      db.close();
   }

   @Override
   public synchronized void updateThread(String url, int thread_id, long finished) {
      SQLiteDatabase db = mDbHelper.getWritableDatabase();
      db.execSQL("update thread_info set finished = ? where url = ? and thread_id = ?", new Object[]{finished, url, thread_id});
      db.close();
   }

   @Override
   public List<ThreadInfo> getThread(String url) {
      List<ThreadInfo> mList = new ArrayList<ThreadInfo>();
      SQLiteDatabase db = mDbHelper.getReadableDatabase();
      Cursor cursor = db.rawQuery("select * from thread_info where url = ?", new String[]{url});
      ThreadInfo threadInfo;
      while (cursor.moveToNext()) {
         threadInfo = new ThreadInfo();
         threadInfo.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
         threadInfo.setUrl(cursor.getString(cursor.getColumnIndex("url")));
         threadInfo.setStart(cursor.getInt(cursor.getColumnIndex("start")));
         threadInfo.setEnded(cursor.getInt(cursor.getColumnIndex("ended")));
         threadInfo.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));
         mList.add(threadInfo);
      }
      cursor.close();
      db.close();
      return mList;
   }

   @Override
   public boolean isExists(String url, int thread_id) {
      boolean exists = false;
      SQLiteDatabase db = mDbHelper.getWritableDatabase();
      Cursor cursor = db.rawQuery("select * from thread_info where url = ? and thread_id = ?", new String[]{url, thread_id + ""});
      if (cursor.getCount() > 0) {
         exists = true;
      }
      cursor.close();
      db.close();
      return exists;
   }
}
6:下载服务

public class DownloadService extends Service implements DownloadTask.ProgressChangeListener {

   public static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/downloads";
   public static final String ACTION_UPDATE = "action_update";
   public static final String ACTION_START = "action_start";
   public static final String ACTION_STOP = "action_stop";
   public static final String ACTION_FINISH = "action_finish";
   private static final int INIT_ACTION = 0;
   private WorkHandler mHandler = new WorkHandler();
   private Map<Integer, DownloadTask> mTasks = new LinkedHashMap<Integer, DownloadTask>();

   @Override
   public void onCreate() {
      super.onCreate();
   }


   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
      Log.e("m_tag", "onStartCommand" + " intent=" + intent.getDataString());
      String action = intent.getAction();
      if (action.equals(ACTION_START)) {
         FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
//       new InitThread(fileInfo).start();
         DownloadTask.mExecutorService.execute(new InitThread(fileInfo));
         Log.e("m_tag", "start fileInfo:" + fileInfo.toString());
      } else if (action.equals(ACTION_STOP)) {
         FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
         Log.e("m_tag", "stop fileInfo:" + fileInfo.toString());

         DownloadTask task = mTasks.get(fileInfo.getId());
         if (task != null){
            task.isPause = true;
         }


      }
      return Service.START_NOT_STICKY;
   }

   @Nullable
   @Override
   public IBinder onBind(Intent intent) {
      return null;
   }

   @Override
   public void progressChange(String name, int progress) {
      Log.e("m_download", "name=" + name + " progress=" + progress);
   }


   class InitThread extends Thread {
      private FileInfo mFileInfo;

      public InitThread(FileInfo fileInfo) {
         this.mFileInfo = fileInfo;
      }

      @Override
      public void run() {
         super.run();
         HttpURLConnection connection = null;
         RandomAccessFile raf = null;
         try {
            URL url = new URL(mFileInfo.getUrl());
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(5 * 1000);
            int length = -1;
            if (connection.getResponseCode() == 200) {
               length = connection.getContentLength();
            }
            if (length <= 0) {
               return;
            }
            File dir = new File(DOWNLOAD_PATH);
            if (!dir.exists()) {
               dir.mkdir();
               Log.e("m_tag", "文件目录不存在");
            }
            File file = new File(dir, mFileInfo.getFileName());
            raf = new RandomAccessFile(file, "rwd");
            raf.setLength(length);
            mFileInfo.setLength(length);
            mHandler.obtainMessage(INIT_ACTION, mFileInfo).sendToTarget();

         } catch (Exception e) {
            Log.e("m_tag", "ex=" + e.toString());
         } finally {
            try {
               if (raf != null)
                  raf.close();
               connection.disconnect();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
      }
   }


   private class WorkHandler extends Handler {
      @Override
      public void handleMessage(Message msg) {
         super.handleMessage(msg);
         switch (msg.what) {
            case INIT_ACTION:
               FileInfo fileInfo = (FileInfo) msg.obj;
               Log.e("m_tag", "init fileInfo:" + fileInfo.toString());
               DownloadTask task = new DownloadTask(DownloadService.this, fileInfo, 3);
               task.download();
               mTasks.put(fileInfo.getId(), task);
               break;
         }
      }
   }

   @Override
   public void onDestroy() {
      super.onDestroy();
      if (mHandler != null)
         mHandler.removeCallbacksAndMessages(null);
   }


}
7:总结

上面只是一部分代码,用到的知识点都是android的基础知识,有写的不合理的地方欢迎指正源码传送门

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值