Zero, preface,

1. Today is the first part of Android native download, the main core is a breakpoint continuation, multithreaded download will be introduced in the next part

2. This example uses three components: Activity, Service, and BroadcastReceiver 3. This example uses two threads: LinkURLThread for some initial work and DownLoadThread for the core download 4. This example uses SQLite for pausing progress, uses Handler for message delivery, and uses Intent for data delivery 5. In front of the code, I sorted out my ideas and drew a flowchart below, which made my thinking much clearer. 6. This example is more basic, but the series of Many knowledge points of Android, as a summary or very good.

The 2018-11-13 update:

Improved the UI, the whole painting style is different, I feel good, using the previous custom progress bar: see details

Overview of breakpoint continuation logic


First, the preparatory work

Implement the above half of the code first:

1. About download links:

Since it is download, of course there should be a link, the nuggets of APK to test it! Viewing Mode:

2. File information encapsulation class: FileBean
public class FileBean implements Serializable { private int id; // File id private String URL; Private String fileName; // file name private long length; Private long loadedLen; // Constructor, get, set, toString omitted... }Copy the code
2. Constants:Cons.java

Whether it’s an Action added to an Intent, or an indication that the Intent passes data, or that the Handler sends a message

There are certainly a lot of these constants in a project, and they can feel messy if scattered around, so I tend to use a Cons class for consistency

Public static final String SEND_FILE_BEAN = "SEND_FILE_BEAN "; Public static final String SEND_LOADED_PROGRESS = "send_loaded_length"; Public static final String URL = "Https://imtt.dd.qq.com/16891/4611E43165D203CB6A52E65759FE7641.apk?fsname=com.daimajia.gold_5.6.2_196.apk&csr=1bbd". / / file download path public static final String DOWNLOAD_DIR = Environment. External.getexternalstoragedirectory () getAbsolutePath () + "/b_download/"; Public static final int MSG_CREATE_FILE_OK = 0x00; public static final int MSG_CREATE_FILE_OK = 0x00;Copy the code
2. Collaboration between Activity and Service

The interface is relatively simple, I will not stick

1). The Activity:
/** * private void start() {private void start() {private void start() {private void start(); Intent intent = new Intent(MainActivity.this, DownLoadService.class); intent.setAction(Cons.ACTION_START); intent.putExtra(Cons.SEND_FILE_BEAN, fileBean); // Use intent to carry the object startService(Intent); Midtvfilename.settext (filebean.getfilename ())); }Copy the code
Private void stop() {Intent Intent = new Intent(mainactivity.this, downloadService.class); intent.setAction(Cons.ACTION_STOP); startService(intent); // Start service -- stop flag}Copy the code
2).DownLoadService: a downloaded service
Public class DownLoadService extends Service {@override public int onStartCommand(Intent Intent) int flags, int startId) { if (intent.getAction() ! = null) { switch (intent.getAction()) { case Cons.ACTION_START: FileBean fileBean = (FileBean) intent.getSerializableExtra(Cons.SEND_FILE_BEAN); L.d("action_start:" + fileBean + L.l()); break; case Cons.ACTION_STOP: L.d("action_stop:"); break; } } return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { return null; }}Copy the code

Don’t forget to register Service: < Service android:name=”.service.DownLoadService”/>

With the click of two buttons, the test shows that the FileBean object’s pass-through and download start/stop logic are fine


Download the initial thread and use:

1. Implementation of LinkURLThread

1). Connect network files

3). Create a local file of equal size: RandomAccessFile 4). Take a message from mHandler’s message pool and send it to mHandler with the mFileBean and MSG_CREATE_FILE_OK flag

<br/> * Email: [email protected]<br/> * Note: Connect url to do some preparation: get file size. */ public class extends Thread {private FileBean mFileBean; private Handler mHandler; public LinkURLThread(FileBean fileBean, Handler handler) { mFileBean = fileBean; mHandler = handler; } @Override public void run() { HttpURLConnection conn = null; RandomAccessFile raf = null; Try {//1. Connect network file URL URL = new URL(mfilebean.geturl ()); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); if (conn.getResponseCode() == 200) { //2. Len = conn.getContentLength(); len = conn.getContentLength(); if (len > 0) { File dir = new File(Cons.DOWNLOAD_DIR); if (! dir.exists()) { dir.mkdir(); File File = new File(dir, mfilebean.getfilename ()); Raf = new RandomAccessFile(file, "RWD "); raf.setLength(len); // Set the file size mfilebean.setLength (len); //4. Retrieve a message from mHandler's message pool, Send mHandler mHandler.obtainMessage(cons.msg_create_file_OK, mFileBean).sendtotarget (); } } } catch (Exception e) { e.printStackTrace(); } finally { if (conn ! = null) { conn.disconnect(); } try { if (raf ! = null) { raf.close(); } } catch (IOException e) { e.printStackTrace(); }}}}Copy the code
2. Use in Service: DownLoadService

Since the Service also runs on the main thread, the time-consuming operation to access the network is base, so a new thread needs to be opened

Since child threads cannot update the UI, traditional handlers are used for interthread communication

Private Handler Handler = new Handler() {@override public void handleMessage(Message MSG) { switch (msg.what) { case Cons.MSG_CREATE_FILE_OK: FileBean fileBean = (FileBean) msg.obj; ShowAtOnce (DownLoadService. This, "file length :" + fileBean.getLength())); download(fileBean); break; }}}; // Start the thread when downloading the Action: new LinkURLThread(fileBean, mHandler).start();Copy the code

The Handler sends the message to the Service, and then displays the UI in the Service(main thread).


Iii. Database-related operations:

Let’s start with what databases do: record download thread information, information, information!

When suspended, the progress of the current download and thread information is saved to the database, when click start to find thread information from the database, resume the download

1. Thread information encapsulation class: ThreadBean
private int id; // Thread id private String URL; Private long start; // Thread start download location (for multithreading) private long end; Private long loadedLen; // Constructor, get, set, toString omitted...Copy the code
2. Download the database helper class: DownLoadDBHelper

For more information about SQLite, see SI- Android SQLite Basic User Guide:

<br/> * Time: 2018/11/12 0012:14:19<br/> * Email: [email protected]<br/> * Description: */ public class DownLoadDBHelper extends SQLiteOpenHelper {public DownLoadDBHelper(@nullable Context Context)  { super(context, Cons.DB_NAME, null, Cons.VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(Cons.DB_SQL_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(Cons.DB_SQL_DROP); db.execSQL(Cons.DB_SQL_CREATE); }}Copy the code
3. Database constants:Cons.java
/** * public static final String DB_NAME = "download. Db "; Public static final int VERSION = 1; Public static final String DB_TABLE_NAME = "thread_info"; Public static final String DB_SQL_CREATE = // CREATE TABLE "CREATE TABLE "+ DB_TABLE_NAME + "(\n" + "_id INTEGER PRIMARY" KEY AUTOINCREMENT,\n" + "thread_id INTEGER,\n" + "url TEXT,\n" + "start INTEGER,\n" + "end INTEGER,\n" + "loadedLen INTEGER\n" + ")"; Public static final String DB_SQL_DROP =// DROP TABLE IF EXISTS + DB_TABLE_NAME; Public static final String DB_SQL_INSERT = / / INSERT "INSERT INTO" + DB_TABLE_NAME + "(thread_id, url, start, end, loadedLen) values(? ,? ,? ,? ,?) "; Public static final String DB_SQL_DELETE =// DELETE "DELETE FROM "+ DB_TABLE_NAME +" WHERE url =? AND thread_id = ?" ; Public static final String DB_SQL_UPDATE = "UPDATE "+ DB_TABLE_NAME +" SET loadedLen =? WHERE url = ? AND thread_id = ?" ; Public static final String DB_SQL_FIND =// query "SELECT * FROM "+ DB_TABLE_NAME +" WHERE url =? ; Public static final String DB_SQL_FIND_IS_EXISTS =// Check whether "SELECT * FROM "+ DB_TABLE_NAME +" WHERE url =? AND thread_id = ?" ;Copy the code
4. Data access interface: DownLoadDao

Provide database operation interface, as for why a DAO interface, directly with the implementation of the class is not good, here focus on the point

An interface represents a capability guarantee, and the object that implements the class is one of the objects that has that capability. If you're absolutely sure that the implementation won't change (i.e., here's one using SQLite), just use the implementation class. However, if you don't want to save it to the database, but save it to a file or SP, then all the parts related to the implementation class need to be modified, and if scattered all over the place, it doesn't crash. The nice thing about using interfaces is that, whether you're black or white (implementation), just help me catch the rats (problem solving). So you could write a scheme to store thread information in a file, implement the methods in the DAO, and easily switch from the black cat (database implementation) to the white dog (file implementation) just by changing the DAO implementation in the code. Of course you can also prepare a owl (SP), or a deratization cannon (network), so let download logic and decoupling storage logic Do you want to let the white dog in the morning (file operations implementation) catch mice, let white (database) in the afternoon, night owl (SP), is not out of the question This is the benefits of facial interface programming, If you find yourself in a similar situation, where many implementations have their pros and cons, you can always interface with each other and write implementations later to suit your needsCopy the code
<br/> * Time: 2018/11/12 0012:14:36<br/> * Email: [email protected]<br/> * Description: */ public interface DownLoadDao {/** ** insertThread information in the database ** @param threadBean thread information */ void insertThread(threadBean) threadBean); Void deleteThread(String url, int threadId); void deleteThread(String url, int threadId); /** * updateThread information in database -- download progress ** @param url download url * @param threadId */ void updateThread(String url, int threadId ,long loadedLen); List<ThreadBean> getThreads(String url); /* List<ThreadBean> getThreads(String url); ** @param url download url * @param threadId threadId */ Boolean isExist(String url, int threadId); }Copy the code
5. Database interface implementation class: DownLoadDaoImpl

Some basic SQL operations, I’m used to native SQL, don’t forget to close db after each operation, and cursors

<br/> * Time: 2018/11/12 0012:14:43<br/> * Email: [email protected]<br/> * Description: */ public class DownLoadDaoImpl implements DownLoadDao {private DownLoadDBHelper mDBHelper; private Context mContext; public DownLoadDaoImpl(Context context) { mContext = context; mDBHelper = new DownLoadDBHelper(mContext); } @Override public void insertThread(ThreadBean threadBean) { SQLiteDatabase db = mDBHelper.getWritableDatabase(); db.execSQL(Cons.DB_SQL_INSERT, new Object[]{threadBean.getId(), threadBean.getUrl(), threadBean.getStart(), threadBean.getEnd(), threadBean.getLoadedLen()}); db.close(); } @Override public void deleteThread(String url, int threadId) { SQLiteDatabase db = mDBHelper.getWritableDatabase(); db.execSQL(Cons.DB_SQL_DELETE, new Object[]{url, threadId}); db.close(); } @Override public void updateThread(String url, int threadId, long loadedLen) { SQLiteDatabase db = mDBHelper.getWritableDatabase(); db.execSQL(Cons.DB_SQL_UPDATE, new Object[]{loadedLen, url, threadId}); db.close(); } @Override public List<ThreadBean> getThreads(String url) { SQLiteDatabase db = mDBHelper.getWritableDatabase(); Cursor cursor = db.rawQuery(Cons.DB_SQL_FIND, new String[]{url}); List<ThreadBean> threadBeans = new ArrayList<>(); while (cursor.moveToNext()) { ThreadBean threadBean = new ThreadBean(); threadBean.setId(cursor.getInt(cursor.getColumnIndex("thread_id"))); threadBean.setUrl(cursor.getString(cursor.getColumnIndex("url"))); threadBean.setStart(cursor.getLong(cursor.getColumnIndex("start"))); threadBean.setEnd(cursor.getLong(cursor.getColumnIndex("end"))); threadBean.setLoadedLen(cursor.getLong(cursor.getColumnIndex("loadedLen"))); threadBeans.add(threadBean); } cursor.close(); db.close(); return threadBeans; } @Override public boolean isExist(String url, int threadId) { SQLiteDatabase db = mDBHelper.getWritableDatabase(); Cursor cursor = db.rawQuery(Cons.DB_SQL_FIND_IS_EXISTS, new String[]{url, threadId + ""}); boolean exists = cursor.moveToNext(); cursor.close(); db.close(); return exists; }}Copy the code

Core download threads: DownLoadThread and Broadcast progress :BroadcastReceiver

1. Download thread:

If Range is used in the request, the server returns a success status code of 206: not 200, indicating that some content and Range were successful

<br/> * Time: 2018/11/12 0012:15:10<br/> * Email: [email protected]<br/> * Description: */ public class DownLoadThread extends Thread {private ThreadBean mThreadBean; // Download thread information private FileBean mFileBean; Private long mLoadedLen; // Downloading length public Boolean isDownLoading; Private DownLoadDao mDao; // Data access interface private Context mContext; Public DownLoadThread(ThreadBean ThreadBean, FileBean FileBean, Context Context) {mThreadBean = ThreadBean; mDao = new DownLoadDaoImpl(context); mFileBean = fileBean; mContext = context; } @Override public void run() { if (mThreadBean == null) {//1. Download thread information is empty, return directly; } //2. Insert thread information into database if (! mDao.isExist(mThreadBean.getUrl(), mThreadBean.getId())) { mDao.insertThread(mThreadBean); } HttpURLConnection conn = null; RandomAccessFile raf = null; InputStream is = null; Url url = new url (mthreadbean.geturl ()); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); Long start = mthreadbean.getStart () + mthreadbean.getLoadedlen (); Conn.setrequestproperty ("Range", "bytes=" + start + "-" + mThreadbean.getend ()); // Start position //conn Sets the property to mark the location of the resource (which is shown to the server). File File = new File(Cons.DOWNLOAD_DIR, mfilebean.getfilename ()); Raf = new RandomAccessFile(file, "RWD "); raf.seek(start); Intent Intent = new Intent(cons.action_update); Intent mLoadedLen += mthreadbean.getLoadedlen (); //206----- If (conn.getresponsecode () == 206) {// Read data is = conn.getinputStream (); byte[] buf = new byte[1024 * 4]; int len = 0; long time = System.currentTimeMillis(); while ((len = is.read(buf)) ! // write the file raf.write(buf, 0, len); MLoadedLen += len; If (system.currentTimemillis () -time > 500) {// Reduce the rendering speed of the UI McOntext.sendbroadcast (intent); intent.putExtra(Cons.SEND_LOADED_PROGRESS, (int) (mLoadedLen * 100 / mFileBean.getLength())); mContext.sendBroadcast(intent); time = System.currentTimeMillis(); } // Pause the save progress to the database if (! isDownLoading) { mDao.updateThread(mThreadBean.getUrl(), mThreadBean.getId(), mLoadedLen); return; Mdao.deletethread (mThreadbean.geturl (), mThreadbean.getid ())); PutExtra (cons. SEND_LOADED_PROGRESS, 100); mContext.sendBroadcast(intent); } catch (Exception e) { e.printStackTrace(); } finally { if (conn ! = null) { conn.disconnect(); } try { if (raf ! = null) { raf.close(); } if (is ! = null) { is.close(); } } catch (IOException e) { e.printStackTrace(); }}}}Copy the code
3. BroadcastReceiver

Note that this is not limited to BroadcastReceiver, any communication between threads will work, just taking progress from the download thread

<br/> * Time: 2018/11/12 0012:16:05<br/> * Email: [email protected]<br/> * Description: Public class Update Eceiver extends BroadcastReceiver {private ProgressBar[] mProgressBar; public UpdateReceiver(ProgressBar... progressBar) { mProgressBar = progressBar; } @Override public void onReceive(Context context, Intent intent) { if (Cons.ACTION_UPDATE.equals(intent.getAction())) { int progress = intent.getIntExtra(Cons.SEND_LOADED_PROGRESS, 0); for (ProgressBar progressBar : mProgressBar) { progressBar.setProgress(progress); }}}}Copy the code

Five, put the two parts together

1.DownLoadService: Downloads services

The download function is called upon receiving information from the Handler

Public void download(fileBean fileBean) {// List<ThreadBean> threads = mDao.getThreads(fileBean.getUrl()); If (threads.size() == 0) {// If there is no thread information, MThreadBean = new ThreadBean(0, fileBean.geturl (), 0, fileBean.getLength(), 0); } else {threadBean = threads.get(0); } mDownLoadThread = new DownLoadThread(mThreadBean, fileBean, this); // Create a download thread mdownloadthread.start (); / / thread mDownLoadThread. IsDownLoading = true; }Copy the code
2. Start and stop download optimization:
Public int onStartCommand(Intent Intent, int flags, int startId) { mDao = new DownLoadDaoImpl(this); if (intent.getAction() ! = null) { switch (intent.getAction()) { case Cons.ACTION_START: FileBean fileBean = (FileBean) intent.getSerializableExtra(Cons.SEND_FILE_BEAN); if (mDownLoadThread ! = null) { if (mDownLoadThread.isDownLoading) { return super.onStartCommand(intent, flags, startId); } } new LinkURLThread(fileBean, mHandler).start(); break; case Cons.ACTION_STOP: if (mDownLoadThread ! = null) { mDownLoadThread.isDownLoading = false; } break; } } return super.onStartCommand(intent, flags, startId); }Copy the code
3. Register and unregister broadcasts in the Activity
Private void register() {mUpdateReceiver = new UpdateReceiver(mProgressBar,mIdRoundPb); IntentFilter filter = new IntentFilter(); filter.addAction(Cons.ACTION_UPDATE); registerReceiver(mUpdateReceiver, filter); } @Override protected void onDestroy() { super.onDestroy(); if (mUpdateReceiver ! = null) {// unregisterReceiver(mUpdateReceiver); }}Copy the code


After downloading, the installation is normal, the opening is normal, and the download is OK


Postscript: Jiwen specification

1. Growth records and errata of this paper
Program source code The date of note
V0.1, 2018-11-12 Android native download (part 1) Basic logic + breakpoint continuation
V0.1, 2018-11-13 UI Interface optimization
2. More about me
Pen name QQ WeChat hobby
Zhang Fengjie special lie 1981462002 zdl1994328 language
My lot My Jane books My CSDN Personal website
3. The statement

1—- This article was originally written by Zhang Fengjie, please note when reprinted

4—- see here, I thank you for your love and support