Android multi thread HTTP download

Design sketch
Android multi thread HTTP download
download.gif
Vernacular analysis:

Multi thread: multiple threads must be slightly
breakpoint: thread download location
http: stop the thread from the position to stop downloading download, until the job is done.

Core analysis:
Breakpoint:

The length of data that the current thread has downloaded

Http:

Ask the server to request the last thread to stop downloading the data

Con.setRequestProperty ("Range", "bytes=" + start + "+" + end);
Distribution thread:
Int currentPartSize = fileSize / mThreadNum;
Define location

Defines the location and the end of the thread to start downloading

For (int i = 0; I < mThreadNum; i++) {int start = I * currentPartSize; / / the calculation of each thread download start position int end = start + currentPartSize-1; / / if thread end position (i==mThreadNum-1) {end=fileSize;}}
Create database:

Because each file is divided into several parts, it is necessary to be downloaded by different threads at the same time. Of course, to create a thread table, save the current thread to download the start of the location and the end of the location, as well as the completion of the progress, etc.. Create a file table, save the current download of the file information, such as: file name, URL, download progress information

Thread table:
Public static String CREATE_TABLE_SQL= "create" +TABLE_NAME+ "(_id integer primary" + "key autoincrement, threadId, start, end, final, completed, URL)") table";
File table:
Public static String CREATE_TABLE_SQL= "create" +TABLE_NAME+ "(_id integer primary" + "key autoincrement, fileName, URL, final, length, finished)") table";
Thread class

Nothing more than 2 categories, one is the thread management class DownLoadManager.java, the core method: start (), stop (), restart (), addTask ().Clear (). The other is the thread task class
DownLoadTask.java, is a thread class, used to download the thread assigned tasks. Paste the specific code behind.

Create database method class

Nothing more than a single case model, encapsulation of some additions and deletions to change the basic database method, such as the back will paste the specific code.

Create entity class

That is, the creation of ThreadInfo and FileInfo these 2 entities, download the file information and thread information temporarily stored up.

Third party open source library

NumberProgressBar is an open source library on the progress bar, very good. Direct link

Code specific analysis

1 is the first to create entity class, the file entity class FileInfo, there must be fileName, URL, length, finised, isStop, isDownloading these attributes. The thread entity class ThreadInfo must have threadId, start, end, completed, URL these properties. These are very simple

//ThredInfo.java public class FileInfo String {private fileName; / / String / / private file name URL; int download private file size private int length; / / finished; / / private Boolean isStop=false download has completed the schedule; / / private Boolean isDownloading=false is to suspend the download; / / is to download the 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 = len GTH public (getFinished);} int {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'}'+}};

//FileInfo.java

Public class FileInfo {private String fileName; / / String / / private file name URL; int download private file size private int length; / / finished; / / private Boolean isStop=false download has completed the schedule; / / private Boolean isDownloading=false is to suspend the download; / / is to download the 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;} Pu Blic 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 +'}';}}

2 entity classes finished, then write to create a class, SQLiteOpenHelper class inheritance, to manage the database connection, the main functions: management database initialization, and allows the application to get the SQLiteDatabase object through the.

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) {}}

3 next to the number of additions and deletions to check the operation of the database, using a single case model, with a double test lock to achieve a single case. Benefits: not only to a large extent to ensure the safety of the thread, but also to achieve delayed loading. Disadvantages: the use of volatile keyword will make the loss of the JVM code optimization, the impact of performance. And in some high concurrency situations, multiple instances may still be created, which is called double check lock failure. Singleton pattern

