# # # # # 2

##### Multithreading: There must be multiple threads. Breakpoint: Where the thread stopped downloading. Continuation: The thread continues downloading from where it stopped downloading until it completes the task.

##### Core analysis: ###### Breakpoint: length of data downloaded by the current thread

###### continuation: Requests data from the server about where the thread last stopped downloading

con.setRequestProperty("Range", "bytes=" + start + "-" + end);
Copy the code

###### Allocate thread:

 int currentPartSize = fileSize / mThreadNum;
Copy the code

###### Define location

Define where the thread starts the download and where it ends

for (int i = 0; i < mThreadNum; i++) { int start = i * currentPartSize; Int end = start + currentPartSize-1; If (I ==mThreadNum-1){end=fileSize; }}Copy the code

###### Create database: Since each file is divided into multiple parts, it is downloaded by different threads simultaneously. Of course, you need to create a thread table to store where the current thread download started and ended, as well as the completion progress, etc. Create a file table to store information about downloaded files, such as file name, URL, download progress, etc. ###### thread table:

public static final String CREATE_TABLE_SQL="create table "+TABLE_NAME+"(_id integer primary "
        +"key autoincrement, threadId, start , end, completed, url)";
Copy the code

# # # # # # file table:

public static final String CREATE_TABLE_SQL="create table "+TABLE_NAME+"(_id integer primary" +
        " key autoincrement ,fileName, url, length, finished)";
Copy the code

Download Manager.java. The core methods are: start(),stop(),restart(),addTask().clear(). The other is the thread task class Downloadtask.java, which is a thread class for downloading tasks assigned by threads. The code will be posted later.

###### creating a database method class is nothing more than a singleton pattern, encapsulating some basic database methods such as add, delete, change, check, etc., will post the specific code later. ###### Create entity classes ThreadInfo and FileInfo to store downloaded files and thread information temporarily. ###### introduced NumberProgressBar, an open source library for progress bars. The direct link

# # # # # code specific analysis 1. The first is to create an entity class, file the FileInfo entity class, there must be a fileName, url, length, finised, isStop, isDownloading these attributes. Thread ThreadInfo entity class must have a threadId, start, end, completed, url these attributes. It’s all very simple

//ThreadInfo.java
public class ThreadInfo {
private String threadId;
private int start;
private int end;
private int compeleted;
private String url;


public ThreadInfo(){

}

public ThreadInfo(String threadId, int start, int end, int compeleted, String url) {
    this.threadId = threadId;
    this.start = start;
    this.end = end;
    this.compeleted = compeleted;
    this.url = url;
}

public String getThreadId() {
    return threadId;
}

public void setThreadId(String threadId) {
    this.threadId = threadId;
}

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 getCompeleted() {
    return compeleted;
}

public void setCompeleted(int compeleted) {
    this.compeleted = compeleted;
}

public String getUrl() {
    return url;
}

public void setUrl(String url) {
    this.url = url;
}
Copy the code

}

//FileInfo.java

public class FileInfo { private String fileName; // file name private String URL; Private int length; // File size private int finished; Private Boolean isStop=false; // Whether to suspend downloading private Boolean isDownloading=false; Public FileInfo(){} public FileInfo(String fileName,String URL){this.filename =fileName; this.url=url; } 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 getLength() { return length; } public void setLength(int length) { this.length = length; } public int getFinished() { return finished; } public void setFinished(int finished) { this.finished = finished; } public boolean isStop() { return isStop; } public void setStop(boolean stop) { isStop = stop; } public boolean isDownloading() { return isDownloading; } public void setDownloading(boolean downloading) { isDownloading = downloading; } @Override public String toString() { return "FileInfo{" + "fileName='" + fileName + '\'' + ", url='" + url + '\'' + ", length=" + length + ", finished=" + finished + ", isStop=" + isStop + ", isDownloading=" + isDownloading + '}'; }}Copy the code

SQLiteOpenHelper (SQLiteOpenHelper) : SQLiteOpenHelper (SQLiteOpenHelper) : sqliteHelper (SQLiteOpenHelper) : sqliteHelper (SQLiteOpenHelper)

public class ThreadHelper extends SQLiteOpenHelper{
public static final String TABLE_NAME="downthread";
public static final String CREATE_TABLE_SQL="create table "+TABLE_NAME+"(_id integer primary "
        +"key autoincrement, threadId, start , end, completed, url)";


public ThreadHelper(Context context, String name, int version) {
    super(context, name, null, version);
}

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

}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}}
Copy the code

