多文件多线程断点续传项目练习总结

来源:互联网 发布:央视评论网络直播乱象 编辑:程序博客网 时间:2024/05/16 07:59

一、练习项目概述
此项目实现多线程下载多个文件,涉及到的知识点有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开发,有何不足之处多谢指教。

0 0