断点续传---多线程下载进阶(二)

流程:

/**
 * 断点续传:就是下载如果暂停后,下次下载时候,继续从上次下载的位置开始下载即可
 * 大致流程:
 * 点击下载后,开始一个service,在onStartCommand中进行
 * InitThread开始分线程进行获取网络资源的大小,获取完毕发送消息MSG_INIT,
 * 进行downLoad,下载前判断,是否下载过,如果么有就初始化
 * threadInfo = new ThreadInfo(0, mFileInfo.getUrl(), 0,
					mFileInfo.getLength(), 0);
	如果又下载就进行获取,
	然后开启分线程进行下载
	new DownloadThread(threadInfo).start();
	下载操作:向数据库插入线程信息,设置下载位置,设置文件写入位置,开始进行下载
	下载时候,通过handler发送广播(传递下载进度值),
	在广播接收器中进行mProgressBar.setProgress(finised);设置进度即可
	下载完毕,发送消息,进行Toast.makeText(mMainActivity, "下载完毕", 0).show();
 *
 */


activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/tv_fileName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="imooc.apk" />

    <ProgressBar
        android:id="@+id/pb_progress"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_fileName" />

     <Button
        android:id="@+id/btn_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/pb_progress"
        android:layout_alignParentRight="true"
        android:text="暂停" />
    
    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/pb_progress"
        android:layout_toLeftOf="@id/btn_stop"
        android:text="下载" />

   
</RelativeLayout>

FileInfo

package com.download.entities;

import java.io.Serializable;

public class FileInfo implements Serializable {
	private int id;
	private String url;
	private String fileName;
	private int length;
	private int finished;

	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 + "]";
	}

}

ThreadInfo

package com.download.entities;

public class ThreadInfo {
	/**
	 * 对应数据库的字段即可
	 */
	private int id;
	private String url;
	private int start;
	private int end;
	private int finished;

	public ThreadInfo() {
	}

	public ThreadInfo(int id, String url, int start, int end, int finished) {
		this.id = id;
		this.url = url;
		this.start = start;
		this.end = end;
		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 getEnd() {
		return end;
	}

	public void setEnd(int end) {
		this.end = end;
	}

	public int getFinished() {
		return finished;
	}

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

	@Override
	public String toString() {
		return "ThreadInfo [id=" + id + ", url=" + url + ", start=" + start
				+ ", end=" + end + ", finished=" + finished + "]";
	}

}

DBHelper

package com.download.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * 数据库帮助类
 */
public class DBHelper extends SQLiteOpenHelper {
	private static final String DB_NAME = "download.db";
	private static final int VERSION = 1;
	//字段:id、thread_id、url、start、end、finished
	private static final String SQL_CREATE = "create table thread_info(_id integer primary key autoincrement,"
			+ "thread_id integer, url text, start integer, end integer, finished integer)";
	private static final String SQL_DROP = "drop table if exists thread_info";

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

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

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

}
ThreadDAO

package com.download.db;

import java.util.List;

import com.download.entities.ThreadInfo;

public interface ThreadDAO {
	/**
	 * 插入线程信息
	 */
	public void insertThread(ThreadInfo threadInfo);

	/**
	 * 删除线程信息
	 */
	public void deleteThread(String url, int thread_id);

	/**
	 * 更新线程下载进度
	 */
	public void updateThread(String url, int thread_id, int finished);

	/**
	 * 查询文件的线程信息
	 */
	public List<ThreadInfo> getThreads(String url);

	/**
	 * 线程信息是否存在
	 */
	public boolean isExists(String url, int thread_id);
}

ThreadDAOImpl

package com.download.db;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.download.entities.ThreadInfo;

/**
 * 数据访问接口实现
 */
public class ThreadDAOImpl implements ThreadDAO {
	private DBHelper mHelper = null;

	public ThreadDAOImpl(Context context) {
		mHelper = new DBHelper(context);
	}

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

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

