Android/java http multi-thread breakpoint download (with source code)
First look at the project structure:
The multi‑thread http breakpoint download involves several modules, such as databases, multithreading, and http requests. It is not difficult to find out many things. Next, I will share my practices with you.
I. first look at MainActivity. java
Member variables, mainly the variables in the download process and handler
Private String path = "http: // 192.168.1.3: 8080/wanmei/yama.apk"; private String sdcardPath; private int threadNum = 5; ProgressDialog dialog; // download progress private int process; // percentage of download completion private int done; private int filelength; // The download volume that has been completed before the start of this download private int completed; // The thread pool is used to enable elegant interrupt threads to download the ExecutorService pool; @ SuppressLint ("HandlerLeak") private Handler handler = new Handler () {public void handleMessage (android. OS. message msg) {process + = msg. arg1; done = (int) (1.0 * process/filelength) * 100); Log. I ("process", "process" + done); dialog. setProgress (done); // display dialogif (done = 100) When no dialog is displayed for the first time) {// prompt that the download is complete. // After the thread download is complete, the cached data DBService in the database will be deleted. getInstance (getApplicationContext ()). delete (path); // implement a latency effect, which allows you to read more information for a moment. 100% Timer timer = new Timer (); timer. schedule (new TimerTask () {@ Overridepublic void run () {dialog. dismiss () ;}}, 1000 );}};};
The download method triggers the download event. check whether there is an SD card before starting thread download.
Public void download (View v) {completed = 0; process = 0; done = 0; pool = Executors. newFixedThreadPool (threadNum); initProgressDialog (); new Thread () {public void run () {try {if (Environment. getExternalStorageState (). equals (Environment. MEDIA_MOUNTED) {sdcardPath = Environment. getExternalStorageDirectory (). getAbsolutePath ();} else {toast ("no memory card"); return;} download (path, threadNum);} catch (Exception e) {e. printStackTrace ();}};}. start ();}
Before starting the download, we must first make an http request to get the size and file name of the downloaded file, and prepare the size of the local file and the region where each thread should download it. At this time, the request information is included in the response header. You only need to request the head, which not only shortens the response time, but also saves traffic.
Public void download (String path, int threadsize) throws Exception {long startTime = System. currentTimeMillis (); URL url = new URL (path); // HttpHead head = new HttpHead (path); HttpURLConnection conn = (HttpURLConnection) url. openConnection (); // only httphead needs to be obtained here, to the request header file, no body is required, // not only can shorten the response time, but also can save traffic // conn. setRequestMethod ("GET"); conn. setRequestMethod ("HEAD"); conn. setConnectTimeout (5*1000); Map
> HeaderMap = conn. getHeaderFields (); Iterator
Iterator = headerMap. keySet (). iterator (); while (iterator. hasNext () {String key = iterator. next (); List
Values = headerMap. get (key); System. out. println (key + ":" + values. toString ();} filelength = conn. getContentLength (); // get the length of the object to be downloaded long endTime = System. currentTimeMillis (); Log. I ("spend", "spend time =" + (endTime-startTime); String filename = getFilename (path ); // obtain the File name from the path File = new File (sdcardPath + "/download/"); if (! File. exists () {File. mkdirs ();} File saveFile = new File (sdcardPath + "/download/" + filename); RandomAccessFile accessFile = new RandomAccessFile (saveFile, "rwd"); accessFile. setLength (filelength); // set the length of the local file to be the same as that of the downloaded file accessFile. close (); // calculate the length of data downloaded by each thread
Int block = filelength % threadsize = 0? Filelength/threadsize: filelength/threadsize + 1;// Determine if it is the first download. if it is not the first download, calculate the number of if (! DBService. getInstance (getApplicationContext ()). isHasInfors (path) {for (int threadid = 0; threadid <threadNum; threadid ++) {completed + = DBService. getInstance (getApplicationContext ()). getInfoByIdAndUrl (threadid, path) ;}} Message msg = handler. obtainMessage (); msg. arg1 = completed; handler. sendMessage (msg); for (int threadid = 0; threadid <threadsize; threadid ++) specify pool.exe cute (new DownloadThread (getApplicationContext (), path, saveFile, block, threadid, threadNum ). setOnDownloadListener (this ));}}
DownloadThread. java
There are two points: 1. Google recommends httpurlconnection. I tried to download faster than httpclient.
2. the byte array used for caching during download. Its length affects the download speed.
@ Overridepublic void run () {Log. I ("download", "thread id:" + threadid + "Start download"); // calculate the start position formula: thread id * length of data downloaded by each thread + downloaded (resumable data transfer) =? // Formula for calculating the end position: (thread id + 1) * length of data downloaded by each thread-1 =? Completed = DBService. getInstance (context ). getInfoByIdAndUrl (threadid, url); int startposition = threadid * block + completed; int endposition = (threadid + 1) * block-1; try {RandomAccessFile accessFile = new RandomAccessFile (saveFile, "rwd"); accessFile. seek (startposition); // specifies the position from which data is written. // during my test, httpurlconnection downloads faster than httpclient 10 times faster than HttpURLConnection conn = (HttpURLConnection) new URL (url ). openC Onnection (); conn. setRequestMethod ("GET"); conn. setConnectTimeout (5*1000); conn. setRequestProperty ("Accept-Language", "zh-CN"); conn. setRequestProperty ("Accept", "image/gif, image/jpeg, image/pjpeg," + "image/pjpeg, application/x-shockwave-flash, "+" application/xaml + xml, application/vnd. ms-xpsdocument, "+" application/x-ms-xbap, application/x-ms-application, "+" application/vnd. ms-excel, application /Vnd. ms-powerpoint, "+" application/msword, */* "); conn. setRequestProperty ("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0;" + "Windows NT 5.2; Trident/4.0 ;. net clr 1.1.4322 ;. net clr 2.0.50727; "+ ". net clr 3.0.04506.30 ;. net clr 3.0.20.6.2152 ;. net clr 3.5.30729) "); conn. setRequestProperty ("Referer", url); conn. setRequestProperty ("Connection", "Keep-Alive"); conn. setRequestProperty ("RANGE", "bytes = "+ Startposition +"-"+ endposition); // you can specify a range for retrieving object data. // HttpClient httpClient = new DefaultHttpClient (); // HttpGet httpGet = new HttpGet (url); // httpGet. addHeader ("Range", // "bytes =" + startposition + "-" + endposition); // HttpResponse response = httpClient.exe cute (httpGet); InputStream inStream = conn. getInputStream (); // The length of the array actually represents the size of the stream downloaded each time. // if it is too small, for example, 1024, each time, only bytes of content will be downloaded, which is too slow. // for files that are downloaded for dozens of megabytes It is too hard to say. If it is too small, it is equivalent to a speed limit. // but it cannot be too large. If it is too large, the data in the buffer will be too large, as a result, oom // in order not to enable the maximum speed for oom, the available content of the application can be obtained here and int freeMemory = (int) Runtime can be dynamically allocated. getRuntime (). freeMemory (); // obtain the remaining available memory of the application byte [] buffer = new byte [freeMemory/threadNum]; // The available memory should be evenly allocated to several threads. // byte [] buffer = new byte [1024]; int len = 0; int total = 0; boolean isInterrupted = false; while (len = inStream. read (buffer ))! =-1) {accessFile. write (buffer, 0, len); total + = len; Log. I ("download", "thread id:" + threadid + "downloaded" + total + "total" + block); // real-time update progress listener. onDownload (threadid, len, total, url); // when the thread is suggested to be interrupted, exit the loop and terminate the download operationIf (Thread. interrupted () {isInterrupted = true; break ;}} InStream. close (); accessFile. close (); if (isInterrupted) {Log. I ("download", "thread id:" + threadid + "download stopped");} else {Log. I ("download", "thread id:" + threadid + "download completed") ;}} catch (Exception e) {e. printStackTrace ();}}
When I exit the application to the background, I will stop the download. No, I just don't want to write the button any more, and I can write it myself.
Here, I tried to interrupt all threads through shutdownNow () in the Thread pool. In fact, it is not an interrupt, but after this method is called, the Thread in the Thread. the interrupted () method returns true, and then I exit the loop through break; to interrupt the download.
@ Overrideprotected void onStop () {super. onStop (); // when the application returns to the background, the download pool. shutdownNow (); dialog. dismiss ();}
Interface callback
In theory, the update progress should not be updated in real time. sqlite is essentially a file. It is too resource-consuming to open and close files frequently, therefore, in the actual project, the progress should be updated only in special circumstances such as user suspension or network disconnection.
@ Overridepublic void onDownload (int threadId, int process, int completed, String url) {// update progress to the database. Theoretically, the progress should not be updated in real time, // sqlite is essentially a file, and frequent opening and closing of the file is too resource-consuming. // in actual projects, you should update the progress of DBService only in special circumstances such as user suspension or network disconnection. getInstance (getApplicationContext ()). updataInfos (threadId, completed, url); Message msg = handler. obtainMessage (); msg. arg1 = process; handler. sendMessage (msg );}
DBService. java
Package com. huxq. multhreaddownload; import java. util. arrayList; import java. util. list; import android. content. context; import android. database. cursor; import android. database. sqlite. SQLiteDatabase; import android. util. log; public class DBService {private DBHelper dbHelper; private static DBService instance; private DBService (Context context) {dbHelper = new DBHelper (context);}/*** Singleton mode, you don't have to use it again ** @ pa Ram context * @ return */public static DBService getInstance (Context context) {if (instance = null) {synchronized (DBService. class) {if (instance = null) {instance = new DBService (context); return instance ;}} return instance ;} /*** check whether data exists in the database */public boolean isHasInfors (String urlstr) {SQLiteDatabase database = dbHelper. getReadableDatabase (); String SQL = "select count (*) from download_info where Url =? "; Cursor cursor = database. rawQuery (SQL, new String [] {urlstr}); cursor. moveToFirst (); int count = cursor. getInt (0); Log. I ("count", "count =" + count); cursor. close (); return count = 0;}/*** Save the download details */public void saveInfos (List
Infos) {SQLiteDatabase database = dbHelper. getWritableDatabase (); for (DownloadInfo info: infos) {String SQL = "insert into download_info (thread_id, start_pos," + "end_pos, compelete_size, url) values (?,?,?,?,?) "; Object [] bindArgs = {info. getThreadId (), info. getStartPos (), info. getEndPos (), info. getCompeleteSize (), info. getUrl () ;;database.exe cSQL (SQL, bindArgs) ;}}/*** get download details */public List
GetInfos (String urlstr) {List
List = new ArrayList
(); SQLiteDatabase database = dbHelper. getReadableDatabase (); String SQL = "select thread_id, start_pos, end_pos, compelete_size, url" + "from download_info where url =? "; Cursor cursor = database. rawQuery (SQL, new String [] {urlstr}); while (cursor. moveToNext () {DownloadInfo info = new DownloadInfo (cursor. getInt (0), cursor. getInt (1), cursor. getInt (2), cursor. getInt (3), cursor. getString (4); list. add (info);} cursor. close (); return list ;} /*** obtain the download progress of a thread with a specific ID ** @ param id * @ param url * @ return */public synchronized int getInfoByIdAndUrl (int id, String url) {SQLiteData Base database = dbHelper. getReadableDatabase (); String SQL = "select compelete_size" + "from download_info where thread_id =? And url =? "; Cursor cursor = database. rawQuery (SQL, new String [] {id +" ", url}); if (cursor! = Null & cursor. moveToFirst () {Log. I ("count", "thread id =" + id + "completed =" + cursor. getInt (0); return cursor. getInt (0);} return 0;}/*** update the download information in the database */public synchronized void updataInfos (int threadId, int compeleteSize, String urlstr) {SQLiteDatabase database = dbHelper. getReadableDatabase (); // update if it exists. If it does not exist, insert String SQL = "replace into download_info" + "(compelete_size, thread_id, url) values (?,?,?) "; Object [] bindArgs = {compeleteSize, threadId, urlstr failed database.exe cSQL (SQL, bindArgs);}/*** close database */public void closeDb () {dbHelper. close ();}/*** delete database data after download */public void delete (String url) {SQLiteDatabase database = dbHelper. getReadableDatabase (); int count = database. delete ("download_info", "url =? ", New String [] {url}); Log. I ("delete", "delete count =" + count); database. close ();} public void saveOrUpdateInfos () {} public synchronized void deleteByIdAndUrl (int id, String url) {SQLiteDatabase database = dbHelper. getReadableDatabase (); int count = database. delete ("download_info", "thread_id =? And url =? ", New String [] {id +" ", url}); Log. I ("delete", "delete id =" + id + "," + "count =" + count); database. close ();}}
It took me some time to write these things, because there are also a lot of things involved. I will post a DEMO at last. If you are interested, you can check it out. If you have any questions, please leave a message or contact me for further discussion.