断点续传和下载原理分析
最近做一个文件上传和下载的应用对文件上传和下载进行了一个完整的流程分析
以来是方便自己对文件上传和下载的理解,而来便于团队内部的分享
故而做了几张图,将整体的流程都画下来,便于大家的理解和分析,如果有不完善的地方希望
大家多提意见,
由于参考了网上许多的资料,特此感谢
首先是文件上传,这个要用到服务器
关键代码
FileServer.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
1 package com.hao; 2 3 import java.io.File; 4 import java.util.List; 5 6 import com.hao.upload.UploadThread; 7 import com.hao.upload.UploadThread.UploadProgressListener; 8 import com.hao.util.ConstantValues; 9 import com.hao.util.FileBrowserActivity; 10 11 import android.app.Activity; 12 import android.app.Dialog; 13 import android.app.ProgressDialog; 14 import android.content.DialogInterface; 15 import android.content.Intent; 16 import android.content.res.Resources; 17 import android.net.Uri; 18 import android.os.Bundle; 19 import android.os.Environment; 20 import android.os.Handler; 21 import android.os.Message; 22 import android.util.Log; 23 import android.view.View; 24 import android.view.View.OnClickListener; 25 import android.widget.Button; 26 import android.widget.TextView; 27 import android.widget.Toast; 28 /** 29 * 30 * @author Administrator 31 * 32 */ 33 public class UploadActivity extends Activity implements OnClickListener{ 34 private static final String TAG = "SiteFileFetchActivity"; 35 private Button download, upload, select_file; 36 private TextView info; 37 private static final int PROGRESS_DIALOG = 0; 38 private ProgressDialog progressDialog; 39 private UploadThread uploadThread; 40 private String uploadFilePath = null; 41 private String fileName; 42 /** Called when the activity is first created. */ 43 @Override 44 public void onCreate(Bundle savedInstanceState) { 45 super.onCreate(savedInstanceState); 46 setContentView(R.layout.upload); 47 initView(); 48 } 49 50 private void initView(){ 51 download = (Button) findViewById(R.id.download); 52 download.setOnClickListener(this); 53 upload = (Button) findViewById(R.id.upload); 54 upload.setOnClickListener(this); 55 info = (TextView) findViewById(R.id.info); 56 select_file = (Button) findViewById(R.id.select_file); 57 select_file.setOnClickListener(this); 58 } 59 60 @Override 61 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 62 // TODO Auto-generated method stub 63 super.onActivityResult(requestCode, resultCode, data); 64 if (resultCode == RESULT_OK) { 65 if (requestCode == 1) { 66 Uri uri = data.getData(); // 接收用户所选文件的路径 67 info.setText("select: " + uri); // 在界面上显示路径 68 uploadFilePath = uri.getPath(); 69 int last = uploadFilePath.lastIndexOf("/"); 70 uploadFilePath = uri.getPath().substring(0, last+1); 71 fileName = uri.getLastPathSegment(); 72 } 73 } 74 } 75 76 protected Dialog onCreateDialog(int id) { 77 switch(id) { 78 case PROGRESS_DIALOG: 79 progressDialog = new ProgressDialog(UploadActivity.this); 80 progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 81 progressDialog.setButton("暂停", new DialogInterface.OnClickListener() { 82 @Override 83 public void onClick(DialogInterface dialog, int which) { 84 // TODO Auto-generated method stub 85 uploadThread.closeLink(); 86 dialog.dismiss(); 87 } 88 }); 89 progressDialog.setMessage("正在上传..."); 90 progressDialog.setMax(100); 91 return progressDialog; 92 default: 93 return null; 94 } 95 } 96 97 /** 98 * 使用Handler给创建他的线程发送消息, 99 * 匿名内部类 100 */ 101 private Handler handler = new Handler() 102 { 103 @Override 104 public void handleMessage(Message msg) 105 { 106 //获得上传长度的进度 107 int length = msg.getData().getInt("size"); 108 progressDialog.setProgress(length); 109 if(progressDialog.getProgress()==progressDialog.getMax())//上传成功 110 { 111 progressDialog.dismiss(); 112 Toast.makeText(UploadActivity.this, getResources().getString(R.string.upload_over), 1).show(); 113 } 114 } 115 }; 116 117 @Override 118 public void onClick(View v) { 119 // TODO Auto-generated method stub 120 Resources r = getResources(); 121 switch(v.getId()){ 122 case R.id.select_file: 123 Intent intent = new Intent(); 124 //设置起始目录和查找的类型 125 intent.setDataAndType(Uri.fromFile(new File("/sdcard")), "*/*");//"*/*"表示所有类型,设置起始文件夹和文件类型 126 intent.setClass(UploadActivity.this, FileBrowserActivity.class); 127 startActivityForResult(intent, 1); 128 break; 129 case R.id.download: 130 startActivity(new Intent(UploadActivity.this, SmartDownloadActivity.class)); 131 break; 132 case R.id.upload: 133 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))//判断SDCard是否存在 134 { 135 if(uploadFilePath == null){ 136 Toast.makeText(UploadActivity.this, "还没设置上传文件", 1).show(); 137 } 138 System.out.println("uploadFilePath:"+uploadFilePath+" "+fileName); 139 //取得SDCard的目录 140 File uploadFile = new File(new File(uploadFilePath), fileName); 141 Log.i(TAG, "filePath:"+uploadFile.toString()); 142 if(uploadFile.exists()) 143 { 144 showDialog(PROGRESS_DIALOG); 145 info.setText(uploadFile+" "+ConstantValues.HOST+":"+ConstantValues.PORT); 146 progressDialog.setMax((int) uploadFile.length());//设置长传文件的最大刻度 147 uploadThread = new UploadThread(UploadActivity.this, uploadFile, ConstantValues.HOST, ConstantValues.PORT); 148 uploadThread.setListener(new UploadProgressListener() { 149 150 @Override 151 public void onUploadSize(int size) { 152 // TODO Auto-generated method stub 153 Message msg = new Message(); 154 msg.getData().putInt("size", size); 155 handler.sendMessage(msg); 156 } 157 }); 158 uploadThread.start(); 159 } 160 else 161 { 162 Toast.makeText(UploadActivity.this, "文件不存在", 1).show(); 163 } 164 } 165 else 166 { 167 Toast.makeText(UploadActivity.this, "SDCard不存在!", 1).show(); 168 } 169 break; 170 } 171 172 } 173 174 175 }
UploadThread.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
1 package com.hao; 2 3 import java.io.File; 4 5 import com.hao.R; 6 import com.hao.R.id; 7 import com.hao.R.layout; 8 import com.hao.download.SmartFileDownloader; 9 import com.hao.download.SmartFileDownloader.SmartDownloadProgressListener; 10 import com.hao.util.ConstantValues; 11 12 import android.app.Activity; 13 import android.os.Bundle; 14 import android.os.Environment; 15 import android.os.Handler; 16 import android.os.Message; 17 import android.view.View; 18 import android.widget.Button; 19 import android.widget.ProgressBar; 20 import android.widget.TextView; 21 import android.widget.Toast; 22 23 /** 24 * 25 * @author Administrator 26 * 27 */ 28 public class SmartDownloadActivity extends Activity { 29 private ProgressBar downloadbar; 30 private TextView resultView; 31 private String path = ConstantValues.DOWNLOAD_URL; 32 SmartFileDownloader loader; 33 private Handler handler = new Handler() { 34 @Override 35 // 信息 36 public void handleMessage(Message msg) { 37 switch (msg.what) { 38 case 1: 39 int size = msg.getData().getInt("size"); 40 downloadbar.setProgress(size); 41 float result = (float) downloadbar.getProgress() / (float) downloadbar.getMax(); 42 int p = (int) (result * 100); 43 resultView.setText(p + "%"); 44 if (downloadbar.getProgress() == downloadbar.getMax()) 45 Toast.makeText(SmartDownloadActivity.this, "下载成功", 1).show(); 46 break; 47 case -1: 48 Toast.makeText(SmartDownloadActivity.this, msg.getData().getString("error"), 1).show(); 49 break; 50 } 51 52 } 53 }; 54 55 public void onCreate(Bundle savedInstanceState) { 56 super.onCreate(savedInstanceState); 57 setContentView(R.layout.download); 58 59 Button button = (Button) this.findViewById(R.id.button); 60 Button closeConn = (Button) findViewById(R.id.closeConn); 61 closeConn.setOnClickListener(new View.OnClickListener() { 62 63 @Override 64 public void onClick(View v) { 65 // TODO Auto-generated method stub 66 if(loader != null){ 67 finish(); 68 }else{ 69 Toast.makeText(SmartDownloadActivity.this, "还没有开始下载,不能暂停", 1).show(); 70 } 71 } 72 }); 73 downloadbar = (ProgressBar) this.findViewById(R.id.downloadbar); 74 resultView = (TextView) this.findViewById(R.id.result); 75 resultView.setText(path); 76 button.setOnClickListener(new View.OnClickListener() { 77 @Override 78 public void onClick(View v) { 79 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 80 download(path, ConstantValues.FILE_PATH); 81 } else { 82 Toast.makeText(SmartDownloadActivity.this, "没有SDCard", 1).show(); 83 } 84 } 85 }); 86 } 87 88 // 对于UI控件的更新只能由主线程(UI线程)负责,如果在非UI线程更新UI控件,更新的结果不会反映在屏幕上,某些控件还会出错 89 private void download(final String path, final File dir) { 90 new Thread(new Runnable() { 91 @Override 92 public void run() { 93 try { 94 loader = new SmartFileDownloader(SmartDownloadActivity.this, path, dir, 3); 95 int length = loader.getFileSize();// 获取文件的长度 96 downloadbar.setMax(length); 97 loader.download(new SmartDownloadProgressListener() { 98 @Override 99 public void onDownloadSize(int size) {// 可以实时得到文件下载的长度 100 Message msg = new Message(); 101 msg.what = 1; 102 msg.getData().putInt("size", size); 103 handler.sendMessage(msg); 104 } 105 }); 106 } catch (Exception e) { 107 Message msg = new Message();// 信息提示 108 msg.what = -1; 109 msg.getData().putString("error", "下载失败");// 如果下载错误,显示提示失败! 110 handler.sendMessage(msg); 111 } 112 } 113 }).start();// 开始 114 115 } 116 }
这个单个的下载线程
SmartDownloadThread.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
1 package com.hao.download; 2 3 import java.io.File; 4 import java.io.RandomAccessFile; 5 import java.net.HttpURLConnection; 6 import java.net.URL; 7 import java.util.LinkedHashMap; 8 import java.util.Map; 9 import java.util.UUID; 10 import java.util.concurrent.ConcurrentHashMap; 11 import java.util.regex.Matcher; 12 import java.util.regex.Pattern; 13 14 import com.hao.db.DownloadFileService; 15 16 import android.content.Context; 17 import android.util.Log; 18 19 /** 20 * 文件下载主程序 21 * @author Administrator 22 * 23 */ 24 public class SmartFileDownloader { 25 private static final String TAG = "SmartFileDownloader"; 26 private Context context; 27 private DownloadFileService fileService; 28 /* 已下载文件长度 */ 29 private int downloadSize = 0; 30 /* 原始文件长度 */ 31 private int fileSize = 0; 32 /*原始文件名*/ 33 private String fileName; 34 /* 线程数 */ 35 private SmartDownloadThread[] threads; 36 /* 本地保存文件 */ 37 private File saveFile; 38 /* 缓存各线程下载的长度 */ 39 private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(); 40 /* 每条线程下载的长度 */ 41 private int block; 42 /* 下载路径 */ 43 private String downloadUrl; 44 45 /** 46 * 获取文件名 47 */ 48 public String getFileName(){ 49 return this.fileName; 50 } 51 /** 52 * 获取线程数 53 */ 54 public int getThreadSize() { 55 return threads.length; 56 } 57 58 /** 59 * 获取文件大小 60 * @return 61 */ 62 public int getFileSize() { 63 return fileSize; 64 } 65 66 /** 67 * 累计已下载大小 68 * @param size 69 */ 70 protected synchronized void append(int size) { 71 downloadSize += size; 72 } 73 74 /** 75 * 更新指定线程最后下载的位置 76 * @param threadId 线程id 77 * @param pos 最后下载的位置 78 */ 79 protected void update(int threadId, int pos) { 80 this.data.put(threadId, pos); 81 } 82 83 /** 84 * 保存记录文件 85 */ 86 protected synchronized void saveLogFile() { 87 this.fileService.update(this.downloadUrl, this.data); 88 } 89 90 /** 91 * 构建文件下载器 92 * @param downloadUrl 下载路径 93 * @param fileSaveDir 文件保存目录 94 * @param threadNum 下载线程数 95 */ 96 public SmartFileDownloader(Context context, String downloadUrl, 97 File fileSaveDir, int threadNum) { 98 try { 99 this.context = context; 100 this.downloadUrl = downloadUrl; 101 fileService = new DownloadFileService(this.context); 102 URL url = new URL(this.downloadUrl); 103 if (!fileSaveDir.exists()) fileSaveDir.mkdirs(); 104 this.threads = new SmartDownloadThread[threadNum]; 105 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 106 conn.setConnectTimeout(5 * 1000); 107 conn.setRequestMethod("GET"); 108 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, */*"); 109 conn.setRequestProperty("Accept-Language", "zh-CN"); 110 conn.setRequestProperty("Referer", downloadUrl); 111 conn.setRequestProperty("Charset", "UTF-8"); 112 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)"); 113 conn.setRequestProperty("Connection", "Keep-Alive"); 114 conn.connect(); 115 printResponseHeader(conn); 116 if (conn.getResponseCode() == 200) { 117 this.fileSize = conn.getContentLength();// 根据响应获取文件大小 118 if (this.fileSize <= 0) 119 throw new RuntimeException("Unkown file size "); 120 121 fileName = getFileName(conn); 122 this.saveFile = new File(fileSaveDir, fileName);/* 保存文件 */ 123 Map<Integer, Integer> logdata = fileService.getData(downloadUrl); 124 if (logdata.size() > 0) { 125 for (Map.Entry<Integer, Integer> entry : logdata.entrySet()) 126 data.put(entry.getKey(), entry.getValue()); 127 } 128 //划分每个线程下载文件长度 129 this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize / this.threads.length 130 : this.fileSize / this.threads.length + 1; 131 if (this.data.size() == this.threads.length) { 132 for (int i = 0; i < this.threads.length; i++) { 133 this.downloadSize += this.data.get(i + 1); 134 } 135 print("已经下载的长度" + this.downloadSize); 136 } 137 } else { 138 throw new RuntimeException("server no response "); 139 } 140 } catch (Exception e) { 141 print(e.toString()); 142 throw new RuntimeException("don't connection this url"); 143 } 144 } 145 146 /** 147 * 获取文件名 148 */ 149 private String getFileName(HttpURLConnection conn) { 150 String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);//链接的最后一个/就是文件名 151 if (filename == null || "".equals(filename.trim())) {// 如果获取不到文件名称 152 for (int i = 0;; i++) { 153 String mine = conn.getHeaderField(i); 154 print("ConnHeader:"+mine+" "); 155 if (mine == null) 156 break; 157 if ("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())) { 158 Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase()); 159 if (m.find()) 160 return m.group(1); 161 } 162 } 163 filename = UUID.randomUUID() + ".tmp";// 默认取一个文件名 164 } 165 return filename; 166 } 167 168 /** 169 * 开始下载文件 170 * 171 * @param listener 172 * 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null 173 * @return 已下载文件大小 174 * @throws Exception 175 */ 176 public int download(SmartDownloadProgressListener listener) 177 throws Exception { 178 try { 179 RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw"); 180 if (this.fileSize > 0) 181 randOut.setLength(this.fileSize); 182 randOut.close(); 183 URL url = new URL(this.downloadUrl); 184 if (this.data.size() != this.threads.length) { 185 this.data.clear();// 清除数据 186 for (int i = 0; i < this.threads.length; i++) { 187 this.data.put(i + 1, 0); 188 } 189 } 190 for (int i = 0; i < this.threads.length; i++) { 191 int downLength = this.data.get(i + 1); 192 if (downLength < this.block && this.downloadSize < this.fileSize) { // 该线程未完成下载时,继续下载 193 this.threads[i] = new SmartDownloadThread(this, url, 194 this.saveFile, this.block, this.data.get(i + 1), i + 1); 195 this.threads[i].setPriority(7); 196 this.threads[i].start(); 197 } else { 198 this.threads[i] = null; 199 } 200 } 201 this.fileService.save(this.downloadUrl, this.data); 202 boolean notFinish = true;// 下载未完成 203 while (notFinish) {// 循环判断是否下载完毕 204 Thread.sleep(900); 205 notFinish = false;// 假定下载完成 206 for (int i = 0; i < this.threads.length; i++) { 207 if (this.threads[i] != null && !this.threads[i].isFinish()) { 208 notFinish = true;// 下载没有完成 209 if (this.threads[i].getDownLength() == -1) {// 如果下载失败,再重新下载 210 this.threads[i] = new SmartDownloadThread(this, 211 url, this.saveFile, this.block, this.data.get(i + 1), i + 1); 212 this.threads[i].setPriority(7); 213 this.threads[i].start(); 214 } 215 } 216 } 217 if (listener != null) 218 listener.onDownloadSize(this.downloadSize); 219 } 220 fileService.delete(this.downloadUrl); 221 } catch (Exception e) { 222 print(e.toString()); 223 throw new Exception("file download fail"); 224 } 225 return this.downloadSize; 226 } 227 228 /** 229 * 获取Http响应头字段 230 * 231 * @param http 232 * @return 233 */ 234 public static Map<String, String> getHttpResponseHeader( 235 HttpURLConnection http) { 236 Map<String, String> header = new LinkedHashMap<String, String>(); 237 for (int i = 0;; i++) { 238 String mine = http.getHeaderField(i); 239 if (mine == null) 240 break; 241 header.put(http.getHeaderFieldKey(i), mine); 242 } 243 return header; 244 } 245 246 /** 247 * 打印Http头字段 248 * 249 * @param http 250 */ 251 public static void printResponseHeader(HttpURLConnection http) { 252 Map<String, String> header = getHttpResponseHeader(http); 253 for (Map.Entry<String, String> entry : header.entrySet()) { 254 String key = entry.getKey() != null ? entry.getKey() + ":" : ""; 255 print(key + entry.getValue()); 256 } 257 } 258 259 // 打印日志 260 private static void print(String msg) { 261 Log.i(TAG, msg); 262 } 263 264 public interface SmartDownloadProgressListener { 265 public void onDownloadSize(int size); 266 } 267 }
好了这里只是将主要的代码分享出来,主要是为了了解他的基本流程,然后自己可以扩展,和优化