一、练习项目概述
此项目实现多线程下载多个文件,涉及到的知识点有ListView的使用,通知的使用,数据存储,网络连接,数据库的读写,Service的使用,广播的使用,多线程,handler等的使用。
项目流程中数据存储与传递图如下:
2、过程说明
过程1:Activity将保存文件的List数据传递给适配器显示在界面上。
过程2:用户点击下载,将下载文件的数据传递给DownLoadService。
过程3:DownLoadService将收到的指令的文件信息传递给DownLoadTask。
过程4:将文件下载的进度通过广播的发送给Activity,更新UI。
过程5:当用户点击停止时,修改对应下载任务中的所有下载线程读写控制符,并将线程信息保存到数据库。
过程6:当用户点击开始下载时,如果当前下载任务不存在时,读取数据库不存在线程信息时,初始化下载线程,并保存到下载任务和数据库中。
3、编码逻辑
1、界面与Bean类
2、DownloadService类
3、InitThread类
4、DBhelper类
5、Dao接口
6、DaoImpl类
7、DownLoadTask类
8、DownLoadThread类
9、完善Activity类
10、实现通知
11、测试
4、代码
MianActivity类
package app.kuxiao.com.myapplication;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import adapter.FileAdapter;
import bean.FileInfo;
import service.DownloadService;
public class MainActivity extends AppCompatActivity {
private ListView mListView = null;
private List<FileInfo> fileInfos =null;
private FileAdapter fileAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
mListView = (ListView) findViewById(R.id.id_lv_file);
fileInfos = new ArrayList<>();
FileInfo fileInfo = new FileInfo(0,0,0,0,"https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk",0,"mobileqq_android.apk",3);
FileInfo fileInfo1 = new FileInfo(1,0,0,0,"https://itunes.apple.com/cn/app/qq-2011/id444934666?mt=8",0,"qq-2011",3);
FileInfo fileInfo2 = new FileInfo(2,0,0,0,"http://dlglobal.qq.com/weixin/Windows/WeChat_C1018.exe",0,"WeChat_C1018.exe",3);
FileInfo fileInfo3 = new FileInfo(3,0,0,0,"http://www.imooc.com/mobile/mukewang.apk",0,"mukewang.apk",3);
fileInfos.add(fileInfo);
fileInfos.add(fileInfo1);
fileInfos.add(fileInfo2);
fileInfos.add(fileInfo3);
IntentFilter intentFilter = new IntentFilter(DownloadService.DOWNLOAD_UPDATE);
intentFilter.addAction(DownloadService.DOWNLOAD_FINISHED);
registerReceiver(receiver, intentFilter);
fileAdapter = new FileAdapter(fileInfos,this);
mListView.setAdapter(fileAdapter);
}
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DownloadService.DOWNLOAD_UPDATE))
{
int taskKey = intent.getIntExtra("taskKey",4);
int finised = (int)intent.getLongExtra("finished",0);
fileAdapter.updateProgress(taskKey,finised);
}else if(intent.getAction().equals(DownloadService.DOWNLOAD_FINISHED))
{
int key = intent.getIntExtra("taskKey",4);
FileInfo info = fileInfos.get(key);
Intent intent1 = new Intent(context, DownloadService.class);
intent1.setAction(DownloadService.DOWNLOAD_FINISHED);
intent1.putExtra("taskKey",key);
startService(intent1);
Toast.makeText(MainActivity.this,info.getFileName() + "下載完成...",Toast.LENGTH_SHORT).show();
}
}
};
@Override
protected void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
}
2、Bean类
1、FileInfo
package bean;
import java.io.Serializable;
public class FileInfo implements Serializable{
private String url;
private int start;
private int end;
private int id;
private int finished;
private int length;
private String fileName;
private int threadcont;
public FileInfo(int id,int start, int end, int finished, String url, int length,String fileName,int thread_cont) {
this.id = id;
this.end = end;
this.start = start;
this.finished = finished;
this.url = url;
this.length = length;
this.fileName = fileName;
this.threadcont = thread_cont;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public FileInfo()
{
}
public int getThreadcont() {
return threadcont;
}
public void setThreadcont(int threadcont) {
this.threadcont = threadcont;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
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;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "FileInfo{" +
"url='" + url + '\'' +
", start=" + start +
", end=" + end +
", finished=" + finished +
", length=" + length +
'}';
}
}
2、ThreadInfo类
package bean;
public class ThreadInfo {
private int start;
private int end;
private String url;
private int finished;
private int id;
public ThreadInfo()
{
}
public ThreadInfo(int start, String url, int end, int finished, int id) {
this.start = start;
this.url = url;
this.end = end;
this.finished = finished;
this.id = id;
}
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 String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getFinished() {
return finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "ThreadInfo{" +
"start=" + start +
", end=" + end +
", url='" + url + '\'' +
", finished=" + finished +
", id=" + id +
'}';
}
}
3、数据库
1、DBhelper类
package db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBhelper extends SQLiteOpenHelper {
private static DBhelper mDBhelper = null;
private static final String DB_NAME = "download.db";
private static final int DB_VERSION = 1;
private static final String DB_CREATE = "create table thread_info(_id integer primary key autoincrement," +
FileDao.Thread_id + " integer," + FileDao.Thread_url + " text," + FileDao.Thread_start + " integer," + FileDao.Thread_end +
" integer," + FileDao.Thread_finished + " integer" + ")";
private static final String DB_DROP = "drop table if exists thread_info";
private DBhelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public static DBhelper getInstance(Context context) {
if (mDBhelper == null) {
synchronized (new Object()) {
mDBhelper = new DBhelper(context);
}
}
return mDBhelper;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DB_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DB_DROP);
db.execSQL(DB_CREATE);
}
}
2、MyDaoService接口
package db;
import java.util.List;
public interface MyDaoService<T> {
//插入多条
public void insertMore(List<T> params);
//插入一条
public void insertOne(T params);
//删除一条
public void deleteOne(String url);
//查询多条
public List<T> getMore(String url);
//查询一条
public T getOne(int parmas);
//更新一条
public void updateInfo(String url, int id,int finished);
}
3、FileDao类(Dao实现类)
package db;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import bean.ThreadInfo;
public class FileDao implements MyDaoService<ThreadInfo> {
public static final String Thread_id = "thread_id";
public static final String Thread_url = "thread_url";
public static final String Thread_start = "thread_start";
public static final String Thread_end = "thread_end";
public static final String Thread_finished = "thread_finished";
private DBhelper mDBhelper = null;
public FileDao(Context context) {
this.mDBhelper = DBhelper.getInstance(context);
}
@Override
public void insertMore(List<ThreadInfo> params) {
}
@Override
public synchronized void insertOne(ThreadInfo params) {
SQLiteDatabase db = mDBhelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(Thread_id, params.getId());
contentValues.put(Thread_url, params.getUrl());
contentValues.put(Thread_start, params.getStart());
contentValues.put(Thread_end, params.getEnd());
contentValues.put(Thread_finished, params.getFinished());
db.insert("thread_info", null, contentValues);
db.close();
}
@Override
public synchronized void deleteOne(String thread_url) {
SQLiteDatabase db = mDBhelper.getWritableDatabase();
db.delete("thread_info", Thread_url + " = ?", new String[]{thread_url});
db.close();
}
@Override
public List<ThreadInfo> getMore(String thread_url) {
SQLiteDatabase db = mDBhelper.getReadableDatabase();
List<ThreadInfo> list = new ArrayList<>();
Cursor cursor = db.query("thread_info", null, Thread_url + " = ?", new String[]{thread_url}, null, null, null);
while (cursor.moveToNext()) {
ThreadInfo threadInfo = new ThreadInfo();
threadInfo.setId(cursor.getInt(cursor.getColumnIndex(Thread_id)));
threadInfo.setUrl(cursor.getString(cursor.getColumnIndex(Thread_url)));
threadInfo.setStart(cursor.getInt(cursor.getColumnIndex(Thread_start)));
threadInfo.setEnd(cursor.getInt(cursor.getColumnIndex(Thread_end)));
threadInfo.setFinished(cursor.getInt(cursor.getColumnIndex(Thread_finished)));
list.add(threadInfo);
}
cursor.close();
db.close();
return list;
}
@Override
public ThreadInfo getOne(int parmas) {
return null;
}
@Override
public synchronized void updateInfo(String url, int id, int finished) {
SQLiteDatabase db = mDBhelper.getWritableDatabase();
/* ContentValues values = new ContentValues();
values.put(Thread_finished,finished);
db.update("thread_info", values, Thread_url + "= ? and +" + Thread_id + " = ?", new String[]{url, id + ""});*/
db.execSQL("update thread_info set thread_finished = ? where thread_url = ?and thread_id = ?",new Object[]{finished,url,id});
Log.i("DownloadTask", "数据库更新操作的finished" + finished);
db.close();
}
}
4、Service类
1、DownLoadService类
package service;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import bean.FileInfo;
import bean.ThreadInfo;
import db.FileDao;
public class DownloadService extends Service {
public static final String DOWNLOAD_STOP = "ATION_STOP";
public static final String DOWNLOAD_SART = "ATION_START";
public static final String DOWNLOAD_UPDATE = "ATION_UPDATA";
public static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory() + "/download/";
public static final int msg_what = 1;
public static final String DOWNLOAD_FINISHED = "ATION_FINISHED";
private Map<Integer, DownLoadTask> taskMap = null;
@Override
public void onCreate() {
taskMap = new LinkedHashMap<>();
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("DownloadService","Intent是否为null" + (intent == null) );
if (null != intent) {
if (intent.getAction().equals(DOWNLOAD_SART)) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileinfo");
InitThread initThread = null;
DownLoadTask downLoadTask = taskMap.get(fileInfo.getId());
if (downLoadTask == null) {
initThread = new InitThread(fileInfo);
DownLoadTask.mExecutorService.execute(initThread);
} else if (downLoadTask.getDownloadThreads().get(0).getIsPause()) {
List<DownLoadTask.DownloadThread> downloadThreads = downLoadTask.getDownloadThreads();
for (DownLoadTask.DownloadThread thread : downloadThreads) {
thread.setIsPause(false);
}
taskMap.get(fileInfo.getId()).download();
}
} else if (intent.getAction().equals(DOWNLOAD_STOP)) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileinfo");
if (taskMap.get(fileInfo.getId()) != null) {
Log.i("DownLoadService", "暂停" + fileInfo.getFileName() + "文件的下载...");
DownLoadTask downLoadTask = taskMap.get(fileInfo.getId());
List<DownLoadTask.DownloadThread> downloadThreads = downLoadTask.getDownloadThreads();
for (DownLoadTask.DownloadThread thread : downloadThreads) {
thread.setIsPause(true);
}
}
} else if (intent.getAction().equals(DOWNLOAD_FINISHED)) {
int key = intent.getIntExtra("taskKey", 4);
Log.i("DownloadService", "第" + key + "个文件下载完成");
taskMap.remove(key);
}
return super.onStartCommand(intent, flags, startId);
}
return super.onStartCommand(intent, flags, startId);
}
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == msg_what) {
FileInfo fileInfo = (FileInfo) msg.obj;
DownLoadTask downLoadTask = new DownLoadTask(DownloadService.this, fileInfo);
downLoadTask.download();
taskMap.put(fileInfo.getId(), downLoadTask);
Log.i("DownLoadTask", "執行到了這裡5");
}
}
};
/**
* 初始化线程类
*/
class InitThread extends Thread {
private FileInfo mFileInfo;
public InitThread(FileInfo fileInfo) {
this.mFileInfo = fileInfo;
}
@Override
public void run() {
try {
URL url = new URL(mFileInfo.getUrl());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
int length = -1;
if (conn.getResponseCode() == 200) {
length = conn.getContentLength();
}
if (length <= 0) {
return;
}
File dir = new File(DOWNLOAD_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, mFileInfo.getFileName());
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.setLength(length);
mFileInfo.setLength(length);
mHandler.obtainMessage(msg_what, mFileInfo).sendToTarget();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onDestroy() {
taskMap = null;
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
2、DownLoadTask类
package service;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import bean.FileInfo;
import bean.ThreadInfo;
import db.FileDao;
public class DownLoadTask {
//访问数据库对象
private FileDao mFileDao = null;
//保存所有的线程信息
private List<ThreadInfo> threads = null;
//上下文
private Context context;
//下载任务的文件信息
private FileInfo mFileInfo = null;
//保存下载任务的所有的下载线程对象
private List<DownloadThread> DownloadThreads = null;
//线程池对象
public static ExecutorService mExecutorService = Executors.newCachedThreadPool();
public DownLoadTask(Context context, FileInfo fileInfo) {
this.context = context;
mFileDao = new FileDao(context);
this.mFileInfo = fileInfo;
threads = new ArrayList<>();
DownloadThreads = new ArrayList<>();
}
public List<DownloadThread> getDownloadThreads() {
return DownloadThreads;
}
/**
* 检查是否完成任务
*/
public synchronized void checkIsFinished()
{
int finished = 0;
for (ThreadInfo info :threads)
{
finished += info.getFinished();
}
if (finished == mFileInfo.getLength())
{
Intent intent = new Intent(DownloadService.DOWNLOAD_FINISHED);
intent.putExtra("taskKey",mFileInfo.getId());
context.sendBroadcast(intent);
mFileDao.deleteOne(mFileInfo.getUrl());
}
}
/**
* 下载方法
*/
public void download() {
List<ThreadInfo> list = mFileDao.getMore(mFileInfo.getUrl());
Log.i("DownLoadTask", "数据库取出来的线程信息为数据为" + list.toString());
if (list.size() == 0) {
//每一个线程下载的长度
int size = mFileInfo.getLength() / mFileInfo.getThreadcont();
for (int i = 0; i < mFileInfo.getThreadcont(); i++) {
ThreadInfo threadInfo = new ThreadInfo(i * size, mFileInfo.getUrl(), (i + 1) * size - 1, 0, i);
if (i == mFileInfo.getThreadcont() - 1) {
threadInfo.setEnd(mFileInfo.getLength());
}
threads.add(threadInfo);
//将线程信息保存到数据库
mFileDao.insertOne(threadInfo);
}
}else {
threads.clear();
threads.addAll(list);
}
this.DownloadThreads.clear();
for (ThreadInfo info : threads) {
DownloadThread downloadThread = new DownloadThread(info);
mExecutorService.execute(downloadThread);
this.DownloadThreads.add(downloadThread);
}
Log.i("DownloadTask" ,"下载线程的数量为" + getDownloadThreads().size());
}
/**
* 下载线程类
*/
class DownloadThread extends Thread {
//当前下载线程的信息
private ThreadInfo threadInfo;
public DownloadThread(ThreadInfo threadInfo) {
this.threadInfo = threadInfo;
}
//下载线程停止控制符
private boolean isPause = false;
public void setIsPause(boolean status) {
this.isPause = status;
}
public boolean getIsPause()
{
return this.isPause;
}
@Override
public void run() {
HttpURLConnection conn = null;
InputStream is = null;
RandomAccessFile raf = null;
try {
URL url = new URL(threadInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
int start = threadInfo.getStart() + threadInfo.getFinished();
conn.setRequestProperty("range", "bytes= " + start + "-" + threadInfo.getEnd());
if (conn.getResponseCode() == 206) {
is = conn.getInputStream();
int length = -1;
byte[] bytes = new byte[4 * 1024];
long time = System.currentTimeMillis();
raf = new RandomAccessFile(DownloadService.DOWNLOAD_PATH + mFileInfo.getFileName(), "rwd");
raf.seek(start);
while ((length = is.read(bytes, 0, bytes.length)) != -1) {
raf.write(bytes, 0, length);
threadInfo.setFinished(threadInfo.getFinished() + length);
if (System.currentTimeMillis() - time > 3000) {
Intent intent = new Intent(DownloadService.DOWNLOAD_UPDATE);
intent.putExtra("taskKey", mFileInfo.getId());
time = System.currentTimeMillis();
int finishedadd = 0;
synchronized (new Object())
{
for (ThreadInfo info : threads)
{
finishedadd += info.getFinished();
}
}
long progressMath = (long)finishedadd*100/mFileInfo.getLength();
intent.putExtra("finished",progressMath);
Log.i("DownLoadTask", "正在下載....,finishedadd的值为 " + finishedadd);
Log.i("DownLoadTask", "正在下載....,文件的长度为 " + mFileInfo.getLength());
Log.i("DownLoadTask","正在下載....,进度条的值 "+ progressMath);
context.sendBroadcast(intent);
}
if (isPause) {
mFileDao.updateInfo(threadInfo.getUrl(), threadInfo.getId(), threadInfo.getFinished());
return;
}
}
//每一个线程完成时检查下是否下载完成
checkIsFinished();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
conn.disconnect();
is.close();
raf.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
5、测试结果
特别说明:此人自学Android开发,有何不足之处多谢指教。