	@Override
	public void updateThread(String url, int thread_id, int finished) {
		SQLiteDatabase db = mHelper.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> getThreads(String url) {
		List<ThreadInfo> list = new ArrayList<ThreadInfo>();

		SQLiteDatabase db = mHelper.getWritableDatabase();
		Cursor cursor = db.rawQuery("select * from thread_info where url = ?",
				new String[] { url });
		while (cursor.moveToNext()) {
			ThreadInfo 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.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
			threadInfo.setFinished(cursor.getInt(cursor
					.getColumnIndex("finished")));
			list.add(threadInfo);
		}
		cursor.close();
		db.close();
		return list;
	}

	@Override
	public boolean isExists(String url, int thread_id) {
		SQLiteDatabase db = mHelper.getWritableDatabase();
		Cursor cursor = db.rawQuery(
				"select * from thread_info where url = ? and thread_id = ?",
				new String[] { url, thread_id + "" });
		boolean exists = cursor.moveToNext();
		cursor.close();
		db.close();
		return exists;
	}
}

MainActivity

package com.download.app;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.download.entities.FileInfo;
import com.download.services.DownloadService;
import com.imooc.DownLoad.R;

public class MainActivity extends Activity {
	// 进度条
	private ProgressBar mProgressBar = null;
	// 开始
	private Button mStartBtn = null;
	// 暂停
	private Button mStopBtn = null;
	public static MainActivity mMainActivity = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// 初始化控件
		mProgressBar = (ProgressBar) findViewById(R.id.pb_progress);
		mStartBtn = (Button) findViewById(R.id.btn_start);
		mStopBtn = (Button) findViewById(R.id.btn_stop);
		// 将进度条设置为最大
		mProgressBar.setMax(100);
		/**
		 * 参数:(int id, String url, String fileName, int length,int finished)
		 * 
		 */
		final FileInfo fileInfo = new FileInfo(0,
				"http://www.imooc.com/mobile/imooc.apk", "imooc.apk", 0, 0);

		// 添加事件监听
		mStartBtn.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				/**
				 * 通过Intent传递参数给Service
				 */
				Intent intent = new Intent(MainActivity.this,
						DownloadService.class);
				//设置intent的标记
				intent.setAction(DownloadService.ACTION_START);
				intent.putExtra("fileInfo", fileInfo);
				startService(intent);
			}
		});

		mStopBtn.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// 通过Intent传递参数给Service
				Intent intent = new Intent(MainActivity.this,
						DownloadService.class);
				intent.setAction(DownloadService.ACTION_STOP);
				intent.putExtra("fileInfo", fileInfo);
				startService(intent);
			}
		});

		// 注册广播接收器
		IntentFilter filter = new IntentFilter();
		filter.addAction(DownloadService.ACTION_UPDATE);
		registerReceiver(mReceiver, filter);

		mMainActivity = this;
	}

	/**
	 * 更新UI的广播接收器
	 */
	BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			if (DownloadService.ACTION_UPDATE.equals(intent.getAction())) {
				int finised = intent.getIntExtra("finished", 0);
				mProgressBar.setProgress(finised);
			}
		}
	};

	/**
	 * 监听返回键
	 * 
	 * @see android.app.Activity#onKeyUp(int, android.view.KeyEvent)
	 */
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		if (KeyEvent.KEYCODE_BACK == keyCode && mStartBtn != null) // 按了返回键时应暂停下载
		{
			mStopBtn.performClick(); // 模拟按下暂停按钮
		}

		return super.onKeyUp(keyCode, event);
	}

	public Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			Toast.makeText(mMainActivity, "下载完毕", 0).show();
		}
	};

	/**
	 * 退出后,注销广播接收器
	 */
	protected void onDestroy() {
		super.onDestroy();
		unregisterReceiver(mReceiver);
	}

}

DownloadService

package com.download.services;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

import org.apache.http.HttpStatus;

import com.download.entities.FileInfo;

import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

public class DownloadService extends Service {
	
	public static final String ACTION_START = "ACTION_START";
	public static final String ACTION_STOP = "ACTION_STOP";
	public static final String ACTION_UPDATE = "ACTION_UPDATE";
	public static final int MSG_INIT = 0;
	private DownloadTask mTask = null;
	private String TAG = "DownloadService";

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// 获得Activity传过来的参数
		if (ACTION_START.equals(intent.getAction())) {
			//获取intent传递过来的,携带的FileInfo信息
			FileInfo fileInfo = (FileInfo) intent
					.getSerializableExtra("fileInfo");
			// 启动初始化线程
			new InitThread(fileInfo).start();
		} else if (ACTION_STOP.equals(intent.getAction())) {
			FileInfo fileInfo = (FileInfo) intent
					.getSerializableExtra("fileInfo");

			if (mTask != null) {
				mTask.isPause = true;
			}
		}

