Zero, preface,
1. The previous article implemented a single thread single file download, this article will talk about multiple files multithreaded download, before this hope you first understand the previous article
2. This paper will use technologies other than the previous part: multithreading, thread pool (simple), RecyclerView, attention points under database multithreading access, Volatile AtomicLong(simple)
The final static effect
The final dynamic effect
First, analyze the principle of multithreading to download a single file:
1. Thread division
As we all know, a file is made up of many bytes, and bytes are made up of binary bits. If you think of a byte as a brick. So downloading is like putting the brick of the server into the phone, and then putting it in a file, and then when it's done, the file is full, the task is done and then the file is a movie that can be played, a picture that can be viewed, an APP that can be installed. For downloading a file, the single-threaded download described in the previous article is equivalent to a person moving it piece by piece. This multithreading is to hire several people to move, you can imagine the efficiency is higher. How about if I open a thousand threads in a second? If you need to move 1,000 bricks and hire 1,000 people, you will be efficient, but they will not work for nothing. Compared to moving three people, you will pay 333 times more wages, that is, the cost of starting a thread, just the right amount.Copy the code
The loss of a single byte can cause a file to be corrupted, so it is conceivable that multiple people working together must have a clear division of labor
Otherwise, if one brick is removed incorrectly, the entire file will be scrapped. Let’s look at the division of labor of threads. Take three threads to download 1000 bytes for example:
2. Flow chart of multithreaded download
The overall architecture is similar to single-threaded downloads, with the biggest changes:
Since multiple threads need to be managed, a DownLoadTask is used to manage all the download threads of a file, which encapsulates the download and pause logic. In the DownLoadTask#download method, if the database has no information, the task is assigned to the thread, the thread information is created, and the database is inserted. DownLoadThread is an internal class of the DownLoadTask for easy use. Finally, create and enable downloadthreads one by one in the Download method, store downloadthreads into the collection manager, and close all threads in the collection in the DownLoadTask#pause methodCopy the code
Two, code implementation:
1. Use of RecyclerView:
RecyclerView will be a single entry into a list interface
1). Increase URL constants
Public static final String URL_JUEJIN = "Https://imtt.dd.qq.com/16891/4611E43165D203CB6A52E65759FE7641.apk?fsname=com.daimajia.gold_5.6.2_196.apk&csr=1bbd". Public static final String URL_QQ = "Https://qd.myapp.com/myapp/qqteam/Androidlite/qqlite_3.7.1.704_android_r110206_GuanWang_537057973_release_10000484.apk" ; Public static final String URL_YOUDAO = "Http://codown.youdao.com/note/youdaonote_android_6.3.5_youdaoweb.apk". Public static final String URL_WEIXIN = "http://gdown.baidu.com/data/wisegame/3d4de3ae1d2dc7d5/weixin_1360.apk"; // Public static final String IDENTITY = "http://codown.youdao.com/dictmobile/youdaodict_android_youdaoweb.apk";Copy the code
2). Initialize data
Private ArrayList<FileBean> initData() {FileBean juejin = new FileBean(0, 0); Cons.URL_JUEJIN, "nuggets. Apk ", 0, 0); FileBean yunbiji = new FileBean(1, cons. URL_YOUDAO, "Youdao Cloud Note.apk ", 0, 0); FileBean qq = new FileBean(2, Cons.URL_QQ, "QQ.apk", 0, 0); FileBean weiChat = new FileBean(3, cons. URL_WEIXIN, 0, 0); FileBean cidian = new FileBean (4, Cons. URL_YOUDAO_CIDIAN, youdao dictionary. Apk, 0, 0). ArrayList<FileBean> fileBeans = new ArrayList<>(); fileBeans.add(juejin); fileBeans.add(yunbiji); fileBeans.add(qq); fileBeans.add(weiChat); fileBeans.add(cidian); return fileBeans; }Copy the code
3). RecyclerView adapter
The download and pause intents implemented in the buttons in the Activity in the previous article are stored in the RVAdapter
<br/> * Time: 2018/11/13 0013:11:58<br/> * Email: [email protected]<br/> * Description: RecyclerView public class RVAdapter extends RecyclerView.Adapter<MyViewHolder> {private Context mContext; private List<FileBean> mData; public RVAdapter(Context context, List<FileBean> data) { mContext = context; mData = data; } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(R.layout.item_pb, parent, false); View.setonclicklistener (v -> {//TODO click entry}); return new MyViewHolder(view); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { FileBean fileBean = mData.get(position); Holder. MBtnStart. SetOnAlphaListener (v - > {ToastUtil. ShowAtOnce (mContext, "start download:" + fileBean. GetFileName ()); Intent intent = new Intent(mContext, DownLoadService.class); intent.setAction(Cons.ACTION_START); intent.putExtra(Cons.SEND_FILE_BEAN, fileBean); // Use an intent to carry the object McOntext.startservice (Intent); // Start service -- download tag}); holder.mBtnStop.setOnAlphaListener(v -> { Intent intent = new Intent(mContext, DownLoadService.class); intent.setAction(Cons.ACTION_STOP); intent.putExtra(Cons.SEND_FILE_BEAN, fileBean); // Use an intent to carry the object McOntext.startservice (Intent); Toastutil.showatonce (mContext, "stop downloading:" + filebean.getfilename ()); }); holder.mTVFileName.setText(fileBean.getFileName()); holder.mPBH.setProgress((int) fileBean.getLoadedLen()); holder.mPBV.setProgress((int) fileBean.getLoadedLen()); } @Override public int getItemCount() { return mData.size(); } /** * updateProgress * @param id file id * @param progress progress */ public void updateProgress(int id, int progress) { mData.get(id).setLoadedLen(progress); notifyDataSetChanged(); }} /** * ViewHolder extends recyclerView. ViewHolder {public ProgressBar mPBH; public ProgressBar mPBV; public AlphaImageView mBtnStart; public AlphaImageView mBtnStop; public TextView mTVFileName; public MyViewHolder(View itemView) { super(itemView); mPBH = itemView.findViewById(R.id.id_pb_h); mPBV = itemView.findViewById(R.id.id_pb_v); mBtnStart = itemView.findViewById(R.id.id_btn_start); mBtnStop = itemView.findViewById(R.id.id_btn_stop); mTVFileName = itemView.findViewById(R.id.id_tv_file_name); }}Copy the code
4). Set the adapter in MainActivity
mAdapter = new RVAdapter(this, fileBeans);
mIdRvPage.setAdapter(mAdapter);
mIdRvPage.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
Copy the code
2.DownLoadTask analysis:
The most important thing about DownLoadTask is that it manages all the threads for downloading a file. Download is an exposed download method. Pause to stop.
For example, if you open three threads, the class’s mDownLoadThreads store the threads in the collection so that you can use DownLoadThread and the core logic of the previous article. This class is used as an internal DownLoadTask class to make it easier to use its variables. In addition, Timer Timer is used to send progress. Sending progress in DownLoadThread will result in inconsistent progress in several threads, which will affect vision
- Three threads work together
- Database status while paused
<br/> * Time: 2018/11/13 0013:15:21<br/> * Email: [email protected]<br/> * Description: */ public class DownLoadTask {private FileBean mFileBean; // Download file information private DownLoadDao mDao; // Data access interface private Context mContext; // context private int mThreadCount; // Number of threads public Boolean isDownLoading; Private Timer mTimer; // timer private List<DownLoadThread> mDownLoadThreads; ---- Use volatile and Atomic to synchronize private Volatile AtomicLong mLoadedLen = new AtomicLong(); mLoadedLen = new AtomicLong(); / / using the thread pool public static ExecutorService sExe = Executors. NewCachedThreadPool (); public DownLoadTask(FileBean fileBean, Context context, int threadCount) { mFileBean = fileBean; mContext = context; mThreadCount = threadCount; mDao = new DownLoadDaoImpl(context); mDownLoadThreads = new ArrayList<>(); mTimer = new Timer(); List<ThreadBean> threads = mdao.getThreads (mFileBean.geturl ()); If (threads. The size () = = 0) {/ / if there is no thread information, the new thread information / / -- -- -- -- -- - download length for each process long len = mFileBean. GetLength ()/mThreadCount; for (int i = 0; i < mThreadCount; I++) {// create threadCount ThreadBean ThreadBean = null; if (i ! = mThreadCount - 1) { threadBean = new ThreadBean( i, mFileBean.getUrl(), len * i, (i + 1) * len - 1, 0); } else { threadBean = new ThreadBean( i, mFileBean.getUrl(), len * i, mFileBean.getLength(), 0); Thread.add (threadBean);} threadBean (threadBean); Mdao.insertthread (threadBean); mdao.insertThread (threadBean); }} // Start multiple threads for (ThreadBean info: Threads) {DownLoadThread thread = new DownLoadThread(info); // Create the download thread sexe.execute (thread); // Start thread thread.isDownLoading = true; isDownLoading = true; mDownLoadThreads.add(thread); } mtimer.schedule (new TimerTask() {Override public void run() {Intent Intent = new Intent(Cons.ACTION_UPDATE); Intent mContext. SendBroadcast (intent); intent.putExtra(Cons.SEND_LOADED_PROGRESS, (int) (mLoadedLen.get() * 100 / mFileBean.getLength())); intent.putExtra(Cons.SEND_FILE_ID, mFileBean.getId()); mContext.sendBroadcast(intent); }}, 1000, 1000); } public void pause() { for (DownLoadThread downLoadThread : mDownLoadThreads) { downLoadThread.isDownLoading = false; isDownLoading = false; Public class DownLoadThread extends Thread {private ThreadBean mThreadBean; // Downloading thread information public Boolean isDownLoading; Public DownLoadThread(ThreadBean ThreadBean) {mThreadBean = ThreadBean; } @Override public void run() { if (mThreadBean == null) {//1. Download thread information is empty, return directly; } 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); Mloadedlen.set (mloadeDlen.get () + mthreadBean.getLoadedlen ()); //206----- If (conn.getresponsecode () == 206) {// Read data is = conn.getinputStream (); byte[] buf = new byte[1024 * 4]; int len = 0; while ((len = is.read(buf)) ! // write the file raf.write(buf, 0, len); Mloadedlen.set (mloadeDlen.get () + len); SetLoadedLen (mThreadbean.getLoadedlen () + len); // Pause the save progress to the database if (! this.isDownLoading) { mDao.updateThread(mThreadBean.getUrl(), mThreadBean.getId(), mThreadBean.getLoadedLen()); return; }}} // Check whether downloading is complete. IsDownLoading = false; checkIsAllOK(); } 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(); }}} /** * Check whether all threads have completed */ private synchronized void checkIsAllOK() {Boolean allFinished = true; for (DownLoadThread downLoadThread : mDownLoadThreads) { if (downLoadThread.isDownLoading) { allFinished = false; break; } } if (allFinished) { mTimer.cancel(); Mdao.deletethread (mThreadbean.geturl ()); mthreadbean.geturl (); Intent Intent = new Intent(); intent.setAction(Cons.ACTION_FINISH); // Add Action intent. PutExtra (cons. SEND_FILE_BEAN, mFileBean); mContext.sendBroadcast(intent); }}}}Copy the code
3. The Service changes
The slight difference is that one download task becomes multiple downloads, which are stored using android-specific SparseArray
<br/> * Time: 2018/11/12 0012:12:23<br/> * Email: [email protected]<br/> * Description: */ public class DownLoadService extends Service {// Because of multiple files, maintain a collection of tasks: Private SparseArray<DownLoadTask> mTaskMap = new SparseArray<>(); 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())); DownLoadTask task = new DownLoadTask(fileBean, DownLoadService.this, 3); task.download(); mTaskMap.put(fileBean.getId(), task); break; }}}; 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); DownLoadTask start = mTaskMap.get(fileBean.getId()); if (start ! = null) { if (start.isDownLoading) { return super.onStartCommand(intent, flags, startId); } } DownLoadTask.sExe.execute(new LinkURLThread(fileBean, mHandler)); break; case Cons.ACTION_STOP: FileBean stopFile = (FileBean) intent.getSerializableExtra(Cons.SEND_FILE_BEAN); DownLoadTask task = mtaskmap.get (stopfile.getid ()); if (task ! = null) { task.pause(); } break; } } return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { return null; }}Copy the code
4. Processing of broadcasts:
There is an Action that has been downloaded, and the progress bar passed in by MainActivity is changed to MAdapter.updateProgress to refresh the view
<br/> * Time: 2018/11/12 0012:16:05<br/> * Email: [email protected]<br/> * Description: Public class UpdateReceiver extends BroadcastReceiver {private RVAdapter mAdapter; public UpdateReceiver(RVAdapter adapter) { mAdapter = adapter; } @Override public void onReceive(Context context, Intent Intent) {if (cons.action_update.equals (intent.getAction())) intent.getIntExtra(Cons.SEND_LOADED_PROGRESS, 0); int id = intent.getIntExtra(Cons.SEND_FILE_ID, 0); mAdapter.updateProgress(id, loadedProgress); } else if (cons.action_finish.equals (intent.getAction())) { intent.getSerializableExtra(Cons.SEND_FILE_BEAN); mAdapter.updateProgress(fileBean.getId(), 0); Toastutil.showatonce (context, "fileBean.getfilename ()"); }}}Copy the code
Three, multi-threaded operation of the database points to note:
1. DownLoadDBHelper singleton
To avoid different threads getting different DownLoadDBHelper objects, use singleton mode
private static DownLoadDBHelper sDownLoadDBHelper;
public static DownLoadDBHelper newInstance(Context context) {
if (sDownLoadDBHelper == null) {
synchronized (DownLoadDBHelper.class) {
if (sDownLoadDBHelper == null) {
sDownLoadDBHelper = new DownLoadDBHelper(context);
}
}
}
return sDownLoadDBHelper;
}
Copy the code
2. Add synchronization to the change database method: db.DownLoadDaoImpl
Avoid conflicts when multiple threads modify the database
public synchronized void insertThread(ThreadBean threadBean)
public synchronized void deleteThread(String url)
public synchronized void updateThread(String url, int threadId, long loadedLen)
Copy the code
By the end of the first and second chapters, you should be able to achieve something like this:
Looking back, it is not difficult to bear the point, think more, after the train of thought through or very good understanding.
Postscript: Jiwen specification
1. Growth records and errata of this paper
Program source code | The date of | note |
---|---|---|
V0.1, | 2018-11-13 | Android native download (part 2) multi-file download + multi-thread download |
2. More about me
Pen name | 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