转载请声明:http://write.blog.youkuaiyun.com/postedit
基本思路大概如下:
1.如果需要支持断点,需要将下载的进度保存到数据库中,以便下次从该断点处向服务器请求下载文件的起点。
2.既然要用到数据库,那就少不了要建立一个实体类。
3.多任务下载当中,每一个任务为一个线程,它需要独立响应暂停、继续等按钮的单击事件,暂停时这个线程需要wait,便需要考虑到同步问题。
4.下载时需要实时更新进度,这就需要使用到handler,在handler的handlerMessage方法中更新进度条。
代码如下:
1.创建一个Helper类DBOpenHelper.class 这个类只是创建数据库的帮助类。
- public class DBOpenHelper extends SQLiteOpenHelper{
- public DBOpenHelper(Context context) {
- super(context, "download.db", null, 1);
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- // TODO Auto-generated method stub
- 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) {
- // TODO Auto-generated method stub
- }
- }
2. 创建实体类Info
- public class Info {
- private String path;
- private int thid;
- private Long done;
- public Info(String path, int thid, Long i) {
- this.path = path;
- this.thid = thid;
- this.done = i;
- }
- public String getPath() {
- return path;
- }
- public void setPath(String path) {
- this.path = path;
- }
- public int getThid() {
- return thid;
- }
- public void setThid(int thid) {
- this.thid = thid;
- }
- public Long getDone() {
- return done;
- }
- public void setDone(Long done) {
- this.done = done;
- }
- }
3.创建操作数据库的DAO类
- import java.util.ArrayList;
- import java.util.List;
- import android.content.Context;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteDatabase;
- public class InfoDao {
- private static DBOpenHelper helper;
- public InfoDao(Context context) {
- if(helper == null)
- 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 this=?", 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.getLong(2));
- c.close();
- return info;
- }
- public void deleteAll(String path, Long fileLen) {
- 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;
- }
- public void deleteUndone(String path) {
- SQLiteDatabase db = helper.getWritableDatabase();
- db.execSQL("DELETE FROM info WHERE path=? ", new Object[] { path });
- }
- }
在上面的构造函数中:
- private static DBOpenHelper helper;
- public InfoDao(Context context) {
- if(helper == null)
- helper = new DBOpenHelper(context);
- }
是为了在多线程操作数据库时容易出现死锁。
3.创建下载文件的类:
- public class Downloader {
- private Long done=0l;
- private InfoDao dao;
- private Long fileLen;
- private Handler handler;
- private boolean isPause = false;
- private Context context;
- public Downloader(Context context, Handler handler) {
- this.context = context;
- dao = new InfoDao(context);
- System.out.println(dao);
- this.handler = handler;
- }
- public void download(String path, int thCount) throws Exception {
- URL url = new URL(path);
- HttpClient httpClient = new DefaultHttpClient();//下面几段代码是为了连接服务器
- HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), 20 * 1000);
- HttpPost post = new HttpPost(path);
- HttpResponse response = httpClient.execute(post);
- if(response.getStatusLine().getStatusCode()==200 ){//如果返回200表明连接成功
- fileLen = response.getEntity().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();
- Message msg = new Message();
- msg.what = 0;
- msg.getData().putLong("fileLen", fileLen);
- handler.sendMessage(msg);//发送一个空的消息,将文件的长度传递给进度条.
- Long 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 Long partLen;
- private int id;
- public DownloadThread(URL url, File file, Long partLen, int id) {
- this.url = url;
- this.file = file;
- this.partLen = partLen;
- this.id = id;
- }
- @Override
- public void run() {
- // TODO Auto-generated method stub
- Info info = dao.query(url.toString(), id);//查询记录当中没有下完的任务
- if(info!=null){
- done = info.getDone();
- }else{
- info = new Info(url.toString(),id,0l);
- dao.insert(info);
- }
- Long start = info.getDone();// 开始位置 = 已下载量
- Long end = partLen-1;
- try{
- IAddressTask task = new IAddressTask(context);
- HttpClient httpClient = new DefaultHttpClient();
- HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), 20 * 1000);
- HttpPost post = new HttpPost(url.toString());
- post.setHeader("Range", "bytes=" + start + "-" + end); //为上次没有下载完成的任务请求下载的起始位置
- HttpResponse response = httpClient.execute(post);
- RandomAccessFile raf = new RandomAccessFile(file, "rws");
- raf.seek(start);
- InputStream in = response.getEntity().getContent();//获取输入流,写入文件
- byte[] buf = new byte[1024*10];
- int len;
- while((len = in.read(buf))!=-1){
- raf.write(buf,0,len);
- done+=len;
- info.setDone(done);
- dao.update(info);
- Message msg = new Message();
- msg.what = 1;
- msg.getData().putLong("done", done);
- handler.sendMessage(msg); //每次读取一定长度的文件内容后,更新进度条的进度
- if(isPause){
- synchronized (dao) {
- try{
- dao.wait();//暂停时该线程进入等待状态,并释放dao的锁
- httpClient = new DefaultHttpClient();//重新连接服务器,在wait时可能丢失了连接,如果不加这一段代码会出现connection。。。。。peer的错误
- HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), 20 * 1000);
- post = new HttpPost(url.toString());
- post.setHeader("Range", "bytes=" + done + "-" + end);
- response = httpClient.execute(post);
- raf.seek(done);
- in = response.getEntity().getContent();
- }catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
- in.close();
- raf.close();
- // 删除下载记录
- dao.deleteAll(info.getPath(), fileLen);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- //暂停下载
- public void pause() {
- isPause = true;
- }
- //继续下载
- public void resumeDownload() {
- isPause = false;
- //恢复所有线程
- synchronized (dao) {
- dao.notifyAll();
- }
- }
- //删除下载
- public void delete(String path) {
- isPause = true;
- synchronized (dao) {
- dao.deleteUndone(path) ;
- }
- }
- }
5.创建activity:
- public class DownloadActivity extends Activity{
- private LinearLayout rootLinearLayout;
- private EditText pathEditText;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.downloadmain);
- 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);
- }
- }
- public void download(View view) { //系在按钮的单击事件
- final String path = pathEditText.getText().toString();
- final File file = new File(Environment.getExternalStorageDirectory(),path.substring(path.lastIndexOf("/") + 1));
- if(file.exists()){
- new AlertDialog.Builder(DownloadActivity.this)
- .setTitle("文件下载")
- .setPositiveButton("确定", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- file.delete();
- createDownload(path);
- }
- })
- .setNegativeButton("取消", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- })
- .setMessage("文件已存在,确定要重新下载吗?")
- .create()
- .show();
- }else createDownload(path);
- }
- private void createDownload(String path) { //为每一个任务创建一个下载线程,并分配进度条、按钮等
- LayoutInflater inflater = LayoutInflater.from(this);
- LinearLayout linearLayout = (LinearLayout) inflater.inflate(R.layout.download2, null);
- LinearLayout childLinearLayout = (LinearLayout)linearLayout.findViewById(R.id.linearpro);
- ProgressBar progressBar = (ProgressBar) linearLayout.findViewById(R.id.downloadpro);
- TextView textView = (TextView) linearLayout. findViewById(R.id.downloadper);
- Button button = (Button) linearLayout.findViewById(R.id.pause);
- Button delbutton = (Button) linearLayout.findViewById(R.id.delete);
- try {
- MyListener listener = new MyListener(progressBar, textView, path);//为每一个任务创建一个监听对象
- button.setOnClickListener(listener);
- delbutton.setOnClickListener(listener);
- //调用当前页面中某个容器的addView,将新创建的View添加进来
- rootLinearLayout.addView(linearLayout);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private final class MyListener implements OnClickListener {
- private ProgressBar progressBar;
- private TextView textView;
- private Long fileLen;
- private Downloader downloader;
- private String name;
- private String path;
- public MyListener(ProgressBar progressBar, TextView textView, String path) {
- this.progressBar = progressBar;
- this.textView = textView;
- this.path = path;
- name = path.substring(path.lastIndexOf("/") + 1);
- downloader = new Downloader(getApplicationContext(), handler);
- handler.sendEmptyMessage(-1);//这里发送消息是为了防止因为连接服务器引起界面无响应
- }
- //Handler传输数据
- private Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case -1:
- try {
- downloader.download(path, 1); //开始下载文件
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(getApplicationContext(), "下载过程中出现异常", 0).show();
- }
- break;
- case 0:
- //获取文件的大小
- fileLen = msg.getData().getLong("fileLen");
- //设置进度条最大刻度:setMax()
- progressBar.setMax(100);
- break;
- case 1:
- //获取当前下载的总量
- Long done = msg.getData().getLong("done");
- //当前进度的百分比
- textView.setText(name + "\t" + done * 100 / fileLen + "%");
- //进度条设置当前进度:setProgress()
- progressBar.setProgress((int)(done * 100 / fileLen));
- if (done.equals(fileLen)) {
- Toast.makeText(getApplicationContext(), name + " 下载完成", 0).show();
- //下载完成后退出进度条
- rootLinearLayout.removeView((View) progressBar.getParent().getParent());
- }
- break;
- case 3:rootLinearLayout.removeView((View) progressBar.getParent().getParent());
- break;
- }
- }
- };
- /**
- * 暂停和继续下载
- */
- public void onClick(View v) {
- if(v.getId()==R.id.pause){
- Button pauseButton = (Button) v;
- if ("||".equals(pauseButton.getText())) {
- downloader.pause();
- pauseButton.setText("▶");
- } else {
- downloader.resumeDownload();
- pauseButton.setText("||");
- }
- }else if(v.getId()==R.id.delete){
- File file = new File(Environment.getExternalStorageDirectory(),path.substring(path.lastIndexOf("/") + 1));
- if(file.exists())file.delete();
- downloader.delete(path);
- handler.sendEmptyMessage(3);
- }
- }
- }
- }
6.布局文件:downloadmain.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:text="http://192.168.0.232:8080/dy/ThinkinJava.pdf"
- 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>
download2.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:id="@+id/linearpro"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1">
- <ProgressBar android:id="@+id/downloadpro"
- android:layout_width="fill_parent"
- android:layout_height="20dp"
- style="?android:attr/progressBarStyleHorizontal"/>
- <TextView android:id="@+id/downloadper"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="0%"/>
- </LinearLayout>
- <Button android:id="@+id/pause"
- android:layout_width="40dip"
- android:layout_height="40dip"
- android:text="||"/>
- <Button android:id="@+id/delete"
- android:layout_width="40dip"
- android:layout_height="40dip"
- android:text="X" />
- </LinearLayout>