断点续传:指的是在上传/下载时,将任务(一个文件或压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。可以节省时间,提高速度。
断点续传和断点下载都是用的RandomAccessFile, 它具有移动指定的文件大小的位置的功能seek 。
断点续传的用途
有时用户上传/下载文件需要历时数小时,万一线路中断,不具备断点续传的 HTTP/FTP 服务器或下载软件就只能从头重传,比较好的 HTTP/FTP 服务器或下载软件具有断点续传能力,允许用户从上传/下载断线的地方继续传送,这样大大减少了用户的烦恼。
常见的支持断点续传的上传/下载软件:QQ 旋风、迅雷、快车、电驴、酷6、土豆、优酷、百度视频、新浪视频、腾讯视频、百度云等。
在 Linux/Unix 系统下,常用支持断点续传的 FTP 客户端软件是 lftp。
断点续传是由服务器给客户端一个已经上传的位置标记position,然后客户端再将文件指针移动到相应的position,通过输入流将文件剩余部分读出来传输给服务器
断点下载 是由客户端告诉服务器已经下载的大小,然后服务器会将指针移动到相应的position,继续读出,把文件返回给客户端。 当然为了下载的更快一下,也可以多线程下载,那么基本实现就是给每个线程分配固定的字节的文件,分别去读
下面是用自己编的一个"浏览器"来传递请求信息给 Web 服务器,要求从 2000070 字节开始。
GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
仔细看一下就会发现多了一行 RANGE: bytes=2000070-
这一行的意思就是告诉服务器 down.zip 这个文件从 2000070 字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:
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
和前面服务器返回的信息比较一下,就会发现增加了一行:
Content-Range=bytes 2000070-106786027/106786028
返回的代码也改为 206 了,而不再是 200 了。
知道了以上原理,就可以进行断点续传的编程了。
首先是文件上传,这个要用到服务器
关键代码:
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;//监听端口 private boolean quit = false;//退出 private ServerSocket server; private Map<Long, FileLogInfo> datas = new HashMap<Long, FileLogInfo>();//存放断点数据,以后改为数据库存放 public FileServer(int port) { this.port = port; //创建线程池,池中具有(cpu个数*50)条线程 executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50); } /** * 退出 */ public void quit() { this.quit = true; try { server.close(); }catch (IOException e) { e.printStackTrace(); } } /** * 启动服务 * @throws Exception */ public void start() throws Exception { server = new ServerSocket(port);//实现端口监听 while(!quit) { try { Socket socket = server.accept(); executorService.execute(new SocketTask(socket));//为支持多用户并发访问,采用线程池管理每一个用户的连接请求 }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()); //得到客户端发来的第一行协议数据: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) { //下面从协议数据中提取各项参数值 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) { //设置存放的位置与当前应用的位置有关 File dir = new File("c:/temp/"); if(!dir.exists()) dir.mkdirs(); file = new File(dir, filename); //如果上传的文件发生重名,然后进行改名 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); } // 如果上传的文件存在上传记录,读取上次的断点位置 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)); //读取断点位置 position = Integer.valueOf(properties.getProperty("length")); } } } //***************************上面是对协议头的处理,下面正式接收数据*************************************** //向客户端请求传输数据 OutputStream outStream = socket.getOutputStream(); String response = "sourceid="+ id+ ";position="+ position+ "%"; //服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=position //sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传 outStream.write(response.getBytes()); RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd"); //设置文件长度 if(position==0) fileOutStream.setLength(Integer.valueOf(filelength)); //移动文件指定的位置开始写入数据 fileOutStream.seek(position); byte[] buffer = new byte[1024]; int len = -1; int length = position; //从输入流中读取数据写入到文件中,并将已经传入的文件长度写入配置文件,实时记录文件的最后保存位置 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")); //实时记录文件的最后保存位置 properties.store(logFile, null); logFile.close(); } //如果长传长度等于实际长度则表示长传成功 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); } /** * 保存上传记录,日后可以改成通过数据库存放 * @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())); } /** * 当文件上传完毕,删除记录 * @param sourceid */ public void delete(long sourceid) { System.out.println("delete logfile "+sourceid); if(datas.containsKey(sourceid)) datas.remove(sourceid); } }
由于在上面的流程图中已经进行了详细的分析,我在这儿就不讲了,只是在存储数据的时候服务器没有用数据库去存储,这儿只是为了方便,所以要想测试断点上传,服务器是不能停的,否则数据就没有了,在以后改进的时候应该用数据库去存储数据。
文件上传客户端:
关键代码:
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给创建他的线程发送消息, * 匿名内部类 */ private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { //获得上传长度的进度 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(); //设置起始目录和查找的类型 intent.setDataAndType(Uri.fromFile(new File("/sdcard")), "*/*");//"*/*"表示所有类型,设置起始文件夹和文件类型 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, "还没设置上传文件", 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());//设置长传文件的最大刻度 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; /*上传文件服务器的IP地址*/ private String dstName; /*上传服务器端口号*/ private int dstPort; /*上传socket链接*/ private Socket socket; /*存储上传的数据库*/ 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); // 构造拼接协议 String head = "Content-Length=" + uploadFile.length() + ";filename=" + uploadFile.getName() + ";sourceid=" + (souceid == null ? "" : souceid) + "%"; // 通过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); // 代表原来没有上传过此文件,往数据库添加一条绑定记录 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; // 初始化上传的数据长度 int length = Integer.valueOf(position); while ((len = fileOutStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); // 设置长传数据长度 length += len; listener.onUploadSize(length); } fileOutStream.close(); outStream.close(); inStream.close(); socket.close(); // 判断上传完则删除数据 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, "还没有开始下载,不能暂停", 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, "没有SDCard", 1).show(); } } }); } // 对于UI控件的更新只能由主线程(UI线程)负责,如果在非UI线程更新UI控件,更新的结果不会反映在屏幕上,某些控件还会出错 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();// 获取文件的长度 downloadbar.setMax(length); loader.download(new SmartDownloadProgressListener() { @Override public void onDownloadSize(int size) {// 可以实时得到文件下载的长度 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", "下载失败");// 如果下载错误,显示提示失败! handler.sendMessage(msg); } } }).start();// 开始 } }
这个单个的下载线程
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;// 结束位置 http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);// 设置获取实体数据的范围 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; } /** * 已经下载的内容大小 * @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; /* 已下载文件长度 */ private int downloadSize = 0; /* 原始文件长度 */ private int fileSize = 0; /*原始文件名*/ private String fileName; /* 线程数 */ private SmartDownloadThread[] threads; /* 本地保存文件 */ private File saveFile; /* 缓存各线程下载的长度 */ private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(); /* 每条线程下载的长度 */ private int block; /* 下载路径 */ private String downloadUrl; /** * 获取文件名 */ public String getFileName(){ return this.fileName; } /** * 获取线程数 */ public int getThreadSize() { return threads.length; } /** * 获取文件大小 * @return */ public int getFileSize() { return fileSize; } /** * 累计已下载大小 * @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); } /** * 构建文件下载器 * @param downloadUrl 下载路径 * @param fileSaveDir 文件保存目录 * @param threadNum 下载线程数 */ 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();// 根据响应获取文件大小 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()); } //划分每个线程下载文件长度 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("已经下载的长度" + 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);//链接的最后一个/就是文件名 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";// 默认取一个文件名 } return filename; } /** * 开始下载文件 * * @param listener * 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为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();// 清除数据 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) { // 该线程未完成下载时,继续下载 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) {// 循环判断是否下载完毕 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;// 下载没有完成 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响应头字段 * * @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); } }
如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