PS:本文代码来自慕课网同名课程Android-Service系列之断点续传下载,这里只是为了Mark下来方便自己以后使用。
首先是布局文件,因为只是demo,所以使用图形化工具生成。
<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="com.tk.download.MainActivity" >
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<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"
android:layout_marginTop="36dp"
android:layout_toRightOf="@+id/tv" />
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/pb_progress"
android:layout_marginTop="31dp"
android:layout_alignParentLeft="true"
android:text="start" />
<Button
android:id="@+id/btn_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/btn_start"
android:layout_alignBottom="@+id/btn_start"
android:layout_alignParentRight="true"
android:text="stop" />
</RelativeLayout>
MainActivity.java
public class MainActivity extends Activity implements OnClickListener{
private TextView mTvFileName;
private ProgressBar mProgressBar;
private Button btnStart;
private Button btnStop;
private final FileInfo fileInfo = new FileInfo(0,"http://down.qingkan.net/32/32033.txt","翡翠之塔.txt",0,0);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init(){
mTvFileName = (TextView) findViewById(R.id.tv);
mProgressBar = (ProgressBar) findViewById(R.id.pb_progress);
btnStart = (Button) findViewById(R.id.btn_start);
btnStop = (Button) findViewById(R.id.btn_stop);
mProgressBar.setMax(100);
btnStart.setOnClickListener(this);
btnStop.setOnClickListener(this);
IntentFilter filter = new IntentFilter();
filter.addAction(DownloadService.ACTION_UPDATE);
registerReceiver(mReceiver,filter);
}
@Override
protected void onDestroy(){
super.onDestroy();
unregisterReceiver(mReceiver);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* 更新ui的广播接收器
*/
BroadcastReceiver mReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
if(DownloadService.ACTION_UPDATE.equals(intent.getAction())){
int finished = intent.getIntExtra("finished",0);
mProgressBar.setProgress(finished);
}
}
};
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btn_start:
Intent intent = new Intent(MainActivity.this, DownloadService.class);
intent.setAction( DownloadService.ACTION_START);
intent.putExtra("fileInfo", fileInfo);
startService(intent);
break;
case R.id.btn_stop:
Intent mIntent = new Intent(MainActivity.this, DownloadService.class);
mIntent.setAction( DownloadService.ACTION_STOP);
mIntent.putExtra("fileInfo", fileInfo);
startService(mIntent);
break;
}
}
}
数据库相关代码
DBHelper.java
/**
* 创建数据库语句
*
*
*/
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "download.db";
private static final int VERSION = 1;
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);
// TODO Auto-generated constructor stub
}
@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);
}
}
DAO相关
public interface ThreadDAO {
/**
* 插入线程信息
*
* @param threadInfo
*/
public void insertThread(ThreadInfo threadInfo);
/**
* 删除线程
*
* @param url
* @param threadId
*/
public void deleteThread(String url, int threadId);
/**
* 更新线程
*
* @param url
* @param threadId
*/
public void updateThread(String url, int threadId,int finished);
/**
* 查找线程
*
* @param url
* @return
*/
public List<ThreadInfo> getThreads(String url);
/**
* 线程消息是否存在
* @param url
* @param threadId
* @return
*/
public boolean isExtists(String url,int threadId);
}
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.getEnd()});
db.close();
}
@Override
public void deleteThread(String url, int threadId) {
SQLiteDatabase db = mHelper.getWritableDatabase();
db.execSQL("delete from thread_info where url=? and thread_id=?",
new Object[]{url,threadId});
db.close();
}
@Override
public void updateThread(String url, int threadId,int finished) {
SQLiteDatabase db = mHelper.getWritableDatabase();
db.execSQL("update thread_info set finished=? where url=? and thread_id=?",
new Object[]{finished,url,threadId});
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 thread = new ThreadInfo();
thread.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
thread.setUrl(cursor.getString(cursor.getColumnIndex("url")));
thread.setStart(cursor.getInt(cursor.getColumnIndex("start")));
thread.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
thread.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));
list.add(thread);
}
cursor.close();
db.close();
return list;
}
@Override
public boolean isExtists(String url, int threadId) {
SQLiteDatabase db = mHelper.getWritableDatabase();
Cursor cursor = db.rawQuery("select * from thread_info where url=? and thread_id=?", new String[]{url,threadId+""});
boolean extists = cursor.moveToNext();
cursor.close();
db.close();
return extists;
}
}
实体类两个
public class ThreadInfo implements Serializable{
private int id;
private String url;
private int start;
private int end;//文件长度
private int finished;//任务中断时的进度
public ThreadInfo() {
super();
}
public ThreadInfo(int id, String url, int start, int end, int finished) {
super();
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 + "]";
}
}
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) {
super();
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 DownloadService extends Service {
private final String tag = getClass().getName();
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 String DOWNLOAD_PATH =
Environment.getExternalStorageDirectory().getAbsolutePath()+"/mDownLoads/";
public static final int MSG_INIT = 0;
private DownloadTask task = null;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//获得Activity传来的参数
if(ACTION_START.equals(intent.getAction())){
FileInfo fileInfo = (FileInfo)intent.getSerializableExtra("fileInfo");
Log.i(tag,"Start+"+fileInfo.toString());
new InitThread(fileInfo).start();
}else if(ACTION_STOP.equals(intent.getAction())){
FileInfo fileInfo = (FileInfo)intent.getSerializableExtra("fileInfo");
Log.i(tag,"Stop+"+fileInfo.toString());
if(task!=null){
task.setPause(true);
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
Handler mHandler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){
case MSG_INIT:
FileInfo fileInfo = (FileInfo)msg.obj;
Log.i(tag,"INIT"+fileInfo.toString());
//启动下载任务
task = new DownloadTask(DownloadService.this,fileInfo);
task.download();
break;
}
}
};
/**
* 初始化子线程
*/
class InitThread extends Thread{
private FileInfo fileInfo;
public InitThread(FileInfo fileInfo) {
super();
this.fileInfo = fileInfo;
}
public void run(){
HttpURLConnection coon = null;
RandomAccessFile raf = null;
try{
//连接网络文件
URL url = new URL(fileInfo.getUrl());
coon = (HttpURLConnection)url.openConnection();
coon.setConnectTimeout(3000);
coon.setRequestMethod("GET");
int length = -1;
if (coon.getResponseCode()==HttpStatus.SC_OK){
//获取文件长度
length = coon.getContentLength();
}
if (length<0){
return;
}
File dir = new File(DOWNLOAD_PATH);
if(!dir.exists()){
dir.mkdir();
}
//本地创建文件
File file = new File(dir,fileInfo.getFileName());
raf = new RandomAccessFile(file,"rwd");
//设置文件长度
raf.setLength(length);
fileInfo.setLength(length);
//利用handler将信息从线程中回传给service
mHandler.obtainMessage(MSG_INIT,fileInfo).sendToTarget();
}catch(Exception e){
e.printStackTrace();
}finally{
try {
coon.disconnect();
raf.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class DownloadTask {
private Context mContext = null;
private FileInfo mFileInfo = null;
private ThreadDAO mDao = null;
private int mFinished = 0;
private boolean isPause = false;
public DownloadTask(Context mContext, FileInfo mFileInfo) {
super();
this.mContext = mContext;
this.mFileInfo = mFileInfo;
mDao = new ThreadDAOImpl(mContext);
}
public void setPause(boolean isPause) {
this.isPause = isPause;
}
public void download(){
//读取数据库线程信息
List<ThreadInfo> threadInfos = mDao.getThreads(mFileInfo.getUrl());
ThreadInfo threadInfo = null;
if(threadInfos.size()==0){
//初始化线程信息对象
threadInfo = new ThreadInfo(0,mFileInfo.getUrl(),0,mFileInfo.getLength(),0);
}else{
threadInfo = threadInfos.get(0);
}
//创建子线程下载
new DownloadThread(threadInfo).start();
}
class DownloadThread extends Thread{
private ThreadInfo mThreadInfo = null;
public DownloadThread(ThreadInfo mThreadInfo) {
super();
this.mThreadInfo = mThreadInfo;
}
public void run(){
//数据库插入线程信息
if(!mDao.isExtists(mThreadInfo.getUrl(),mThreadInfo.getId())){
mDao.insertThread(mThreadInfo);
}
HttpURLConnection conn = null;
RandomAccessFile raf = null;
InputStream input = null;
try {
URL url = new URL(mThreadInfo.getUrl());
conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
//设置下载位置
int start = mThreadInfo.getStart()+mThreadInfo.getFinished();
conn.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(DownloadService.ACTION_UPDATE);
mFinished+=mThreadInfo.getFinished();
//开始下载,返回值206
if(conn.getResponseCode()==HttpStatus.SC_PARTIAL_CONTENT){
//读取数据
input = conn.getInputStream();
byte[] bytes = new byte[1024];
int len = -1;
long time = System.currentTimeMillis();//减慢进度条刷新时间,减少ui负载
while((len = input.read(bytes))!=-1){
//写入文件
raf.write(bytes,0,len);
//把下载进度发送广播给Activity
mFinished += len;
if(System.currentTimeMillis()-time>500){
time = System.currentTimeMillis();
intent.putExtra("finished", mFinished*100/mFileInfo.getLength());
mContext.sendBroadcast(intent);
}
//下载暂停时,保存下载进度
if(isPause){
mDao.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mFinished);
return;
}
}
//删除数据库中的线程信息
mDao.deleteThread(mThreadInfo.getUrl(), mThreadInfo.getId());
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {conn.disconnect();
input.close();
raf.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}