斷點(diǎn)續(xù)傳:指的是在上傳/下載時(shí),將任務(wù)(一個(gè)文件或壓縮包)人為的劃分為幾個(gè)部分,每一個(gè)部分采用一個(gè)線程進(jìn)行上傳/下載,如果碰到網(wǎng)絡(luò)故障,可以從已經(jīng)上傳/下載的部分開始繼續(xù)上傳/下載未完成的部分,而沒(méi)有必要從頭開始上傳/下載??梢怨?jié)省時(shí)間,提高速度。
斷點(diǎn)續(xù)傳和斷點(diǎn)下載都是用的RandomAccessFile, 它具有移動(dòng)指定的文件大小的位置的功能seek 。
斷點(diǎn)續(xù)傳的用途
有時(shí)用戶上傳/下載文件需要?dú)v時(shí)數(shù)小時(shí),萬(wàn)一線路中斷,不具備斷點(diǎn)續(xù)傳的 HTTP/FTP 服務(wù)器或下載軟件就只能從頭重傳,比較好的 HTTP/FTP 服務(wù)器或下載軟件具有斷點(diǎn)續(xù)傳能力,允許用戶從上傳/下載斷線的地方繼續(xù)傳送,這樣大大減少了用戶的煩惱。
常見的支持?jǐn)帱c(diǎn)續(xù)傳的上傳/下載軟件:QQ 旋風(fēng)、迅雷、快車、電驢、酷6、土豆、優(yōu)酷、百度視頻、新浪視頻、騰訊視頻、百度云等。
在 Linux/Unix 系統(tǒng)下,常用支持?jǐn)帱c(diǎn)續(xù)傳的 FTP 客戶端軟件是 lftp。
斷點(diǎn)續(xù)傳是由服務(wù)器給客戶端一個(gè)已經(jīng)上傳的位置標(biāo)記position,然后客戶端再將文件指針移動(dòng)到相應(yīng)的position,通過(guò)輸入流將文件剩余部分讀出來(lái)傳輸給服務(wù)器
斷點(diǎn)下載 是由客戶端告訴服務(wù)器已經(jīng)下載的大小,然后服務(wù)器會(huì)將指針移動(dòng)到相應(yīng)的position,繼續(xù)讀出,把文件返回給客戶端。 當(dāng)然為了下載的更快一下,也可以多線程下載,那么基本實(shí)現(xiàn)就是給每個(gè)線程分配固定的字節(jié)的文件,分別去讀
下面是用自己編的一個(gè)"瀏覽器"來(lái)傳遞請(qǐng)求信息給 Web 服務(wù)器,要求從 2000070 字節(jié)開始。
GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
仔細(xì)看一下就會(huì)發(fā)現(xiàn)多了一行 RANGE: bytes=2000070-
這一行的意思就是告訴服務(wù)器 down.zip 這個(gè)文件從 2000070 字節(jié)開始傳,前面的字節(jié)不用傳了。
服務(wù)器收到這個(gè)請(qǐng)求以后,返回的信息如下:
206
Content-Length=106786028
Content-Range=bytes 2000070-106786027/106786028
Date=Mon, 30 Apr 2001 12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT
和前面服務(wù)器返回的信息比較一下,就會(huì)發(fā)現(xiàn)增加了一行:
Content-Range=bytes 2000070-106786027/106786028
返回的代碼也改為 206 了,而不再是 200 了。
知道了以上原理,就可以進(jìn)行斷點(diǎn)續(xù)傳的編程了。
首先是文件上傳,這個(gè)要用到服務(wù)器
關(guān)鍵代碼:
FileServer.java
Java代碼
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.io.RandomAccessFile; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import util.FileLogInfo; import util.StreamTool; public class FileServer { private ExecutorService executorService;//線程池 private int port;//監(jiān)聽端口 private boolean quit = false;//退出 private ServerSocket server; private Map<Long, FileLogInfo> datas = new HashMap<Long, FileLogInfo>();//存放斷點(diǎn)數(shù)據(jù),以后改為數(shù)據(jù)庫(kù)存放 public FileServer(int port) { this.port = port; //創(chuàng)建線程池,池中具有(cpu個(gè)數(shù)*50)條線程 executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50); } /** * 退出 */ public void quit() { this.quit = true; try { server.close(); }catch (IOException e) { e.printStackTrace(); } } /** * 啟動(dòng)服務(wù) * @throws Exception */ public void start() throws Exception { server = new ServerSocket(port);//實(shí)現(xiàn)端口監(jiān)聽 while(!quit) { try { Socket socket = server.accept(); executorService.execute(new SocketTask(socket));//為支持多用戶并發(fā)訪問(wèn),采用線程池管理每一個(gè)用戶的連接請(qǐng)求 }catch (Exception e) { e.printStackTrace(); } } } private final class SocketTask implements Runnable { private Socket socket = null; public SocketTask(Socket socket) { this.socket = socket; } @Override public void run() { try { System.out.println("FileServer accepted connection "+ socket.getInetAddress()+ ":"+ socket.getPort()); //得到客戶端發(fā)來(lái)的第一行協(xié)議數(shù)據(jù):Content-Length=143253434;filename=xxx.3gp;sourceid= //如果用戶初次上傳文件,sourceid的值為空。 InputStream inStream = socket.getInputStream(); String head = StreamTool.readLine(inStream); System.out.println("FileServer head:"+head); if(head!=null) { //下面從協(xié)議數(shù)據(jù)中提取各項(xiàng)參數(shù)值 String[] items = head.split(";"); String filelength = items[0].substring(items[0].indexOf("=")+1); String filename = items[1].substring(items[1].indexOf("=")+1); String sourceid = items[2].substring(items[2].indexOf("=")+1); //生成資源id,如果需要唯一性,可以采用UUID long id = System.currentTimeMillis(); FileLogInfo log = null; if(sourceid!=null && !"".equals(sourceid)) { id = Long.valueOf(sourceid); //查找上傳的文件是否存在上傳記錄 log = find(id); } File file = null; int position = 0; //如果上傳的文件不存在上傳記錄,為文件添加跟蹤記錄 if(log==null) { //設(shè)置存放的位置與當(dāng)前應(yīng)用的位置有關(guān) File dir = new File("c:/temp/"); if(!dir.exists()) dir.mkdirs(); file = new File(dir, filename); //如果上傳的文件發(fā)生重名,然后進(jìn)行改名 if(file.exists()) { filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf(".")); file = new File(dir, filename); } save(id, file); } // 如果上傳的文件存在上傳記錄,讀取上次的斷點(diǎn)位置 else { System.out.println("FileServer have exits log not null"); //從上傳記錄中得到文件的路徑 file = new File(log.getPath()); if(file.exists()) { File logFile = new File(file.getParentFile(), file.getName()+".log"); if(logFile.exists()) { Properties properties = new Properties(); properties.load(new FileInputStream(logFile)); //讀取斷點(diǎn)位置 position = Integer.valueOf(properties.getProperty("length")); } } } //***************************上面是對(duì)協(xié)議頭的處理,下面正式接收數(shù)據(jù)*************************************** //向客戶端請(qǐng)求傳輸數(shù)據(jù) OutputStream outStream = socket.getOutputStream(); String response = "sourceid="+ id+ ";position="+ position+ "%"; //服務(wù)器收到客戶端的請(qǐng)求信息后,給客戶端返回響應(yīng)信息:sourceid=1274773833264;position=position //sourceid由服務(wù)生成,唯一標(biāo)識(shí)上傳的文件,position指示客戶端從文件的什么位置開始上傳 outStream.write(response.getBytes()); RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd"); //設(shè)置文件長(zhǎng)度 if(position==0) fileOutStream.setLength(Integer.valueOf(filelength)); //移動(dòng)文件指定的位置開始寫入數(shù)據(jù) fileOutStream.seek(position); byte[] buffer = new byte[1024]; int len = -1; int length = position; //從輸入流中讀取數(shù)據(jù)寫入到文件中,并將已經(jīng)傳入的文件長(zhǎng)度寫入配置文件,實(shí)時(shí)記錄文件的最后保存位置 while( (len=inStream.read(buffer)) != -1) { fileOutStream.write(buffer, 0, len); length += len; Properties properties = new Properties(); properties.put("length", String.valueOf(length)); FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log")); //實(shí)時(shí)記錄文件的最后保存位置 properties.store(logFile, null); logFile.close(); } //如果長(zhǎng)傳長(zhǎng)度等于實(shí)際長(zhǎng)度則表示長(zhǎng)傳成功 if(length==fileOutStream.length()){ delete(id); } fileOutStream.close(); inStream.close(); outStream.close(); file = null; } } catch (Exception e) { e.printStackTrace(); } finally{ try { if(socket!=null && !socket.isClosed()) socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 查找在記錄中是否有sourceid的文件 * @param sourceid * @return */ public FileLogInfo find(Long sourceid) { return datas.get(sourceid); } /** * 保存上傳記錄,日后可以改成通過(guò)數(shù)據(jù)庫(kù)存放 * @param id * @param saveFile */ public void save(Long id, File saveFile) { System.out.println("save logfile "+id); datas.put(id, new FileLogInfo(id, saveFile.getAbsolutePath())); } /** * 當(dāng)文件上傳完畢,刪除記錄 * @param sourceid */ public void delete(long sourceid) { System.out.println("delete logfile "+sourceid); if(datas.containsKey(sourceid)) datas.remove(sourceid); } }
由于在上面的流程圖中已經(jīng)進(jìn)行了詳細(xì)的分析,我在這兒就不講了,只是在存儲(chǔ)數(shù)據(jù)的時(shí)候服務(wù)器沒(méi)有用數(shù)據(jù)庫(kù)去存儲(chǔ),這兒只是為了方便,所以要想測(cè)試斷點(diǎn)上傳,服務(wù)器是不能停的,否則數(shù)據(jù)就沒(méi)有了,在以后改進(jìn)的時(shí)候應(yīng)該用數(shù)據(jù)庫(kù)去存儲(chǔ)數(shù)據(jù)。
文件上傳客戶端:
關(guān)鍵代碼:
UploadActivity.java
Java代碼
package com.hao; import java.io.File; import java.util.List; import com.hao.upload.UploadThread; import com.hao.upload.UploadThread.UploadProgressListener; import com.hao.util.ConstantValues; import com.hao.util.FileBrowserActivity; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; /** * * @author Administrator * */ public class UploadActivity extends Activity implements OnClickListener{ private static final String TAG = "SiteFileFetchActivity"; private Button download, upload, select_file; private TextView info; private static final int PROGRESS_DIALOG = 0; private ProgressDialog progressDialog; private UploadThread uploadThread; private String uploadFilePath = null; private String fileName; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.upload); initView(); } private void initView(){ download = (Button) findViewById(R.id.download); download.setOnClickListener(this); upload = (Button) findViewById(R.id.upload); upload.setOnClickListener(this); info = (TextView) findViewById(R.id.info); select_file = (Button) findViewById(R.id.select_file); select_file.setOnClickListener(this); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == 1) { Uri uri = data.getData(); // 接收用戶所選文件的路徑 info.setText("select: " + uri); // 在界面上顯示路徑 uploadFilePath = uri.getPath(); int last = uploadFilePath.lastIndexOf("/"); uploadFilePath = uri.getPath().substring(0, last+1); fileName = uri.getLastPathSegment(); } } } protected Dialog onCreateDialog(int id) { switch(id) { case PROGRESS_DIALOG: progressDialog = new ProgressDialog(UploadActivity.this); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setButton("暫停", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub uploadThread.closeLink(); dialog.dismiss(); } }); progressDialog.setMessage("正在上傳..."); progressDialog.setMax(100); return progressDialog; default: return null; } } /** * 使用Handler給創(chuàng)建他的線程發(fā)送消息, * 匿名內(nèi)部類 */ private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { //獲得上傳長(zhǎng)度的進(jìn)度 int length = msg.getData().getInt("size"); progressDialog.setProgress(length); if(progressDialog.getProgress()==progressDialog.getMax())//上傳成功 { progressDialog.dismiss(); Toast.makeText(UploadActivity.this, getResources().getString(R.string.upload_over), 1).show(); } } }; @Override public void onClick(View v) { // TODO Auto-generated method stub Resources r = getResources(); switch(v.getId()){ case R.id.select_file: Intent intent = new Intent(); //設(shè)置起始目錄和查找的類型 intent.setDataAndType(Uri.fromFile(new File("/sdcard")), "*/*");//"*/*"表示所有類型,設(shè)置起始文件夾和文件類型 intent.setClass(UploadActivity.this, FileBrowserActivity.class); startActivityForResult(intent, 1); break; case R.id.download: startActivity(new Intent(UploadActivity.this, SmartDownloadActivity.class)); break; case R.id.upload: if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))//判斷SDCard是否存在 { if(uploadFilePath == null){ Toast.makeText(UploadActivity.this, "還沒(méi)設(shè)置上傳文件", 1).show(); } System.out.println("uploadFilePath:"+uploadFilePath+" "+fileName); //取得SDCard的目錄 File uploadFile = new File(new File(uploadFilePath), fileName); Log.i(TAG, "filePath:"+uploadFile.toString()); if(uploadFile.exists()) { showDialog(PROGRESS_DIALOG); info.setText(uploadFile+" "+ConstantValues.HOST+":"+ConstantValues.PORT); progressDialog.setMax((int) uploadFile.length());//設(shè)置長(zhǎng)傳文件的最大刻度 uploadThread = new UploadThread(UploadActivity.this, uploadFile, ConstantValues.HOST, ConstantValues.PORT); uploadThread.setListener(new UploadProgressListener() { @Override public void onUploadSize(int size) { // TODO Auto-generated method stub Message msg = new Message(); msg.getData().putInt("size", size); handler.sendMessage(msg); } }); uploadThread.start(); } else { Toast.makeText(UploadActivity.this, "文件不存在", 1).show(); } } else { Toast.makeText(UploadActivity.this, "SDCard不存在!", 1).show(); } break; } } }
UploadThread.java
Java代碼
package com.hao.upload; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.Socket; import android.content.Context; import android.util.Log; import com.hao.db.UploadLogService; import com.hao.util.StreamTool; public class UploadThread extends Thread { private static final String TAG = "UploadThread"; /*需要上傳文件的路徑*/ private File uploadFile; /*上傳文件服務(wù)器的IP地址*/ private String dstName; /*上傳服務(wù)器端口號(hào)*/ private int dstPort; /*上傳socket鏈接*/ private Socket socket; /*存儲(chǔ)上傳的數(shù)據(jù)庫(kù)*/ private UploadLogService logService; private UploadProgressListener listener; public UploadThread(Context context, File uploadFile, final String dstName,final int dstPort){ this.uploadFile = uploadFile; this.dstName = dstName; this.dstPort = dstPort; logService = new UploadLogService(context); } public void setListener(UploadProgressListener listener) { this.listener = listener; } /** * 模擬斷開連接 */ public void closeLink(){ try{ if(socket != null) socket.close(); }catch(IOException e){ e.printStackTrace(); Log.e(TAG, "close socket fail"); } } @Override public void run() { // TODO Auto-generated method stub try { // 判斷文件是否已有上傳記錄 String souceid = logService.getBindId(uploadFile); // 構(gòu)造拼接協(xié)議 String head = "Content-Length=" + uploadFile.length() + ";filename=" + uploadFile.getName() + ";sourceid=" + (souceid == null ? "" : souceid) + "%"; // 通過(guò)Socket取得輸出流 socket = new Socket(dstName, dstPort); OutputStream outStream = socket.getOutputStream(); outStream.write(head.getBytes()); Log.i(TAG, "write to outStream"); InputStream inStream = socket.getInputStream(); // 獲取到字符流的id與位置 String response = StreamTool.readLine(inStream); Log.i(TAG, "response:" + response); String[] items = response.split(";"); String responseid = items[0].substring(items[0].indexOf("=") + 1); String position = items[1].substring(items[1].indexOf("=") + 1); // 代表原來(lái)沒(méi)有上傳過(guò)此文件,往數(shù)據(jù)庫(kù)添加一條綁定記錄 if (souceid == null) { logService.save(responseid, uploadFile); } RandomAccessFile fileOutStream = new RandomAccessFile(uploadFile, "r"); // 查找上次傳送的最終位置,并從這開始傳送 fileOutStream.seek(Integer.valueOf(position)); byte[] buffer = new byte[1024]; int len = -1; // 初始化上傳的數(shù)據(jù)長(zhǎng)度 int length = Integer.valueOf(position); while ((len = fileOutStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); // 設(shè)置長(zhǎng)傳數(shù)據(jù)長(zhǎng)度 length += len; listener.onUploadSize(length); } fileOutStream.close(); outStream.close(); inStream.close(); socket.close(); // 判斷上傳完則刪除數(shù)據(jù) if (length == uploadFile.length()) logService.delete(uploadFile); } catch (Exception e) { e.printStackTrace(); } } public interface UploadProgressListener{ void onUploadSize(int size); } }
下面是多線程下載
SmartDownloadActivity.java
Java代碼
package com.hao; import java.io.File; import com.hao.R; import com.hao.R.id; import com.hao.R.layout; import com.hao.download.SmartFileDownloader; import com.hao.download.SmartFileDownloader.SmartDownloadProgressListener; import com.hao.util.ConstantValues; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; /** * * @author Administrator * */ public class SmartDownloadActivity extends Activity { private ProgressBar downloadbar; private TextView resultView; private String path = ConstantValues.DOWNLOAD_URL; SmartFileDownloader loader; private Handler handler = new Handler() { @Override // 信息 public void handleMessage(Message msg) { switch (msg.what) { case 1: int size = msg.getData().getInt("size"); downloadbar.setProgress(size); float result = (float) downloadbar.getProgress() / (float) downloadbar.getMax(); int p = (int) (result * 100); resultView.setText(p + "%"); if (downloadbar.getProgress() == downloadbar.getMax()) Toast.makeText(SmartDownloadActivity.this, "下載成功", 1).show(); break; case -1: Toast.makeText(SmartDownloadActivity.this, msg.getData().getString("error"), 1).show(); break; } } }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.download); Button button = (Button) this.findViewById(R.id.button); Button closeConn = (Button) findViewById(R.id.closeConn); closeConn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(loader != null){ finish(); }else{ Toast.makeText(SmartDownloadActivity.this, "還沒(méi)有開始下載,不能暫停", 1).show(); } } }); downloadbar = (ProgressBar) this.findViewById(R.id.downloadbar); resultView = (TextView) this.findViewById(R.id.result); resultView.setText(path); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { download(path, ConstantValues.FILE_PATH); } else { Toast.makeText(SmartDownloadActivity.this, "沒(méi)有SDCard", 1).show(); } } }); } // 對(duì)于UI控件的更新只能由主線程(UI線程)負(fù)責(zé),如果在非UI線程更新UI控件,更新的結(jié)果不會(huì)反映在屏幕上,某些控件還會(huì)出錯(cuò) private void download(final String path, final File dir) { new Thread(new Runnable() { @Override public void run() { try { loader = new SmartFileDownloader(SmartDownloadActivity.this, path, dir, 3); int length = loader.getFileSize();// 獲取文件的長(zhǎng)度 downloadbar.setMax(length); loader.download(new SmartDownloadProgressListener() { @Override public void onDownloadSize(int size) {// 可以實(shí)時(shí)得到文件下載的長(zhǎng)度 Message msg = new Message(); msg.what = 1; msg.getData().putInt("size", size); handler.sendMessage(msg); } }); } catch (Exception e) { Message msg = new Message();// 信息提示 msg.what = -1; msg.getData().putString("error", "下載失敗");// 如果下載錯(cuò)誤,顯示提示失??! handler.sendMessage(msg); } } }).start();// 開始 } }
這個(gè)單個(gè)的下載線程
SmartDownloadThread.java
Java代碼
package com.hao.download; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.util.Log; /** * 線程下載 * @author Administrator * */ public class SmartDownloadThread extends Thread { private static final String TAG = "SmartDownloadThread"; private File saveFile; private URL downUrl; private int block; /* *下載開始位置 */ private int threadId = -1; private int downLength; private boolean finish = false; private SmartFileDownloader downloader; public SmartDownloadThread(SmartFileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) { this.downUrl = downUrl; this.saveFile = saveFile; this.block = block; this.downloader = downloader; this.threadId = threadId; this.downLength = downLength; } @Override public void run() { if (downLength < block) {// 未下載完成 try { HttpURLConnection http = (HttpURLConnection) downUrl .openConnection(); http.setConnectTimeout(5 * 1000); http.setRequestMethod("GET"); http.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, */*"); http.setRequestProperty("Accept-Language", "zh-CN"); http.setRequestProperty("Referer", downUrl.toString()); http.setRequestProperty("Charset", "UTF-8"); int startPos = block * (threadId - 1) + downLength;// 開始位置 int endPos = block * threadId - 1;// 結(jié)束位置 http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);// 設(shè)置獲取實(shí)體數(shù)據(jù)的范圍 http.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.4506.2152; .NET CLR 3.5.30729)"); http.setRequestProperty("Connection", "Keep-Alive"); InputStream inStream = http.getInputStream(); byte[] buffer = new byte[1024]; int offset = 0; print("Thread " + this.threadId + " start download from position " + startPos); RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd"); threadfile.seek(startPos); while ((offset = inStream.read(buffer, 0, 1024)) != -1) { threadfile.write(buffer, 0, offset); downLength += offset; downloader.update(this.threadId, downLength); downloader.saveLogFile(); downloader.append(offset); } threadfile.close(); inStream.close(); print("Thread " + this.threadId + " download finish"); this.finish = true; } catch (Exception e) { this.downLength = -1; print("Thread " + this.threadId + ":" + e); } } } private static void print(String msg) { Log.i(TAG, msg); } /** * 下載是否完成 * @return */ public boolean isFinish() { return finish; } /** * 已經(jīng)下載的內(nèi)容大小 * @return 如果返回值為-1,代表下載失敗 */ public long getDownLength() { return downLength; } }
總得下載線程
SmartFileDownloader.java
Java代碼
package com.hao.download; import java.io.File; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.hao.db.DownloadFileService; import android.content.Context; import android.util.Log; /** * 文件下載主程序 * @author Administrator * */ public class SmartFileDownloader { private static final String TAG = "SmartFileDownloader"; private Context context; private DownloadFileService fileService; /* 已下載文件長(zhǎng)度 */ private int downloadSize = 0; /* 原始文件長(zhǎng)度 */ private int fileSize = 0; /*原始文件名*/ private String fileName; /* 線程數(shù) */ private SmartDownloadThread[] threads; /* 本地保存文件 */ private File saveFile; /* 緩存各線程下載的長(zhǎng)度 */ private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(); /* 每條線程下載的長(zhǎng)度 */ private int block; /* 下載路徑 */ private String downloadUrl; /** * 獲取文件名 */ public String getFileName(){ return this.fileName; } /** * 獲取線程數(shù) */ public int getThreadSize() { return threads.length; } /** * 獲取文件大小 * @return */ public int getFileSize() { return fileSize; } /** * 累計(jì)已下載大小 * @param size */ protected synchronized void append(int size) { downloadSize += size; } /** * 更新指定線程最后下載的位置 * @param threadId 線程id * @param pos 最后下載的位置 */ protected void update(int threadId, int pos) { this.data.put(threadId, pos); } /** * 保存記錄文件 */ protected synchronized void saveLogFile() { this.fileService.update(this.downloadUrl, this.data); } /** * 構(gòu)建文件下載器 * @param downloadUrl 下載路徑 * @param fileSaveDir 文件保存目錄 * @param threadNum 下載線程數(shù) */ public SmartFileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) { try { this.context = context; this.downloadUrl = downloadUrl; fileService = new DownloadFileService(this.context); URL url = new URL(this.downloadUrl); if (!fileSaveDir.exists()) fileSaveDir.mkdirs(); this.threads = new SmartDownloadThread[threadNum]; HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); 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("Accept-Language", "zh-CN"); conn.setRequestProperty("Referer", downloadUrl); conn.setRequestProperty("Charset", "UTF-8"); 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.4506.2152; .NET CLR 3.5.30729)"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.connect(); printResponseHeader(conn); if (conn.getResponseCode() == 200) { this.fileSize = conn.getContentLength();// 根據(jù)響應(yīng)獲取文件大小 if (this.fileSize <= 0) throw new RuntimeException("Unkown file size "); fileName = getFileName(conn); this.saveFile = new File(fileSaveDir, fileName);/* 保存文件 */ Map<Integer, Integer> logdata = fileService.getData(downloadUrl); if (logdata.size() > 0) { for (Map.Entry<Integer, Integer> entry : logdata.entrySet()) data.put(entry.getKey(), entry.getValue()); } //劃分每個(gè)線程下載文件長(zhǎng)度 this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1; if (this.data.size() == this.threads.length) { for (int i = 0; i < this.threads.length; i++) { this.downloadSize += this.data.get(i + 1); } print("已經(jīng)下載的長(zhǎng)度" + this.downloadSize); } } else { throw new RuntimeException("server no response "); } } catch (Exception e) { print(e.toString()); throw new RuntimeException("don't connection this url"); } } /** * 獲取文件名 */ private String getFileName(HttpURLConnection conn) { String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);//鏈接的最后一個(gè)/就是文件名 if (filename == null || "".equals(filename.trim())) {// 如果獲取不到文件名稱 for (int i = 0;; i++) { String mine = conn.getHeaderField(i); print("ConnHeader:"+mine+" "); if (mine == null) break; if ("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())) { Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase()); if (m.find()) return m.group(1); } } filename = UUID.randomUUID() + ".tmp";// 默認(rèn)取一個(gè)文件名 } return filename; } /** * 開始下載文件 * * @param listener * 監(jiān)聽下載數(shù)量的變化,如果不需要了解實(shí)時(shí)下載的數(shù)量,可以設(shè)置為null * @return 已下載文件大小 * @throws Exception */ public int download(SmartDownloadProgressListener listener) throws Exception { try { RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw"); if (this.fileSize > 0) randOut.setLength(this.fileSize); randOut.close(); URL url = new URL(this.downloadUrl); if (this.data.size() != this.threads.length) { this.data.clear();// 清除數(shù)據(jù) for (int i = 0; i < this.threads.length; i++) { this.data.put(i + 1, 0); } } for (int i = 0; i < this.threads.length; i++) { int downLength = this.data.get(i + 1); if (downLength < this.block && this.downloadSize < this.fileSize) { // 該線程未完成下載時(shí),繼續(xù)下載 this.threads[i] = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i + 1), i + 1); this.threads[i].setPriority(7); this.threads[i].start(); } else { this.threads[i] = null; } } this.fileService.save(this.downloadUrl, this.data); boolean notFinish = true;// 下載未完成 while (notFinish) {// 循環(huán)判斷是否下載完畢 Thread.sleep(900); notFinish = false;// 假定下載完成 for (int i = 0; i < this.threads.length; i++) { if (this.threads[i] != null && !this.threads[i].isFinish()) { notFinish = true;// 下載沒(méi)有完成 if (this.threads[i].getDownLength() == -1) {// 如果下載失敗,再重新下載 this.threads[i] = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i + 1), i + 1); this.threads[i].setPriority(7); this.threads[i].start(); } } } if (listener != null) listener.onDownloadSize(this.downloadSize); } fileService.delete(this.downloadUrl); } catch (Exception e) { print(e.toString()); throw new Exception("file download fail"); } return this.downloadSize; } /** * 獲取Http響應(yīng)頭字段 * * @param http * @return */ public static Map<String, String> getHttpResponseHeader( HttpURLConnection http) { Map<String, String> header = new LinkedHashMap<String, String>(); for (int i = 0;; i++) { String mine = http.getHeaderField(i); if (mine == null) break; header.put(http.getHeaderFieldKey(i), mine); } return header; } /** * 打印Http頭字段 * * @param http */ public static void printResponseHeader(HttpURLConnection http) { Map<String, String> header = getHttpResponseHeader(http); for (Map.Entry<String, String> entry : header.entrySet()) { String key = entry.getKey() != null ? entry.getKey() + ":" : ""; print(key + entry.getValue()); } } // 打印日志 private static void print(String msg) { Log.i(TAG, msg); } public interface SmartDownloadProgressListener { public void onDownloadSize(int size); } }
如對(duì)本文有疑問(wèn),請(qǐng)?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會(huì)為你解答!! 點(diǎn)擊進(jìn)入論壇