3. Next, encapsulate some add, delete, change and check operations of the database, use singleton mode, and realize singleton with double check lock. Benefits: Not only to a large extent to ensure thread safety, but also to achieve lazy loading. Disadvantages: Using the volatile keyword can cause the JVM to lose optimization for this code, affecting performance. And in some cases of high concurrency, it is still possible to create multiple instances, which is called double-checked lock failure. The singleton pattern

public class Thread { private SQLiteDatabase db; public static final String DB_NAME="downthread.db3"; public static final int VERSION=1; private Context mContext; private volatile static Thread t=null; private Thread(){ mContext= BaseApplication.getContext(); db=new ThreadHelper(mContext,DB_NAME,VERSION).getReadableDatabase(); } public static Thread getInstance(){ if(t==null){ synchronized (Thread.class){ if(t==null){ t=new Thread(); } } } return t; } public SQLiteDatabase getDb(){ return db; Public synchronized void insert(ThreadInfo ThreadInfo){ContentValues values=new ContentValues(); values.put("threadId",threadInfo.getThreadId()); values.put("start",threadInfo.getStart()); values.put("end",threadInfo.getEnd()); values.put("completed",threadInfo.getCompeleted()); values.put("url",threadInfo.getUrl()); long rowId=db.insert(ThreadHelper.TABLE_NAME,null,values); if(rowId! =-1){utilslog. I (" thread record inserted successfully "); }else{utilslog. I (" failed to insert thread record "); Public synchronized ThreadInfo query(String threadId,String queryUrl){Cursor} public synchronized ThreadInfo query(String threadId,String queryUrl){Cursor cursor=db.query(ThreadHelper.TABLE_NAME,null,"threadId= ? and url= ?" ,new String[]{threadId,queryUrl},null,null,null); ThreadInfo info=new ThreadInfo(); if(cursor! =null){ while (cursor.moveToNext()){ int start=cursor.getInt(2); int end=cursor.getInt(3); int completed=cursor.getInt(4); String url=cursor.getString(5); info.setThreadId(threadId); info.setStart(start); info.setEnd(end); info.setCompeleted(completed); info.setUrl(url); } cursor.close(); } return info; } public synchronized void update(ThreadInfo info){ContentValues values=new ContentValues(); values.put("start",info.getStart()); values.put("completed",info.getCompeleted()); db.update(ThreadHelper.TABLE_NAME,values,"threadId= ? and url= ?" ,new String[]{info.getThreadId(),info.getUrl()}); } public void close(){db.close(); } public Boolean isExist(String url){Cursor Cursor =db.query(threadhelper.table_name,null,"url=?" ,new String[]{url},null,null,null); boolean isExist=cursor.moveToNext(); cursor.close(); return isExist; } public synchronized void delete(ThreadInfo info){ long rowId=db.delete(ThreadHelper.TABLE_NAME,"url =? and threadId= ?",new String[]{info.getUrl(),info.getThreadId()}); if(rowId! =-1){utilslog. I (" download thread record deleted successfully "); }else{utilslog. I (" failed to delete download thread record "); } } public synchronized void delete(String url){ long rowId=db.delete(ThreadHelper.TABLE_NAME,"url =? ",new String[]{url}); if(rowId! =-1){utilslog. I (" download thread record deleted successfully "); }else{utilslog. I (" failed to delete download thread record "); }}}Copy the code

4. Now that we’ve done the basic preparation, let’s start writing classes about downloads. The first thing to write is definitely the DownLoadManager class, which is the class that manages task downloads. So without saying much, let’s just look at the code.

public class DownLoadManager {
private Map<String, FileInfo> map = new HashMap<>();
private static int mThreadNum;
private int fileSize;
private boolean flag = false; //true第一次下载 false不是第一次下载
private List<DownLoadTask> threads;
private static FileInfo mInfo;
private static ResultListener mlistener;
public static ExecutorService executorService = Executors.newCachedThreadPool();
public static File file;
private int totalComleted;

private DownLoadManager() {
    threads = new ArrayList<>();
}

public static DownLoadManager getInstance(FileInfo info, int threadNum,ResultListener listener) {
    mlistener = listener;
    mThreadNum = threadNum;
    mInfo = info;
    return DownLoadManagerHolder.dlm;
}

private static class DownLoadManagerHolder {
    private static final DownLoadManager dlm = new DownLoadManager();
}

public void start() {
    totalComleted=0;
    clear();
    final FileInfo newInfo = DownLoad.getInstance().queryData(mInfo.getUrl());
    newInfo.setDownloading(true);
    map.put(mInfo.getUrl(),newInfo);
    prepare(newInfo);
}

//停止下载任务
public void stop() {
    map.get(mInfo.getUrl()).setDownloading(false);
    map.get(mInfo.getUrl()).setStop(true);
}


public void clear(){
    if(threads.size()>0){
        threads.clear();
    }
}

//重新下载任务
public void restart() {
    stop();
    try {
        File file = new File(com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH, map.get(mInfo.getUrl()).getFileName());
        if (file.exists()) {
            file.delete();
        }
        java.lang.Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    DownLoad.getInstance().resetData(mInfo.getUrl());
    start();
}

//获取当前任务状态, 是否在下载
public boolean getCurrentState() {
    return map.get(mInfo.getUrl()).isDownloading();
}

//添加下载任务
public void addTask(FileInfo info) {
    //判断数据库是否已经存在此下载信息
    if (!DownLoad.getInstance().isExist(info)) {
        DownLoad.getInstance().insertData(info);
        map.put(info.getUrl(), info);
    } else {
        DownLoad.getInstance().delete(info);
        DownLoad.getInstance().insertData(info);
        UtilsLog.i("map已经更新");
        map.remove(info.getUrl());
        map.put(info.getUrl(), info);
    }
}

private void prepare(final FileInfo newInfo) {
    new java.lang.Thread(){
        @Override
        public void run() {
            HttpURLConnection con = null;
            RandomAccessFile raf=null;
            try {
                //连接资源
                URL url = new URL(newInfo.getUrl());
                UtilsLog.i("url=" + url);
                con = (HttpURLConnection) url.openConnection();
                con.setConnectTimeout(2 * 1000);
                con.setRequestMethod("GET");
                int length = -1;
                UtilsLog.i("responseCode=" + con.getResponseCode());
                if (con.getResponseCode() == 200) {
                    length = con.getContentLength();
                    UtilsLog.i("文件大小=" + length);
                }
                if (length <= 0) {
                    return;
                }
                //创建文件保存路径
                File dir = new File(com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH);
                if (!dir.exists()) {
                    dir.mkdirs();//建立多级文件夹
                }
                newInfo.setLength(length);
                fileSize = length;
                UtilsLog.i("当前线程Id=" + java.lang.Thread.currentThread().getId() + ",name=" + java.lang.Thread.currentThread().getName());
                int currentPartSize = fileSize / mThreadNum;
                file = new File(com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH, newInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                raf.setLength(fileSize);
                if (Thread.getInstance().isExist(newInfo.getUrl())) {
                    flag = false;
                } else {
                    flag = true;
                }
                for (int i = 0; i < mThreadNum; i++) {
                    if (flag) {
                        UtilsLog.i("第一次多线程下载");
                        int start = i * currentPartSize;//计算每条线程下载的开始位置
                        int end = start + currentPartSize-1;//线程结束的位置
                        if(i==mThreadNum-1){
                            end=fileSize;
                        }
                        String threadId = "xiaoma" + i;
                        ThreadInfo threadInfo = new ThreadInfo(threadId, start, end, 0,newInfo.getUrl());
                        Thread.getInstance().insert(threadInfo);
                        DownLoadTask thread = new DownLoadTask(threadInfo,newInfo, threadId, start, end, 0);
                        DownLoadManager.executorService.execute(thread);
                        threads.add(thread);
                    } else {
                        UtilsLog.i("不是第一次多线程下载");
                        ThreadInfo threadInfo = Thread.getInstance().query("xiaoma" + i, newInfo.getUrl());
                        DownLoadTask thread = new DownLoadTask(threadInfo,newInfo,threadInfo.getThreadId(),threadInfo.getStart(),threadInfo.getEnd(),threadInfo.getCompeleted());//这里出现过问题
                        DownLoadManager.executorService.execute(thread);
                        threads.add(thread);
                    }
                }

                boolean  isCompleted=false;
                while(!isCompleted){
                    isCompleted=true;
                    for(DownLoadTask thread:threads){
                        totalComleted+=thread.completed;
                        if(!thread.isCompleted){
                            isCompleted=false;
                        }
                    }

                    if(newInfo.isStop()){
                        totalComleted=0;
                        return;
                    }

                    Message message=new Message();
                    message.what=0x555;
                    message.arg1=fileSize;
                    message.arg2=totalComleted;
                    handler.sendMessage(message);

                    if(isCompleted){
                        totalComleted=0;
                        //任务线程全部完成,清空集合
                        clear();
                        handler.sendEmptyMessage(0x666);
                        return;
                    }

                    totalComleted=0;
                    java.lang.Thread.sleep(1000);
                }
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    if (con != null) {
                        con.disconnect();
                    }
                    if(raf!=null){
                        raf.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();
}


private Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case 0x555:
                if(mlistener!=null){
                    mlistener.progress(msg.arg1,msg.arg2);
                }
                break;
            case 0x666:
                if(mlistener!=null){
                    mlistener.comleted();
                }
                break;
        }
    }
};}
Copy the code

5. Next up is the DownLoadTask class, which is a thread download class.

public class DownLoadTask extends java.lang.Thread{ private int start; Private int end; Private RandomAccessFile raf; Public int completed=0; // Number of bytes downloaded by the current thread private String threadId; // Self-defined thread Id private FileInfo info; private ThreadInfo threadInfo; public boolean isCompleted=false; Start public int finshed=0; start public int finshed=0; public int newStart=0; public DownLoadTask(ThreadInfo threadInfo,FileInfo info,String threadId, int start, int end,int completed){ this.threadInfo=threadInfo; this.info=info; this.threadId=threadId; this.start=start; this.end=end; this.completed=completed; } @Override public void run() { HttpURLConnection con = null; try { UtilsLog.i("start="+start+",end="+end+",completed="+completed+",threadId="+getThreadId()); URL url = new URL(info.getUrl()); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(2 * 1000); con.setRequestMethod("GET"); con.setRequestProperty("Range", "bytes=" + start + "-"+end); Raf =new RandomAccessFile(downloadManager.file," RWD "); // Write raf.seek(start) from somewhere in the file; If (con.getresponsecode () == 206) {InputStream is = con.getinputStream (); byte[] buffer = new byte[4096]; int hasRead = 0; while ((hasRead = is.read(buffer)) ! // write to raf.write(buffer, 0, hasRead); // The degree of completion of a single file completed += hasRead; threadInfo.setCompeleted(completed); // Save new start finshed=finshed+hasRead; NewStart =start+finshed; threadInfo.setStart(newStart); //UtilsLog.i("Thread:"+getThreadId()+",completed=" + completed); If (info.isstop ()) {utilslog. I ("isStop="+ info.isstop ()); Utilslog. I (" Now Thread:"+getThreadId()+",completed=" + completed); // Save the download progress utilslog. I (" Now Thread:"+getThreadId()+",completed=" + completed); Thread.getInstance().update(threadInfo); return; }} thread.getInstance ().delete(threadInfo); isCompleted=true; Thread.getInstance().update(threadInfo); Utilslog. I (" Thread :"+getThreadId()+" Task completed! --"+"completed="+completed); } } catch (Exception e) { if (con ! = null) { con.disconnect(); } try { if (raf ! = null) { raf.close(); } } catch (IOException e1) { e1.printStackTrace(); } } } public String getThreadId() { return threadId; }}Copy the code

6 interface, is a monitor download progress interface, is also very simple.

public interface ResultListener{ void progress(int max, int progress); void comleted(); }Copy the code

###### end general operation is like this, in fact, multithreading is also very simple.