[Android development experience] A simple implementation and explanation of the [multi-thread resumable download] function, android resumable download
Reprinted please indicate the source: http://blog.csdn.net/zhaokaiqiang1992
On the first day of work, I chatted with you in the Technical Group and accidentally talked about the use of the framework. One person said that the entire library was introduced into the project to use the xUtils feature for resumable download and disconnection, in google's official recommendations, this practice is not recommended. Although the collection framework integrates many functions, the more Code there is, the more likely the problem may occur, in addition, the APK size is added in the invisible form, so the loss is worth the candle. Therefore, this article mainly focuses on the download function of "disconnected and resumable data transfer". Let's briefly talk about the ideas and code implementation. Because there are many such codes, I found a well-written demo, it was simply optimized.
Before you paste the code, analyze the requirements and solutions. The first is the download function. We can simply use HttpURLConnection. There is no need to introduce the framework, and then resumable data transfer, in fact, resumable upload means that we can stop our download tasks at any time. At the next start, we can continue to download tasks from the last download location, saving download time, it is very convenient and practical. It is nothing more than recording the download location during the download process. When the download starts again, we can continue to request the server from the previous location. Speaking of this, there is a class that has to be mentioned, that is, RandomAccessFile, which is the core class for implementing the resumable data transfer function. RandomAccessFile allows us to perform read and write operations from the position we want. Therefore, we can split the file we want to download into several parts, and start multiple threads to download the files from different locations. After all the parts are downloaded, we will be able to get a complete file, which is the principle of multi-threaded download. After completing the above steps, our multi-threaded resumable download function is basically complete, the following is a Demo on the Internet. I have made some modifications to the code. From the code, let's take a look at how to implement the Code.
First, to implement resumable data transfer, we need to record the location of the file downloaded by each thread. You can use the file, sp, or DB, in this Demo, we will first take a look at the database's Helper implementation class, which stores the primary key, thread number, start position, end position, completion position, and so on.
DownLoadHelper. java
Public class DownLoadHelper extends SQLiteOpenHelper {private static final String SQL _NAME = "download. db "; private static final int DOWNLOAD_VERSION = 1; public DownLoadHelper (Context context) {super (context, SQL _NAME, null, DOWNLOAD_VERSION);}/*** in download. create a download_info table under the db database to store the download information */@ Overridepublic void onCreate (SQLiteDatabase db) mongodb.exe cSQL ("create table download_info (_ id integer primary key autoincrement, thread_id integer, "+" start_pos integer, end_pos integer, compelete_size integer, url char) ") ;}@ Overridepublic void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion ){}}
With Helper, we are creating an SQL tool class to complete data operations on the table.
DownlaodSqlTool. java
Public class DownlaodSqlTool {private DownLoadHelper dbHelper; public DownlaodSqlTool (Context context) {dbHelper = new DownLoadHelper (context );} /*** create download details */public void insertInfos (List <DownloadInfo> 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 () Fetch fetch database.exe cSQL (SQL, bindArgs) ;}/ *** get download details */public List <DownloadInfo> getInfos (String urlstr) {List <DownloadInfo> list = new ArrayList <DownloadInfo> (); SQLiteDatabase database = dbHelper. getWritableDatabase (); String SQL = "select thread_id, start_pos, end_pos, compelete_siz E, 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);} return list;}/*** update the download information in the database */public void updataInfos (int threadId, int compeleteSize, String urlstr) {SQLiteDatabase database = dbHelper. getWritableDatabase (); String SQL = "update download_info set compelete_size =? Where thread_id =? And url =? "; 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. getWritableDatabase (); database. delete ("download_info", "url =? ", New String [] {url });}}
Database-related classes. The resumable data transfer function has been completed. The following describes how to implement the download.
First, we extract entity classes from the downloaded files for ease of operation.
DownloadInfo. java
Public class DownloadInfo {private int threadId; // downloader idprivate int startPos; // start point private int endPos; // end point private int compeleteSize; // completion level private String url; // public DownloadInfo (int threadId, int startPos, int endPos, int compeleteSize, String URL) {this. threadId = threadId; this. startPos = startPos; this. endPos = endPos; this. compeleteSize = compeleteSize; this. url = url;} public DownloadInfo () {} public String getUrl () {return url;} public void setUrl (String url) {this. url = url;} public int getThreadId () {return threadId;} public void setThreadId (int threadId) {this. threadId = threadId;} public int getStartPos () {return startPos;} public void setStartPos (int startPos) {this. startPos = startPos;} public int getEndPos () {return endPos;} public void setEndPos (int endPos) {this. endPos = endPos;} public int getCompeleteSize () {return compeleteSize;} public void setCompeleteSize (int compeleteSize) {this. compeleteSize = compeleteSize;} @ Overridepublic String toString () {return "DownloadInfo [threadId =" + threadId + ", startPos =" + startPos + ", endPos =" + endPos + ", compeleteSize = "+ compeleteSize +"] ";}}
After the entity class is extracted, we can implement the download function. DownloadHttpTool is the main class to implement the download function.
DownloadHttpTool. java
Public class DownloadHttpTool {private static final String TAG = DownloadHttpTool. class. getSimpleName (); // Number of threads private int threadCount; // URL address private String urlstr; private Context mContext; private Handler mHandler; // private List of classes for saving download information <DownloadInfo> downloadInfos; // directory private String localPath; // file name private String fileName; private int fileSize; // database operation class private DownlaodSqlTool sqlTool for saving file information; // use Enumeration indicates three download statuses: private enum Download_State {Downloading, Pause, Ready, Delete;} // The current download status is private Download_State state = Download_State.Ready; // The total number of downloads from all threads: private int globalCompelete = 0; public DownloadHttpTool (int threadCount, String urlString, String localPath, String fileName, Context context, Handler handler) {super (); this. threadCount = threadCount; this. urlstr = urlString; this. localPath = localPath; th Is. mContext = context; this. mHandler = handler; this. fileName = fileName; sqlTool = new DownlaodSqlTool (mContext);} // call the ready method to configure public void ready () {Log. w (TAG, "ready"); globalCompelete = 0; downloadInfos = sqlTool. getInfos (urlstr); if (downloadInfos. size () = 0) {initFirst ();} else {File file = new File (localPath + "/" + fileName); if (! File. exists () {sqlTool. delete (urlstr); initFirst ();} else {fileSize = downloadInfos. get (downloadInfos. size ()-1 ). getEndPos (); for (DownloadInfo info: downloadInfos) {globalCompelete + = info. getCompeleteSize ();} Log. w (TAG, "globalCompelete:" + globalCompelete) ;}} public void start () {Log. w (TAG, "start"); if (downloadInfos! = Null) {if (state = Download_State.Downloading) {return;} state = Download_State.Downloading; for (DownloadInfo info: downloadInfos) {Log. v (TAG, "startThread"); new DownloadThread (info. getThreadId (), info. getStartPos (), info. getEndPos (), info. getCompeleteSize (), info. getUrl ()). start () ;}} public void pause () {state = Download_State.Pause; sqlTool. closeDb ();} public void delete () {state = Download_State. Delete; compelete (); new File (localPath + File. separator + fileName ). delete ();} public void compelete () {sqlTool. delete (urlstr); sqlTool. closeDb ();} public int getFileSize () {return fileSize;} public int getCompeleteSize () {return globalCompelete;}/*** initial download */private void initFirst () {Log. w (TAG, "initFirst"); try {URL url = new URL (urlstr); HttpURLConnection connection = (HttpURLConnection) url. openC Onnection (); connection. setConnectTimeout (5000); connection. setRequestMethod ("GET"); fileSize = connection. getContentLength (); Log. w (TAG, "fileSize:" + fileSize); File fileParent = new File (localPath); if (! FileParent. exists () {fileParent. mkdir ();} File file = new File (fileParent, fileName); if (! File. exists () {file. createNewFile ();} // The local access file RandomAccessFile accessFile = new RandomAccessFile (file, "rwd"); accessFile. setLength (fileSize); accessFile. close (); connection. disconnect ();} catch (Exception e) {e. printStackTrace ();} int range = fileSize/threadCount; downloadInfos = new ArrayList <DownloadInfo> (); for (int I = 0; I <threadCount-1; I ++) {DownloadInfo info = new DownloadInfo (I, I * rang E, (I + 1) * range-1, 0, urlstr); downloadInfos. add (info);} DownloadInfo info = new DownloadInfo (threadCount-1, (threadCount-1) * range, fileSize-1, 0, urlstr); downloadInfos. add (info); sqlTool. insertInfos (downloadInfos);}/*** custom download Thread ** @ author zhaokaiqiang * @ time 5:52:28 */private class DownloadThread extends Thread {private int threadId; private int startPos; private int endPos; pr Ivate int compeleteSize; private String urlstr; private int totalThreadSize; public DownloadThread (int threadId, int startPos, int endPos, int compeleteSize, String urlstr) {this. threadId = threadId; this. startPos = startPos; this. endPos = endPos; totalThreadSize = endPos-startPos + 1; this. urlstr = urlstr; this. compeleteSize = compeleteSize;} @ Overridepublic void run () {HttpURLConnection connection = nul L; RandomAccessFile randomAccessFile = null; InputStream is = null; try {randomAccessFile = new RandomAccessFile (localPath + File. separator + fileName, "rwd"); randomAccessFile. seek (startPos + compeleteSize); URL url = new URL (urlstr); connection = (HttpURLConnection) url. openConnection (); connection. setConnectTimeout (5000); connection. setRequestMethod ("GET"); connection. setRequestProperty ("Range", "bytes =" + (StartPos + compeleteSize) + "-" + endPos); is = connection. getInputStream (); byte [] buffer = new byte [1024]; int length =-1; while (length = is. read (buffer ))! =-1) {randomAccessFile. write (buffer, 0, length); compeleteSize + = length; Message message = Message. obtain (); message. what = threadId; message. obj = urlstr; message. arg1 = length; mHandler. sendMessage (message); Log. w (TAG, "Threadid:" + threadId + "compelete:" + compeleteSize + "total:" + totalThreadSize); // when the program is no longer in the download status, record the current download progress if (state! = Download_State.Downloading) | (compeleteSize> = totalThreadSize) {sqlTool. updataInfos (threadId, compeleteSize, urlstr); break ;}} catch (Exception e) {e. printStackTrace (); sqlTool. updataInfos (threadId, compeleteSize, urlstr);} finally {try {if (is! = Null) {is. close ();} randomAccessFile. close (); connection. disconnect ();} catch (Exception e) {e. printStackTrace ();}}}}}
In the above Code, we define a thread for multi-threaded file download, and record the download location and store it in the database when you exit the download status and complete the download. In the original code, the data was not obtained once, and the database was accessed once, which greatly increased the operation frequency of the database and reduced the efficiency. Originally, the file was downloaded about KB, you only need to perform operations on the database once.
In initFirst (), the download object class is initialized and assigned values based on the size of the downloaded file and the number of enabled threads. After RandomAccessFile is created and DownloadInfo is initialized, you can download the file through start.
In fact, the basic functions have been implemented here. To facilitate our operations and monitor the download progress, we encapsulate the download class as follows:
DownloadUtil. java
Public class DownloadUtil {private DownloadHttpTool listener; private OnDownloadListener onDownloadListener; private int fileSize; private int downloadedSize = 0; @ SuppressLint ("HandlerLeak") private Handler mHandler = new Handler () {@ Overridepublic void handleMessage (Message msg) {int length = msg. arg1; synchronized (this) {// lock to ensure downloaded correctness downloadedSize + = length;} if (onDownloadListener! = Null) {onDownloadListener. downloadProgress (downloadedSize);} if (downloadedSize> = fileSize) {mDownloadHttpTool. compelete (); if (onDownloadListener! = Null) {onDownloadListener. downloadEnd () ;}}}; public DownloadUtil (int threadCount, String filePath, String filename, String urlString, Context context) {mDownloadHttpTool = new DownloadHttpTool (threadCount, urlString, filePath, filename, context, mHandler);} // before downloading, the asynchronous thread first calls the ready method to obtain the file size information, and then calls the start method public void start () {new AsyncTask <Void, Void, void> () {@ Overrideprotected Void doInBackground (Void... ar G0) {mDownloadHttpTool. ready (); return null ;}@ Overrideprotected void onPostExecute (Void result) {fileSize = mDownloadHttpTool. getFileSize (); downloadedSize = mDownloadHttpTool. getCompeleteSize (); Log. w ("Tag", "downloadedSize:" + downloadedSize); if (onDownloadListener! = Null) jsonondownloadlistener.downloadstart(filesizeapps{mdownloadhttptool.start({{{cmd.exe cute ();} public void pause () {mDownloadHttpTool. pause ();} public void delete () {mDownloadHttpTool. delete ();} public void reset () {mDownloadHttpTool. delete (); start ();} public void setOnDownloadListener (OnDownloadListener onDownloadListener) {this. onDownloadListener = onDownloadListener;} // download callback interface public interface OnDownloadListener {public void downloadStart (int fileSize); public void downloadProgress (int downloadedSize); public void downloadEnd ();}}
Through the external exposure interface, we can monitor the download progress!
It is also very easy to use, as shown below.
String urlString = "http://bbra.cn/Uploadfiles/imgs/20110303/fengjin/013.jpg";final String localPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/ADownLoadTest";mDownloadUtil = new DownloadUtil(2, localPath, "abc.jpg", urlString,this);mDownloadUtil.setOnDownloadListener(new OnDownloadListener() {@Overridepublic void downloadStart(int fileSize) {max = fileSize;mProgressBar.setMax(fileSize);}@Overridepublic void downloadProgress(int downloadedSize) {mProgressBar.setProgress(downloadedSize);total.setText((int) downloadedSize * 100 / max + "%");}@Overridepublic void downloadEnd() {Bitmap bitmap = decodeSampledBitmapFromResource(localPath+ File.separator + "abc.jpg", 200, 200);image.setImageBitmap(bitmap);}});