Public class Thread SQLiteDatabase public static {private db; 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;} / / save the current thread public synchronized void insert download progress (ThreadInfo threadInfo) {ContentValues values=new (ContentValues); values.put ("threadId". Th (readInfo.getThreadId)); values.put ("start") (threadInfo.getStart); values.put ("end") (threadInfo.getEnd); values.put ("completed") (threadInfo.getCompeleted); values.put ("URL"); long (threadInfo.getUrl) rowId=db.insert (ThreadHelper.TABLE_NAME, null, values; if (=-1) rowId! (UtilsLog.i) {"thread insert records");}else{UtilsLog.i ("the thread insert records failed");}} / / public synchronized ThreadInfo query to check the progress of the current thread (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; int (3) 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);};} / / update the current thread download the progress of 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 (,)}}); / / Close the DB public void close (db.close));} ({/ / determine the multi-threaded task if the download first created thread 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 {long (ThreadInfo info) rowId=db.delete (ThreadHelper.TABLE_NAME, url = and? ThreadId=? New (info.getUrl), String[]{, (info.getThreadId)}); if (rowId! =-1) {UtilsLog.i (" delete download thread records ");}else{UtilsLog.i (" delete thread download records the failure of ");}} public synchronized void delete (String URL) {long rowId= db.delete (ThreadHelper.TABLE_NAME, url =? "New, String[]{url}); if (rowId! =-1) {UtilsLog.i (" delete "successful download thread records);}else{UtilsLog.i (" delete records failed download thread ");}}}

4 basic preparation for the operation we have done, then start writing about the downloaded class. First of all, must be written in the DownLoadManager category, is to manage the task to download the class. Not much to say, look directly at the code.

Public class DownLoadManager {private Map< String, FileInfo> map = new; HashMap< > static int mThreadNum (private); private int fileSize; private boolean flag = false; //true first download the false is not the first time to download the 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 (New) {threads = 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 = new DownLoadManager DLM (DownLoadManager) public (start);} void {totalComleted=0; clear (final); FileInfo newInfo = DownLoad.getInstance (.QueryData) (mInfo.getUrl) (newInfo.setDownloading); (true); map.put (mInfo.getUrl), (newInfo) prepare (newInfo);} / / stop; public (stop) void download task {map.get (mInfo.getUrl) (.SetDownloading) (false); map.get ((mInfo.getUrl)).SetStop (true) public (clear);} void {if (threads.size) > (0) {threads.clear}} (); / / public void restart re download task ((stop)) {try {File; file = new (File com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH, map.get (mI (nfo.getUrl) (.GetFileName))); (file.exists) (if) (file.delete);} {java.lang.Thread.sleep} (100); catch (InterruptedException E) {e.printStackTrace}); (DownLoad.getInstance) (.ResetData (mInfo.getUrl) (start); ();} / / get the current state of the task, whether in the download public Boolean getCurrentState (return) {map.get (mInfo.getUrl) (.IsDownloading) ();} / / public void addTask add download task (FileInfo info) {/ / determine whether the database already exists this download information 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 updated"); 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 connection resources ((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 ("file size =" + length (length < if);}; {return} = 0); / / create a file path File dir = new File (com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH); if (dir.exists) (!) {(dir.mkdirs) the establishment of a multi-level folder; / / newInfo.setLength} (length); fileSize = length; UtilsLog.i ("the current thread Id=" (.GetId) + java.lang.Thread.currentThread (+), name= (java.lang.Thread.currentThread).GetName (+)); Int currentPartSize = fileSize / mThreadNum; file = new (com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH, File) (newInfo.getFileName); RAF new = RandomAccessFile (file, RWD); raf.setLength (fileSize); if (Thread.getInstance) (newInfo.getUrl) ((.IsExist)) {flag = false;} else {flag} for (int = true; I = 0; I < mThreadNum; i++) {if (flag) {UtilsLog.i ("first multi-threaded download"); int start = I * currentPartSize; / / the calculation of each thread download start position int end = start + current PartSize-1; / / if thread end position (i==mThreadNum-1) {end=fileSize}; String threadId = "Xiaoma" ThreadInfo + I; threadInfo = new ThreadInfo (threadId, start, end, newInfo.getUrl (0)); (Thread.getInstance).Insert (threadInfo); DownLoadTask thread = new DownLoadTask (threadInfo, newInfo, threadId, start, end, 0); DownLoadManager.executorService.execute (thread); threads.add (thread);} else {UtilsLog.i ("not the first multi thread download"); ThreadInfo = threadInfo (Thread.getInstance).Query (Xiaoma + I), newInfo.getUrl (DownLoadTask); thread new = DownLoadTask (threadInfo, newInfo, threadInfo.getThreadId (threadInfo.getStart), threadInfo.getEnd (), (), (threadInfo.getCompeleted)); / / here had problems 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; / / task completed, clear (handler.sendEmptyMessage); empty set (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 (public void handleMessage) {@Override (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;

5 next, it is the DownLoadTask class, is a thread download class.

Public class DownLoadTask extends java.lang.Thread{private int start; / / the current thread to start the download location private int end; / / the end of the thread download location private RandomAccessFile RAF; / / the current thread is responsible for downloading the file size public int completed=0; / / the current thread has been downloaded bytes private String threadId; / / Id private FileInfo their own definition of thread info private; ThreadInfo threadInfo; public Boolean isCompleted=false; //true for the current thread to complete the task, the false for the current thread is not complete the task to save the new start public int / public int newStart=0; public finshed=0; DownLoadTask (ThreadInfo threadInfo, FileInfo info, String threadId, int start, int end, int completed) {this.threadInfo=threadInfo; this.info=info; this.threadId= threa DId; 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 key RandomAccessFile (DownLoadManager.file, RWD); / / from a location in a file is written to the raf.seek (start if (con.getResponseCode); () = = 206) {/ / file download back Return code is 206 InputStream is = con.getInputStream (byte[] new byte[4096]); buffer = int; hasRead = 0; while ((hasRead = is.read (buffer))! = -1) {/ / raf.write file (buffer, 0, hasRead); / / single file to complete the degree of completed + = hasRead; threadInfo.setCompeleted (completed); save the new start / / finshed=finshed+hasRead; / / this problem occurred, and newStart=start+finshed; threadInfo.setStart (newStart); //UtilsLog.i (+getThreadId) ("Thread:" + "completed=" + completed); Stop / download if (info.isStop ()) {UtilsLog.i ("isStop=") (+info.isStop); / / UtilsLog.i ("save the download progress now Thread:" (+getThreadId) + "completed=" + completed); (Thread.getInstance).Update (threadInfo); return;}} / / delete the record (.Delete) Thread.getInstance download thread (threadInfo); isCompleted=true; Thread.getInstance (.Update) (threadInfo); UtilsLog.i ("thread:" (+getThreadId) + "has completed the task! - +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;}}

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

Public interface void progress (int max, int progress); void comleted ();} (ResultListener{)
End

The operation is like this, in fact, multi threading is also very simple.