		return super.onStartCommand(intent, flags, startId);
	}

	private Handler mHandler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case MSG_INIT:
				FileInfo fileInfo = (FileInfo) msg.obj;
				Log.i(TAG, "Init:" + fileInfo);
				// 启动下载任务
				mTask = new DownloadTask(DownloadService.this, fileInfo);
				mTask.downLoad();
				break;

			default:
				break;
			}
		};
	};

	/**
	 * 创建线程进行下载操作
	 *
	 */
	public static final String DOWNLOAD_PATH = Environment
			.getExternalStorageDirectory().getAbsolutePath() + "/downloads/";
	private class InitThread extends Thread {
		private FileInfo mFileInfo = null;

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

		@Override
		public void run() {
			//获取conn
			HttpURLConnection connection = null;
			RandomAccessFile raf = null;

			try {
				// 连接网络文件
				URL url = new URL(mFileInfo.getUrl());
				//打开conn链接
				connection = (HttpURLConnection) url.openConnection();
				connection.setConnectTimeout(5000);
				connection.setRequestMethod("GET");
				int length = -1;

				if (connection.getResponseCode() == HttpStatus.SC_OK) {
					// 获得文件的长度
					length = connection.getContentLength();
				}

				if (length <= 0) {
					return;
				}

				File dir = new File(DOWNLOAD_PATH);
				if (!dir.exists()) {
					dir.mkdir();
				}

				// 在本地创建文件
				File file = new File(dir, mFileInfo.getFileName());
				raf = new RandomAccessFile(file, "rwd");
				// 设置文件长度
				raf.setLength(length);
				mFileInfo.setLength(length);
				//obtainMessage(int what, Object obj),准备完毕发送消息通知进行下载操作
				mHandler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (connection != null) {
					connection.disconnect();
				}
				if (raf != null) {
					try {
						raf.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

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

}

DownloadTask

package com.download.services;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

import org.apache.http.HttpStatus;

import android.R.integer;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import com.download.app.MainActivity;
import com.download.db.ThreadDAO;
import com.download.db.ThreadDAOImpl;
import com.download.entities.FileInfo;
import com.download.entities.ThreadInfo;

public class DownloadTask {
	private Context mContext = null;
	private FileInfo mFileInfo = null;
	private ThreadDAO mDao = null;
	private int mFinised = 0;
	public boolean isPause = false;

	public DownloadTask(Context mContext, FileInfo mFileInfo) {
		this.mContext = mContext;
		this.mFileInfo = mFileInfo;
		mDao = new ThreadDAOImpl(mContext);
	}

	public void downLoad() {
		// 读取数据库的线程信息
		List<ThreadInfo> threads = mDao.getThreads(mFileInfo.getUrl());
		ThreadInfo threadInfo = null;

		if (0 == threads.size()) {
			// 初始化线程信息对象---ThreadInfo(int id, String url, int start, int end, int finished)
			threadInfo = new ThreadInfo(0, mFileInfo.getUrl(), 0,
					mFileInfo.getLength(), 0);
		} else {
			threadInfo = threads.get(0);
		}

		// 创建子线程进行下载
		new DownloadThread(threadInfo).start();
	}

	/**
	 * 下载线程
	 */
	private class DownloadThread extends Thread {
		private ThreadInfo mThreadInfo = null;

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

		@Override
		public void run() {
			// 向数据库插入线程信息
			if (!mDao.isExists(mThreadInfo.getUrl(), mThreadInfo.getId())) {
				mDao.insertThread(mThreadInfo);
			}

			HttpURLConnection connection = null;
			RandomAccessFile raf = null;
			InputStream inputStream = null;

			try {
				URL url = new URL(mThreadInfo.getUrl());
				connection = (HttpURLConnection) url.openConnection();
				connection.setConnectTimeout(5000);
				connection.setRequestMethod("GET");
				// 设置下载位置
				int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
				connection.setRequestProperty("Range", "bytes=" + start + "-"
						+ mThreadInfo.getEnd());
				// 设置文件写入位置
				File file = new File(DownloadService.DOWNLOAD_PATH,
						mFileInfo.getFileName());
				raf = new RandomAccessFile(file, "rwd");
				raf.seek(start);
				Intent intent = new Intent();
				intent.setAction(DownloadService.ACTION_UPDATE);
				mFinised += mThreadInfo.getFinished();
				// 开始下载
				if (connection.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
					// 读取数据
					inputStream = connection.getInputStream();
					byte buf[] = new byte[1024 << 2];
					int len = -1;
					long time = System.currentTimeMillis();
					while ((len = inputStream.read(buf)) != -1) {
						// 写入文件
						raf.write(buf, 0, len);
						// 把下载进度发送广播给Activity
						mFinised += len;
						if (System.currentTimeMillis() - time > 500) {
							time = System.currentTimeMillis();
							intent.putExtra("finished", mFinised * 100
									/ mThreadInfo.getEnd());
							//发送广播
							mContext.sendBroadcast(intent);
						}

						// 在下载暂停时,保存下载进度
						if (isPause) {
							mDao.updateThread(mThreadInfo.getUrl(),
									mThreadInfo.getId(), mFinised);
							return;
						}
					}

					// 删除线程信息
					mDao.deleteThread(mThreadInfo.getUrl(), mThreadInfo.getId());
					Log.i("DownloadTask", "下载完毕");
					//发送消息,通知下载完毕即可
					MainActivity.mMainActivity.handler.sendEmptyMessage(0);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (connection != null) {
						connection.disconnect();
					}
					if (raf != null) {
						raf.close();
					}
					if (inputStream != null) {
						inputStream.close();
					}
				} catch (Exception e2) {
					e2.printStackTrace();
				}
			}
		}
	}
}



你可能感兴趣的:(断点续传---多线程下载进阶(二))