Android使用多線(xiàn)程實(shí)現(xiàn)斷點(diǎn)下載
多線(xiàn)程下載是加快下載速度的一種方式,通過(guò)開(kāi)啟多個(gè)線(xiàn)程去執(zhí)行一個(gè)任務(wù)..可以使任務(wù)的執(zhí)行速度變快..多線(xiàn)程的任務(wù)下載時(shí)常都會(huì)使用得到..比如說(shuō)我們手機(jī)內(nèi)部應(yīng)用寶的下載機(jī)制..一定是通過(guò)使用了多線(xiàn)程創(chuàng)建的下載器..并且這個(gè)下載器可以實(shí)現(xiàn)斷點(diǎn)下載..在任務(wù)被強(qiáng)行終止之后..下次可以通過(guò)觸發(fā)按鈕來(lái)完成斷點(diǎn)下載...那么如何實(shí)現(xiàn)斷點(diǎn)下載這就是一個(gè)問(wèn)題了..
首先我們需要明確一點(diǎn)就是多線(xiàn)程下載器通過(guò)使用多個(gè)線(xiàn)程對(duì)同一個(gè)任務(wù)進(jìn)行下載..但是這個(gè)多線(xiàn)程并不是線(xiàn)程的數(shù)目越多,下載的速度就越快..當(dāng)線(xiàn)程增加的很多的時(shí)候,單個(gè)線(xiàn)程執(zhí)行效率也會(huì)變慢..因此線(xiàn)程的數(shù)目需要有一個(gè)限度..經(jīng)過(guò)樓主親自測(cè)試..多線(xiàn)程下載同一個(gè)任務(wù)時(shí),線(xiàn)程的數(shù)目5-8個(gè)算是比較高效的..當(dāng)線(xiàn)程的數(shù)量超過(guò)10個(gè)之后,那么多線(xiàn)程的效率反而就變慢了(前提:網(wǎng)速大體相同的時(shí)候..)
那么在實(shí)現(xiàn)多線(xiàn)程下載同一個(gè)任務(wù)的時(shí)候我們需要明白其中的道理..下面先看一張附加圖..
這個(gè)圖其實(shí)就很簡(jiǎn)單的說(shuō)明了其中的原理..我們將一個(gè)任務(wù)分成多個(gè)部分..然后開(kāi)啟多個(gè)線(xiàn)程去下載對(duì)應(yīng)的部分就可以了..那么首先要解決的問(wèn)題就是如何使各自的線(xiàn)程去下載各自對(duì)應(yīng)的任務(wù),不能越界..那么我們來(lái)看看具體的實(shí)現(xiàn)過(guò)程..首先我們使用普通Java代碼去實(shí)現(xiàn)..最后將Java代碼移植到Android就可以了..
public class Download { public static int threadCount = 5; //線(xiàn)程開(kāi)啟的數(shù)量.. public static void main(String[] args) { // TODO Auto-generated method stub String path = "http://192.168.199.172:8080/jdk.exe"; try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); int status = conn.getResponseCode(); if(status == 200){ int length = conn.getContentLength(); System.out.println(length); int blocksize = length/threadCount; //將文件長(zhǎng)度進(jìn)行平分.. for(int threadID=1; threadID<=threadCount;threadID++){ int startIndex = (threadID-1)*blocksize; //開(kāi)始位置的求法.. int endIndex = threadID*blocksize -1; //結(jié)束位置的求法.. /** * 如果一個(gè)文件的長(zhǎng)度無(wú)法整除線(xiàn)程數(shù).. * 那么最后一個(gè)線(xiàn)程下載的結(jié)束位置需要設(shè)置文件末尾.. * */ if(threadID == threadCount){ endIndex = length; } System.out.println("線(xiàn)程下載位置:"+startIndex+"---"+endIndex); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
這樣只是實(shí)現(xiàn)了通過(guò)連接服務(wù)器來(lái)獲取文件的長(zhǎng)度..然后去設(shè)置每一個(gè)線(xiàn)程下載的開(kāi)始位置和結(jié)束位置..這里只是完成了這些步驟..有了下載的開(kāi)始位置和結(jié)束位置..我們就需要開(kāi)啟線(xiàn)程來(lái)完成下載了...因此我們需要自己定義下載的過(guò)程...
首先我們需要明確思路:既然是斷點(diǎn)下載..那么如果一旦發(fā)生了斷點(diǎn)情況..我們?cè)谙乱淮芜M(jìn)行下載的時(shí)候需要從原來(lái)斷掉的位置進(jìn)行下載..已經(jīng)下載過(guò)的位置我們就不需要進(jìn)行下載了..因此我們需要記載每一次的下載記錄..那么有了記錄之后..一旦文件下載完成之后..這些記錄就需要被清除掉...因此明確了這兩個(gè)地方的思路就很容易書(shū)寫(xiě)了..
//下載線(xiàn)程的定義.. public static class DownLoadThread implements Runnable{ private int threadID; private int startIndex; private int endIndex; private String path; public DownLoadThread(int threadID,int startIndex,int endIndex,String path){ this.threadID = threadID; this.startIndex = startIndex; this.endIndex = endIndex; this.path = path; } @Override public void run() { // TODO Auto-generated method stub try { //判斷上一次是否下載完畢..如果沒(méi)有下載完畢需要繼續(xù)進(jìn)行下載..這個(gè)文件記錄了上一次的下載位置.. File tempfile =new File(threadID+".txt"); if(tempfile.exists() && tempfile.length()>0){ FileInputStream fis = new FileInputStream(tempfile); byte buffer[] = new byte[1024]; int leng = fis.read(buffer); int downlength = Integer.parseInt(new String(buffer,0,leng));//從上次下載后的位置開(kāi)始下載..重新擬定開(kāi)始下載的位置.. startIndex = downlength; fis.close(); } URL url = new URL(path); HttpURLConnection conn =(HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); int status = conn.getResponseCode(); //206也表示服務(wù)器響應(yīng)成功.. if(status == 206){ //獲取服務(wù)器返回的I/O流..然后將數(shù)據(jù)寫(xiě)入文件當(dāng)中.. InputStream in = conn.getInputStream(); //文件寫(xiě)入開(kāi)始..用來(lái)保存當(dāng)前需要下載的文件.. RandomAccessFile raf = new RandomAccessFile("jdk.exe", "rwd"); raf.seek(startIndex); int len = 0; byte buf[] =new byte[1024]; //記錄已經(jīng)下載的長(zhǎng)度.. int total = 0; while((len = in.read(buf))!=-1){ //用于記錄當(dāng)前下載的信息.. RandomAccessFile file =new RandomAccessFile(threadID+".txt", "rwd"); total += len; file.write((total+startIndex+"").getBytes()); file.close(); //將數(shù)據(jù)寫(xiě)入文件當(dāng)中.. raf.write(buf, 0, len); } in.close(); raf.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //如果所有的線(xiàn)程全部下載完畢后..也就是任務(wù)完成..清除掉所有原來(lái)的記錄文件.. runningThread -- ; if(runningThread==0){ for(int i=1;i<threadCount;i++){ File file = new File(i+".txt"); file.delete(); } } } } }
這樣就完成了文件的數(shù)據(jù)信息的下載..經(jīng)過(guò)測(cè)試..一個(gè)13M的文件在5個(gè)線(xiàn)程共同作用下下載的時(shí)間差不多是12秒左右(網(wǎng)速穩(wěn)定在300k的情況下..帶寬越寬..速度就會(huì)更快)單個(gè)線(xiàn)程下載的時(shí)間差不多是15秒左右..這里才縮短了兩秒鐘的時(shí)間..但是我們不要忘記..如果文件過(guò)大的話(huà)呢?因此樓主親測(cè)了一下一個(gè)90M的文件在5個(gè)線(xiàn)程同時(shí)作用下時(shí)間差不多1分20秒左右..而使用一個(gè)線(xiàn)程進(jìn)行下載差不多2分鐘左右..這里還是縮短了大量的時(shí)間..
因此根據(jù)對(duì)比,還是使用多個(gè)線(xiàn)程來(lái)進(jìn)行下載更加的好一些..雖然Android里的一般應(yīng)用不會(huì)超過(guò)50M左右..但是游戲的話(huà)一般差不多能達(dá)到100-200M左右..因此使用多線(xiàn)程還是能夠提高下載的進(jìn)度和效率..同樣我們可以通過(guò)使用線(xiàn)程池的方式去開(kāi)啟線(xiàn)程..最后這些線(xiàn)程交給線(xiàn)程池去管理就可以了..
在正常的Java項(xiàng)目中我們書(shū)寫(xiě)好了下載代碼..就可以移植到我們的Android應(yīng)用程序當(dāng)中..但是還是有一些地方需要注意..因此在這里去強(qiáng)調(diào)一下..
package com.example.mutithread; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.app.Activity; import android.text.TextUtils; import android.view.Menu; import android.view.View; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText et; private ProgressBar pb; public static int threadCount = 5; public static int runningThread=5; public int currentProgress=0; //當(dāng)前進(jìn)度值.. private TextView tv; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg){ switch (msg.what) { case 1: Toast.makeText(getApplicationContext(), msg.obj.toString(), 0).show(); break; case 2: break; case 3: tv.setText("當(dāng)前進(jìn)度:"+(pb.getProgress()*100)/pb.getMax()); default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et = (EditText) findViewById(R.id.et); pb =(ProgressBar) findViewById(R.id.pg); tv= (TextView) findViewById(R.id.tv); } public void downLoad(View v){ final String path = et.getText().toString().trim(); if(TextUtils.isEmpty(path)){ Toast.makeText(this, "下載路徑錯(cuò)誤", Toast.LENGTH_LONG).show(); return ; } new Thread(){ // String path = "http://192.168.199.172:8080/jdk.exe"; public void run(){ try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); int status = conn.getResponseCode(); if(status == 200){ int length = conn.getContentLength(); System.out.println("文件總長(zhǎng)度"+length); pb.setMax(length); RandomAccessFile raf = new RandomAccessFile("/sdcard/setup.exe","rwd"); raf.setLength(length); raf.close(); //開(kāi)啟5個(gè)線(xiàn)程來(lái)下載當(dāng)前資源.. int blockSize = length/threadCount ; for(int threadID=1;threadID<=threadCount;threadID++){ int startIndex = (threadID-1)*blockSize; int endIndex = threadID*blockSize -1; if(threadID == threadCount){ endIndex = length; } System.out.println("線(xiàn)程"+threadID+"下載:---"+startIndex+"--->"+endIndex); new Thread(new DownLoadThread(threadID, startIndex, endIndex, path)).start() ; } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); } /** * 下載線(xiàn)程.. * */ public class DownLoadThread implements Runnable{ private int ThreadID; private int startIndex; private int endIndex; private String path; public DownLoadThread(int ThreadID,int startIndex,int endIndex,String path){ this.ThreadID = ThreadID; this.startIndex = startIndex; this.endIndex = endIndex; this.path = path; } @Override public void run() { // TODO Auto-generated method stub URL url; try { //檢查是否存在還未下載完成的文件... File tempfile = new File("/sdcard/"+ThreadID+".txt"); if(tempfile.exists() && tempfile.length()>0){ FileInputStream fis = new FileInputStream(tempfile); byte temp[] =new byte[1024]; int leng = fis.read(temp); int downlength = Integer.parseInt(new String(temp,0,leng)); int alreadydown = downlength -startIndex; currentProgress += alreadydown;//發(fā)生斷點(diǎn)之后記錄下載的文件長(zhǎng)度.. startIndex = downlength; fis.close(); } url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); conn.setConnectTimeout(5000); //獲取響應(yīng)碼.. int status =conn.getResponseCode(); if(status == 206){ InputStream in = conn.getInputStream(); RandomAccessFile raf =new RandomAccessFile("/sdcard/jdk.exe", "rwd"); //文件開(kāi)始寫(xiě)入.. raf.seek(startIndex); int len =0; byte[] buffer =new byte[1024]; //已經(jīng)下載的數(shù)據(jù)長(zhǎng)度.. int total = 0; while((len = in.read(buffer))!=-1){ //記錄當(dāng)前數(shù)據(jù)下載的長(zhǎng)度... RandomAccessFile file = new RandomAccessFile("/sdcard/"+ThreadID+".txt", "rwd"); raf.write(buffer, 0, len); total += len; System.out.println("線(xiàn)程"+ThreadID+"total:"+total); file.write((total+startIndex+"").getBytes()); file.close(); synchronized (MainActivity.this) { currentProgress += len; //獲取當(dāng)前總進(jìn)度... //progressBar progressDialog可以直接在子線(xiàn)程內(nèi)部更新UI..由于源碼內(nèi)部進(jìn)行了特殊的處理.. pb.setProgress(currentProgress); //更改界面上的進(jìn)度條進(jìn)度.. Message msg =Message.obtain(); //復(fù)用以前的消息..避免多次new... msg.what = 3; handler.sendMessage(msg); } } raf.close(); in.close(); System.out.println("線(xiàn)程:"+ThreadID+"下載完畢"); }else{ System.out.println("線(xiàn)程:"+ThreadID+"下載失敗"); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); Message msg = new Message(); msg.what = 1; msg.obj = e; handler.sendMessage(msg); }finally{ synchronized (MainActivity.this) { runningThread--; if(runningThread == 0){ for(int i=1;i<=threadCount;i++){ File file = new File("/sdcard/"+i+".txt"); file.delete(); } Message msg =new Message(); msg.what = 2; msg.obj ="下載完畢"; handler.sendMessage(msg); } } } } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
源代碼如上..優(yōu)化的事情我就不做了..為了方便直接就貼上了..這里定義了一個(gè)ProgressBar進(jìn)度條..一個(gè)TextView來(lái)同步進(jìn)度條的下載進(jìn)度..在Android中我們自然不能夠在主線(xiàn)程中去調(diào)用耗時(shí)間的操作..因此這些耗時(shí)的操作我們就通過(guò)開(kāi)啟子線(xiàn)程的方式去使用..但是子線(xiàn)程是不能夠更新UI界面的..因此我們需要使用到Handler Message機(jī)制來(lái)完成主界面UI更新的操作.
但是上面的代碼當(dāng)中我們會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題..在子線(xiàn)程內(nèi)部居然更新了ProgressBar操作..其實(shí)ProgressBar和ProgressDialog是兩個(gè)特例..我們可以在子線(xiàn)程內(nèi)部去更新他們的屬性..我們來(lái)看一下源代碼的實(shí)現(xiàn)過(guò)程..
private synchronized void refreshProgress(int id, int progress, boolean fromUser) { if (mUiThreadId == Thread.currentThread().getId()) { //如果當(dāng)前運(yùn)行的線(xiàn)程和主線(xiàn)程相同..那么更新進(jìn)度條.. doRefreshProgress(id, progress, fromUser, true); } else { //如果不滿(mǎn)足上面說(shuō)的情況.. if (mRefreshProgressRunnable == null) { mRefreshProgressRunnable = new RefreshProgressRunnable();//那么新建立一個(gè)線(xiàn)程..然后執(zhí)行下面的過(guò)程.. } final RefreshData rd = RefreshData.obtain(id, progress, fromUser); //獲取消息隊(duì)列中的消息.. mRefreshData.add(rd); if (mAttached && !mRefreshIsPosted) { post(mRefreshProgressRunnable); //主要是這個(gè)地方..調(diào)用了post方法..將當(dāng)前運(yùn)行的線(xiàn)程發(fā)送到消息隊(duì)列當(dāng)中..那么這個(gè)線(xiàn)程就可以在UI中運(yùn)行了..因此這一步是決定因素.. mRefreshIsPosted = true; } } }
正是由于源碼內(nèi)部調(diào)用了post方法..將當(dāng)前的線(xiàn)程放入到消息隊(duì)列當(dāng)中..那么UI中的Looper線(xiàn)程就會(huì)對(duì)這個(gè)線(xiàn)程進(jìn)行處理,那么就表示這個(gè)線(xiàn)程是可以被執(zhí)行在UI當(dāng)中的..也正是這個(gè)因素導(dǎo)致了我們可以在子線(xiàn)程內(nèi)部更新ProgressBar..但是我們可以看到如果我們想要去更新TextView的時(shí)候..我們就需要調(diào)用Handler Message機(jī)制來(lái)完成UI界面的更新了..因此這一塊需要我們?nèi)プ⒁狻?br /> 移植之后代碼其實(shí)并沒(méi)有發(fā)生太大的變化,這樣就可以完成一個(gè)在Android中的多線(xiàn)程斷點(diǎn)下載器了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家學(xué)習(xí)Android軟件編程有所幫助。
- Android 斷點(diǎn)下載和自動(dòng)安裝的示例代碼
- android多線(xiàn)程斷點(diǎn)下載-帶進(jìn)度條和百分比進(jìn)度顯示效果
- Android HttpURLConnection斷點(diǎn)下載(單線(xiàn)程)
- Android原生實(shí)現(xiàn)多線(xiàn)程斷點(diǎn)下載實(shí)例代碼
- 詳解Android中的多線(xiàn)程斷點(diǎn)下載
- Android入門(mén):多線(xiàn)程斷點(diǎn)下載詳細(xì)介紹
- Android實(shí)現(xiàn)斷點(diǎn)下載的方法
- Android實(shí)現(xiàn)多線(xiàn)程斷點(diǎn)下載的方法
- Android實(shí)現(xiàn)斷點(diǎn)多線(xiàn)程下載
相關(guān)文章
Android入門(mén)之使用SQLite內(nèi)嵌式數(shù)據(jù)庫(kù)詳解
Android內(nèi)帶SQLite內(nèi)嵌式數(shù)據(jù)庫(kù)了。這對(duì)于我們存儲(chǔ)一些更復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)帶來(lái)了極大的便利。本文就來(lái)和大家聊聊具體的使用方法,希望對(duì)大家有所幫助2022-12-12Android10 App 啟動(dòng)分析進(jìn)程創(chuàng)建源碼解析
這篇文章主要為大家介紹了Android10 App啟動(dòng)分析進(jìn)程創(chuàng)建源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Android中Json數(shù)據(jù)讀取與創(chuàng)建的方法
android 讀取json數(shù)據(jù),下面小編給大家整理有關(guān)Android中Json數(shù)據(jù)讀取與創(chuàng)建的方法,需要的朋友可以參考下2015-08-08Android 通過(guò)API獲取數(shù)據(jù)庫(kù)中的圖片文件方式
這篇文章主要介紹了Android 通過(guò)API獲取數(shù)據(jù)庫(kù)中的圖片文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Android利用ViewPager實(shí)現(xiàn)滑動(dòng)廣告板實(shí)例源碼
利用ViewPager我們可以做很多事情,從最簡(jiǎn)單的導(dǎo)航,到頁(yè)面切換菜單等等。ViewPager的功能就是可以使視圖滑動(dòng),就像Lanucher左右滑動(dòng)那樣2013-06-06Android onKeyDown監(jiān)聽(tīng)返回鍵無(wú)效的解決辦法
這篇文章主要介紹了 Android onKeyDown監(jiān)聽(tīng)返回鍵無(wú)效的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06Android生成隨機(jī)數(shù)的方法實(shí)例
這篇文章主要為大家詳細(xì)介紹了Android生成隨機(jī)數(shù)的方法實(shí)例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03