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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值