断点续传和下载原理分析

断点续传和断点下载都是用的RandomAccessFile, 它具有移动指定的文件大小的位置的功能seek 。

断点续传是由服务器给客户端一个已经上传的位置标记position,然后客户端再将文件指针移动到相应的position,通过输入流将文件剩余部分读出来传输给服务器

断点下载 是由客户端告诉服务器已经下载的大小,然后服务器会将指针移动到相应的position,继续读出,把文件返回给客户端。 当然为了下载的更快一下,也可以多线程下载,那么基本实现就是给每个线程分配固定的字节的文件,分别去读

首先是文件上传,这个要用到服务器

关键代码:

FileServer.java

Java代码  

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.OutputStream;
  7. import java.io.PushbackInputStream;
  8. import java.io.RandomAccessFile;
  9. import java.net.ServerSocket;
  10. import java.net.Socket;
  11. import java.text.SimpleDateFormat;
  12. import java.util.Date;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. import java.util.Properties;
  16. import java.util.Set;
  17. import java.util.concurrent.ExecutorService;
  18. import java.util.concurrent.Executors;
  19. import util.FileLogInfo;
  20. import util.StreamTool;
  21. public class FileServer {
  22. private ExecutorService executorService;//线程池
  23. private int port;//监听端口
  24. private boolean quit = false;//退出
  25. private ServerSocket server;
  26. private Map<Long, FileLogInfo> datas = new HashMap<Long, FileLogInfo>();//存放断点数据,以后改为数据库存放
  27. public FileServer(int port)
  28. {
  29. this.port = port;
  30. //创建线程池,池中具有(cpu个数*50)条线程
  31. executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50);
  32. }
  33. /**
  34. * 退出
  35. */
  36. public void quit()
  37. {
  38. this.quit = true;
  39. try
  40. {
  41. server.close();
  42. }catch (IOException e)
  43. {
  44. e.printStackTrace();
  45. }
  46. }
  47. /**
  48. * 启动服务
  49. * @throws Exception
  50. */
  51. public void start() throws Exception
  52. {
  53. server = new ServerSocket(port);//实现端口监听
  54. while(!quit)
  55. {
  56. try
  57. {
  58. Socket socket = server.accept();
  59. executorService.execute(new SocketTask(socket));//为支持多用户并发访问,采用线程池管理每一个用户的连接请求
  60. }catch (Exception e)
  61. {
  62. e.printStackTrace();
  63. }
  64. }
  65. }
  66. private final class SocketTask implements Runnable
  67. {
  68. private Socket socket = null;
  69. public SocketTask(Socket socket)
  70. {
  71. this.socket = socket;
  72. }
  73. @Override
  74. public void run()
  75. {
  76. try
  77. {
  78. System.out.println("FileServer accepted connection "+ socket.getInetAddress()+ ":"+ socket.getPort());
  79. //得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid=
  80. //如果用户初次上传文件,sourceid的值为空。
  81. InputStream inStream = socket.getInputStream();
  82. String head = StreamTool.readLine(inStream);
  83. System.out.println("FileServer head:"+head);
  84. if(head!=null)
  85. {
  86. //下面从协议数据中提取各项参数值
  87. String[] items = head.split(";");
  88. String filelength = items[0].substring(items[0].indexOf("=")+1);
  89. String filename = items[1].substring(items[1].indexOf("=")+1);
  90. String sourceid = items[2].substring(items[2].indexOf("=")+1);
  91. //生成资源id,如果需要唯一性,可以采用UUID
  92. long id = System.currentTimeMillis();
  93. FileLogInfo log = null;
  94. if(sourceid!=null && !"".equals(sourceid))
  95. {
  96. id = Long.valueOf(sourceid);
  97. //查找上传的文件是否存在上传记录
  98. log = find(id);
  99. }
  100. File file = null;
  101. int position = 0;
  102. //如果上传的文件不存在上传记录,为文件添加跟踪记录
  103. if(log==null)
  104. {
  105. //设置存放的位置与当前应用的位置有关
  106. File dir = new File("c:/temp/");
  107. if(!dir.exists()) dir.mkdirs();
  108. file = new File(dir, filename);
  109. //如果上传的文件发生重名,然后进行改名
  110. if(file.exists())
  111. {
  112. filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf("."));
  113. file = new File(dir, filename);
  114. }
  115. save(id, file);
  116. }
  117. // 如果上传的文件存在上传记录,读取上次的断点位置
  118. else
  119. {
  120. System.out.println("FileServer have exits log not null");
  121. //从上传记录中得到文件的路径
  122. file = new File(log.getPath());
  123. if(file.exists())
  124. {
  125. File logFile = new File(file.getParentFile(), file.getName()+".log");
  126. if(logFile.exists())
  127. {
  128. Properties properties = new Properties();
  129. properties.load(new FileInputStream(logFile));
  130. //读取断点位置
  131. position = Integer.valueOf(properties.getProperty("length"));
  132. }
  133. }
  134. }
  135. //***************************上面是对协议头的处理,下面正式接收数据***************************************
  136. //向客户端请求传输数据
  137. OutputStream outStream = socket.getOutputStream();
  138. String response = "sourceid="+ id+ ";position="+ position+ "%";
  139. //服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=position
  140. //sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传
  141. outStream.write(response.getBytes());
  142. RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");
  143. //设置文件长度
  144. if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));
  145. //移动文件指定的位置开始写入数据
  146. fileOutStream.seek(position);
  147. byte[] buffer = new byte[1024];
  148. int len = -1;
  149. int length = position;
  150. //从输入流中读取数据写入到文件中,并将已经传入的文件长度写入配置文件,实时记录文件的最后保存位置
  151. while( (len=inStream.read(buffer)) != -1)
  152. {
  153. fileOutStream.write(buffer, 0, len);
  154. length += len;
  155. Properties properties = new Properties();
  156. properties.put("length", String.valueOf(length));
  157. FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));
  158. //实时记录文件的最后保存位置
  159. properties.store(logFile, null);
  160. logFile.close();
  161. }
  162. //如果长传长度等于实际长度则表示长传成功
  163. if(length==fileOutStream.length()){
  164. delete(id);
  165. }
  166. fileOutStream.close();
  167. inStream.close();
  168. outStream.close();
  169. file = null;
  170. }
  171. }
  172. catch (Exception e)
  173. {
  174. e.printStackTrace();
  175. }
  176. finally{
  177. try
  178. {
  179. if(socket!=null && !socket.isClosed()) socket.close();
  180. }
  181. catch (IOException e)
  182. {
  183. e.printStackTrace();
  184. }
  185. }
  186. }
  187. }
  188. /**
  189. * 查找在记录中是否有sourceid的文件
  190. * @param sourceid
  191. * @return
  192. */
  193. public FileLogInfo find(Long sourceid)
  194. {
  195. return datas.get(sourceid);
  196. }
  197. /**
  198. * 保存上传记录,日后可以改成通过数据库存放
  199. * @param id
  200. * @param saveFile
  201. */
  202. public void save(Long id, File saveFile)
  203. {
  204. System.out.println("save logfile "+id);
  205. datas.put(id, new FileLogInfo(id, saveFile.getAbsolutePath()));
  206. }
  207. /**
  208. * 当文件上传完毕,删除记录
  209. * @param sourceid
  210. */
  211. public void delete(long sourceid)
  212. {
  213. System.out.println("delete logfile "+sourceid);
  214. if(datas.containsKey(sourceid)) datas.remove(sourceid);
  215. }
  216. }

由于在上面的流程图中已经进行了详细的分析,我在这儿就不讲了,只是在存储数据的时候服务器没有用数据库去存储,这儿只是为了方便,所以要想测试断点上传,服务器是不能停的,否则数据就没有了,在以后改进的时候应该用数据库去存储数据。

文件上传客户端:

关键代码:

UploadActivity.java

Java代码  

  1. package com.hao;
  2. import java.io.File;
  3. import java.util.List;
  4. import com.hao.upload.UploadThread;
  5. import com.hao.upload.UploadThread.UploadProgressListener;
  6. import com.hao.util.ConstantValues;
  7. import com.hao.util.FileBrowserActivity;
  8. import android.app.Activity;
  9. import android.app.Dialog;
  10. import android.app.ProgressDialog;
  11. import android.content.DialogInterface;
  12. import android.content.Intent;
  13. import android.content.res.Resources;
  14. import android.net.Uri;
  15. import android.os.Bundle;
  16. import android.os.Environment;
  17. import android.os.Handler;
  18. import android.os.Message;
  19. import android.util.Log;
  20. import android.view.View;
  21. import android.view.View.OnClickListener;
  22. import android.widget.Button;
  23. import android.widget.TextView;
  24. import android.widget.Toast;
  25. /**
  26. *
  27. * @author Administrator
  28. *
  29. */
  30. public class UploadActivity extends Activity implements OnClickListener{
  31. private static final String TAG = "SiteFileFetchActivity";
  32. private Button download, upload, select_file;
  33. private TextView info;
  34. private static final int PROGRESS_DIALOG = 0;
  35. private ProgressDialog progressDialog;
  36. private UploadThread uploadThread;
  37. private String uploadFilePath = null;
  38. private String fileName;
  39. /** Called when the activity is first created. */
  40. @Override
  41. public void onCreate(Bundle savedInstanceState) {
  42. super.onCreate(savedInstanceState);
  43. setContentView(R.layout.upload);
  44. initView();
  45. }
  46. private void initView(){
  47. download = (Button) findViewById(R.id.download);
  48. download.setOnClickListener(this);
  49. upload = (Button) findViewById(R.id.upload);
  50. upload.setOnClickListener(this);
  51. info = (TextView) findViewById(R.id.info);
  52. select_file = (Button) findViewById(R.id.select_file);
  53. select_file.setOnClickListener(this);
  54. }
  55. @Override
  56. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  57. // TODO Auto-generated method stub
  58. super.onActivityResult(requestCode, resultCode, data);
  59. if (resultCode == RESULT_OK) {
  60. if (requestCode == 1) {
  61. Uri uri = data.getData();    // 接收用户所选文件的路径
  62. info.setText("select: " + uri); // 在界面上显示路径
  63. uploadFilePath = uri.getPath();
  64. int last = uploadFilePath.lastIndexOf("/");
  65. uploadFilePath = uri.getPath().substring(0, last+1);
  66. fileName = uri.getLastPathSegment();
  67. }
  68. }
  69. }
  70. protected Dialog onCreateDialog(int id) {
  71. switch(id) {
  72. case PROGRESS_DIALOG:
  73. progressDialog = new ProgressDialog(UploadActivity.this);
  74. progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
  75. progressDialog.setButton("暂停", new DialogInterface.OnClickListener() {
  76. @Override
  77. public void onClick(DialogInterface dialog, int which) {
  78. // TODO Auto-generated method stub
  79. uploadThread.closeLink();
  80. dialog.dismiss();
  81. }
  82. });
  83. progressDialog.setMessage("正在上传...");
  84. progressDialog.setMax(100);
  85. return progressDialog;
  86. default:
  87. return null;
  88. }
  89. }
  90. /**
  91. * 使用Handler给创建他的线程发送消息,
  92. * 匿名内部类
  93. */
  94. private Handler handler = new Handler()
  95. {
  96. @Override
  97. public void handleMessage(Message msg)
  98. {
  99. //获得上传长度的进度
  100. int length = msg.getData().getInt("size");
  101. progressDialog.setProgress(length);
  102. if(progressDialog.getProgress()==progressDialog.getMax())//上传成功
  103. {
  104. progressDialog.dismiss();
  105. Toast.makeText(UploadActivity.this, getResources().getString(R.string.upload_over), 1).show();
  106. }
  107. }
  108. };
  109. @Override
  110. public void onClick(View v) {
  111. // TODO Auto-generated method stub
  112. Resources r = getResources();
  113. switch(v.getId()){
  114. case R.id.select_file:
  115. Intent intent = new Intent();
  116. //设置起始目录和查找的类型
  117. intent.setDataAndType(Uri.fromFile(new File("/sdcard")), "*/*");//"*/*"表示所有类型,设置起始文件夹和文件类型
  118. intent.setClass(UploadActivity.this, FileBrowserActivity.class);
  119. startActivityForResult(intent, 1);
  120. break;
  121. case R.id.download:
  122. startActivity(new Intent(UploadActivity.this, SmartDownloadActivity.class));
  123. break;
  124. case R.id.upload:
  125. if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))//判断SDCard是否存在
  126. {
  127. if(uploadFilePath == null){
  128. Toast.makeText(UploadActivity.this, "还没设置上传文件", 1).show();
  129. }
  130. System.out.println("uploadFilePath:"+uploadFilePath+" "+fileName);
  131. //取得SDCard的目录
  132. File uploadFile = new File(new File(uploadFilePath), fileName);
  133. Log.i(TAG, "filePath:"+uploadFile.toString());
  134. if(uploadFile.exists())
  135. {
  136. showDialog(PROGRESS_DIALOG);
  137. info.setText(uploadFile+" "+ConstantValues.HOST+":"+ConstantValues.PORT);
  138. progressDialog.setMax((int) uploadFile.length());//设置长传文件的最大刻度
  139. uploadThread = new UploadThread(UploadActivity.this, uploadFile, ConstantValues.HOST, ConstantValues.PORT);
  140. uploadThread.setListener(new UploadProgressListener() {
  141. @Override
  142. public void onUploadSize(int size) {
  143. // TODO Auto-generated method stub
  144. Message msg = new Message();
  145. msg.getData().putInt("size", size);
  146. handler.sendMessage(msg);
  147. }
  148. });
  149. uploadThread.start();
  150. }
  151. else
  152. {
  153. Toast.makeText(UploadActivity.this, "文件不存在", 1).show();
  154. }
  155. }
  156. else
  157. {
  158. Toast.makeText(UploadActivity.this, "SDCard不存在!", 1).show();
  159. }
  160. break;
  161. }
  162. }
  163. }

UploadThread.java

Java代码  

  1. package com.hao.upload;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.OutputStream;
  6. import java.io.RandomAccessFile;
  7. import java.net.Socket;
  8. import android.content.Context;
  9. import android.util.Log;
  10. import com.hao.db.UploadLogService;
  11. import com.hao.util.StreamTool;
  12. public class UploadThread extends Thread {
  13. private static final String TAG = "UploadThread";
  14. /*需要上传文件的路径*/
  15. private File uploadFile;
  16. /*上传文件服务器的IP地址*/
  17. private String dstName;
  18. /*上传服务器端口号*/
  19. private int dstPort;
  20. /*上传socket链接*/
  21. private Socket socket;
  22. /*存储上传的数据库*/
  23. private UploadLogService logService;
  24. private UploadProgressListener listener;
  25. public UploadThread(Context context, File uploadFile, final String dstName,final int dstPort){
  26. this.uploadFile = uploadFile;
  27. this.dstName = dstName;
  28. this.dstPort = dstPort;
  29. logService = new UploadLogService(context);
  30. }
  31. public void setListener(UploadProgressListener listener) {
  32. this.listener = listener;
  33. }
  34. /**
  35. * 模拟断开连接
  36. */
  37. public void closeLink(){
  38. try{
  39. if(socket != null) socket.close();
  40. }catch(IOException e){
  41. e.printStackTrace();
  42. Log.e(TAG, "close socket fail");
  43. }
  44. }
  45. @Override
  46. public void run() {
  47. // TODO Auto-generated method stub
  48. try {
  49. // 判断文件是否已有上传记录
  50. String souceid = logService.getBindId(uploadFile);
  51. // 构造拼接协议
  52. String head = "Content-Length=" + uploadFile.length()
  53. + ";filename=" + uploadFile.getName() + ";sourceid="
  54. + (souceid == null ? "" : souceid) + "%";
  55. // 通过Socket取得输出流
  56. socket = new Socket(dstName, dstPort);
  57. OutputStream outStream = socket.getOutputStream();
  58. outStream.write(head.getBytes());
  59. Log.i(TAG, "write to outStream");
  60. InputStream inStream = socket.getInputStream();
  61. // 获取到字符流的id与位置
  62. String response = StreamTool.readLine(inStream);
  63. Log.i(TAG, "response:" + response);
  64. String[] items = response.split(";");
  65. String responseid = items[0].substring(items[0].indexOf("=") + 1);
  66. String position = items[1].substring(items[1].indexOf("=") + 1);
  67. // 代表原来没有上传过此文件,往数据库添加一条绑定记录
  68. if (souceid == null) {
  69. logService.save(responseid, uploadFile);
  70. }
  71. RandomAccessFile fileOutStream = new RandomAccessFile(uploadFile, "r");
  72. // 查找上次传送的最终位置,并从这开始传送
  73. fileOutStream.seek(Integer.valueOf(position));
  74. byte[] buffer = new byte[1024];
  75. int len = -1;
  76. // 初始化上传的数据长度
  77. int length = Integer.valueOf(position);
  78. while ((len = fileOutStream.read(buffer)) != -1) {
  79. outStream.write(buffer, 0, len);
  80. // 设置长传数据长度
  81. length += len;
  82. listener.onUploadSize(length);
  83. }
  84. fileOutStream.close();
  85. outStream.close();
  86. inStream.close();
  87. socket.close();
  88. // 判断上传完则删除数据
  89. if (length == uploadFile.length())
  90. logService.delete(uploadFile);
  91. } catch (Exception e) {
  92. e.printStackTrace();
  93. }
  94. }
  95. public interface UploadProgressListener{
  96. void onUploadSize(int size);
  97. }
  98. }

下面是多线程下载

SmartDownloadActivity.java

Java代码  

  1. package com.hao;
  2. import java.io.File;
  3. import com.hao.R;
  4. import com.hao.R.id;
  5. import com.hao.R.layout;
  6. import com.hao.download.SmartFileDownloader;
  7. import com.hao.download.SmartFileDownloader.SmartDownloadProgressListener;
  8. import com.hao.util.ConstantValues;
  9. import android.app.Activity;
  10. import android.os.Bundle;
  11. import android.os.Environment;
  12. import android.os.Handler;
  13. import android.os.Message;
  14. import android.view.View;
  15. import android.widget.Button;
  16. import android.widget.ProgressBar;
  17. import android.widget.TextView;
  18. import android.widget.Toast;
  19. /**
  20. *
  21. * @author Administrator
  22. *
  23. */
  24. public class SmartDownloadActivity extends Activity {
  25. private ProgressBar downloadbar;
  26. private TextView resultView;
  27. private String path = ConstantValues.DOWNLOAD_URL;
  28. SmartFileDownloader loader;
  29. private Handler handler = new Handler() {
  30. @Override
  31. // 信息
  32. public void handleMessage(Message msg) {
  33. switch (msg.what) {
  34. case 1:
  35. int size = msg.getData().getInt("size");
  36. downloadbar.setProgress(size);
  37. float result = (float) downloadbar.getProgress() / (float) downloadbar.getMax();
  38. int p = (int) (result * 100);
  39. resultView.setText(p + "%");
  40. if (downloadbar.getProgress() == downloadbar.getMax())
  41. Toast.makeText(SmartDownloadActivity.this, "下载成功", 1).show();
  42. break;
  43. case -1:
  44. Toast.makeText(SmartDownloadActivity.this, msg.getData().getString("error"), 1).show();
  45. break;
  46. }
  47. }
  48. };
  49. public void onCreate(Bundle savedInstanceState) {
  50. super.onCreate(savedInstanceState);
  51. setContentView(R.layout.download);
  52. Button button = (Button) this.findViewById(R.id.button);
  53. Button closeConn = (Button) findViewById(R.id.closeConn);
  54. closeConn.setOnClickListener(new View.OnClickListener() {
  55. @Override
  56. public void onClick(View v) {
  57. // TODO Auto-generated method stub
  58. if(loader != null){
  59. finish();
  60. }else{
  61. Toast.makeText(SmartDownloadActivity.this, "还没有开始下载,不能暂停", 1).show();
  62. }
  63. }
  64. });
  65. downloadbar = (ProgressBar) this.findViewById(R.id.downloadbar);
  66. resultView = (TextView) this.findViewById(R.id.result);
  67. resultView.setText(path);
  68. button.setOnClickListener(new View.OnClickListener() {
  69. @Override
  70. public void onClick(View v) {
  71. if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
  72. download(path, ConstantValues.FILE_PATH);
  73. } else {
  74. Toast.makeText(SmartDownloadActivity.this, "没有SDCard", 1).show();
  75. }
  76. }
  77. });
  78. }
  79. // 对于UI控件的更新只能由主线程(UI线程)负责,如果在非UI线程更新UI控件,更新的结果不会反映在屏幕上,某些控件还会出错
  80. private void download(final String path, final File dir) {
  81. new Thread(new Runnable() {
  82. @Override
  83. public void run() {
  84. try {
  85. loader = new SmartFileDownloader(SmartDownloadActivity.this, path, dir, 3);
  86. int length = loader.getFileSize();// 获取文件的长度
  87. downloadbar.setMax(length);
  88. loader.download(new SmartDownloadProgressListener() {
  89. @Override
  90. public void onDownloadSize(int size) {// 可以实时得到文件下载的长度
  91. Message msg = new Message();
  92. msg.what = 1;
  93. msg.getData().putInt("size", size);
  94. handler.sendMessage(msg);
  95. }
  96. });
  97. } catch (Exception e) {
  98. Message msg = new Message();// 信息提示
  99. msg.what = -1;
  100. msg.getData().putString("error", "下载失败");// 如果下载错误,显示提示失败!
  101. handler.sendMessage(msg);
  102. }
  103. }
  104. }).start();// 开始
  105. }
  106. }

这个单个的下载线程

SmartDownloadThread.java

Java代码  

  1. package com.hao.download;
  2. import java.io.File;
  3. import java.io.InputStream;
  4. import java.io.RandomAccessFile;
  5. import java.net.HttpURLConnection;
  6. import java.net.URL;
  7. import android.util.Log;
  8. /**
  9. * 线程下载
  10. * @author Administrator
  11. *
  12. */
  13. public class SmartDownloadThread extends Thread {
  14. private static final String TAG = "SmartDownloadThread";
  15. private File saveFile;
  16. private URL downUrl;
  17. private int block;
  18. /*  *下载开始位置 */
  19. private int threadId = -1;
  20. private int downLength;
  21. private boolean finish = false;
  22. private SmartFileDownloader downloader;
  23. public SmartDownloadThread(SmartFileDownloader downloader, URL downUrl,
  24. File saveFile, int block, int downLength, int threadId) {
  25. this.downUrl = downUrl;
  26. this.saveFile = saveFile;
  27. this.block = block;
  28. this.downloader = downloader;
  29. this.threadId = threadId;
  30. this.downLength = downLength;
  31. }
  32. @Override
  33. public void run() {
  34. if (downLength < block) {// 未下载完成
  35. try {
  36. HttpURLConnection http = (HttpURLConnection) downUrl
  37. .openConnection();
  38. http.setConnectTimeout(5 * 1000);
  39. http.setRequestMethod("GET");
  40. 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, */*");
  41. http.setRequestProperty("Accept-Language", "zh-CN");
  42. http.setRequestProperty("Referer", downUrl.toString());
  43. http.setRequestProperty("Charset", "UTF-8");
  44. int startPos = block * (threadId - 1) + downLength;// 开始位置
  45. int endPos = block * threadId - 1;// 结束位置
  46. http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);// 设置获取实体数据的范围
  47. 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)");
  48. http.setRequestProperty("Connection", "Keep-Alive");
  49. InputStream inStream = http.getInputStream();
  50. byte[] buffer = new byte[1024];
  51. int offset = 0;
  52. print("Thread " + this.threadId + " start download from position " + startPos);
  53. RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");
  54. threadfile.seek(startPos);
  55. while ((offset = inStream.read(buffer, 0, 1024)) != -1) {
  56. threadfile.write(buffer, 0, offset);
  57. downLength += offset;
  58. downloader.update(this.threadId, downLength);
  59. downloader.saveLogFile();
  60. downloader.append(offset);
  61. }
  62. threadfile.close();
  63. inStream.close();
  64. print("Thread " + this.threadId + " download finish");
  65. this.finish = true;
  66. } catch (Exception e) {
  67. this.downLength = -1;
  68. print("Thread " + this.threadId + ":" + e);
  69. }
  70. }
  71. }
  72. private static void print(String msg) {
  73. Log.i(TAG, msg);
  74. }
  75. /**
  76. * 下载是否完成
  77. * @return
  78. */
  79. public boolean isFinish() {
  80. return finish;
  81. }
  82. /**
  83. * 已经下载的内容大小
  84. * @return 如果返回值为-1,代表下载失败
  85. */
  86. public long getDownLength() {
  87. return downLength;
  88. }
  89. }

总得下载线程

SmartFileDownloader.java

Java代码  

    1. package com.hao.download;
    2. import java.io.File;
    3. import java.io.RandomAccessFile;
    4. import java.net.HttpURLConnection;
    5. import java.net.URL;
    6. import java.util.LinkedHashMap;
    7. import java.util.Map;
    8. import java.util.UUID;
    9. import java.util.concurrent.ConcurrentHashMap;
    10. import java.util.regex.Matcher;
    11. import java.util.regex.Pattern;
    12. import com.hao.db.DownloadFileService;
    13. import android.content.Context;
    14. import android.util.Log;
    15. /**
    16. * 文件下载主程序
    17. * @author Administrator
    18. *
    19. */
    20. public class SmartFileDownloader {
    21. private static final String TAG = "SmartFileDownloader";
    22. private Context context;
    23. private DownloadFileService fileService;
    24. /* 已下载文件长度 */
    25. private int downloadSize = 0;
    26. /* 原始文件长度 */
    27. private int fileSize = 0;
    28. /*原始文件名*/
    29. private String fileName;
    30. /* 线程数 */
    31. private SmartDownloadThread[] threads;
    32. /* 本地保存文件 */
    33. private File saveFile;
    34. /* 缓存各线程下载的长度 */
    35. private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();
    36. /* 每条线程下载的长度 */
    37. private int block;
    38. /* 下载路径 */
    39. private String downloadUrl;
    40. /**
    41. * 获取文件名
    42. */
    43. public String getFileName(){
    44. return this.fileName;
    45. }
    46. /**
    47. * 获取线程数
    48. */
    49. public int getThreadSize() {
    50. return threads.length;
    51. }
    52. /**
    53. * 获取文件大小
    54. * @return
    55. */
    56. public int getFileSize() {
    57. return fileSize;
    58. }
    59. /**
    60. * 累计已下载大小
    61. * @param size
    62. */
    63. protected synchronized void append(int size) {
    64. downloadSize += size;
    65. }
    66. /**
    67. * 更新指定线程最后下载的位置
    68. * @param threadId 线程id
    69. * @param pos 最后下载的位置
    70. */
    71. protected void update(int threadId, int pos) {
    72. this.data.put(threadId, pos);
    73. }
    74. /**
    75. * 保存记录文件
    76. */
    77. protected synchronized void saveLogFile() {
    78. this.fileService.update(this.downloadUrl, this.data);
    79. }
    80. /**
    81. * 构建文件下载器
    82. * @param downloadUrl 下载路径
    83. * @param fileSaveDir 文件保存目录
    84. * @param threadNum 下载线程数
    85. */
    86. public SmartFileDownloader(Context context, String downloadUrl,
    87. File fileSaveDir, int threadNum) {
    88. try {
    89. this.context = context;
    90. this.downloadUrl = downloadUrl;
    91. fileService = new DownloadFileService(this.context);
    92. URL url = new URL(this.downloadUrl);
    93. if (!fileSaveDir.exists()) fileSaveDir.mkdirs();
    94. this.threads = new SmartDownloadThread[threadNum];
    95. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    96. conn.setConnectTimeout(5 * 1000);
    97. conn.setRequestMethod("GET");
    98. 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, */*");
    99. conn.setRequestProperty("Accept-Language", "zh-CN");
    100. conn.setRequestProperty("Referer", downloadUrl);
    101. conn.setRequestProperty("Charset", "UTF-8");
    102. 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)");
    103. conn.setRequestProperty("Connection", "Keep-Alive");
    104. conn.connect();
    105. printResponseHeader(conn);
    106. if (conn.getResponseCode() == 200) {
    107. this.fileSize = conn.getContentLength();// 根据响应获取文件大小
    108. if (this.fileSize <= 0)
    109. throw new RuntimeException("Unkown file size ");
    110. fileName = getFileName(conn);
    111. this.saveFile = new File(fileSaveDir, fileName);/* 保存文件 */
    112. Map<Integer, Integer> logdata = fileService.getData(downloadUrl);
    113. if (logdata.size() > 0) {
    114. for (Map.Entry<Integer, Integer> entry : logdata.entrySet())
    115. data.put(entry.getKey(), entry.getValue());
    116. }
    117. //划分每个线程下载文件长度
    118. this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize / this.threads.length
    119. : this.fileSize / this.threads.length + 1;
    120. if (this.data.size() == this.threads.length) {
    121. for (int i = 0; i < this.threads.length; i++) {
    122. this.downloadSize += this.data.get(i + 1);
    123. }
    124. print("已经下载的长度" + this.downloadSize);
    125. }
    126. } else {
    127. throw new RuntimeException("server no response ");
    128. }
    129. } catch (Exception e) {
    130. print(e.toString());
    131. throw new RuntimeException("don‘t connection this url");
    132. }
    133. }
    134. /**
    135. * 获取文件名
    136. */
    137. private String getFileName(HttpURLConnection conn) {
    138. String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf(‘/‘) + 1);//链接的最后一个/就是文件名
    139. if (filename == null || "".equals(filename.trim())) {// 如果获取不到文件名称
    140. for (int i = 0;; i++) {
    141. String mine = conn.getHeaderField(i);
    142. print("ConnHeader:"+mine+" ");
    143. if (mine == null)
    144. break;
    145. if ("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())) {
    146. Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());
    147. if (m.find())
    148. return m.group(1);
    149. }
    150. }
    151. filename = UUID.randomUUID() + ".tmp";// 默认取一个文件名
    152. }
    153. return filename;
    154. }
    155. /**
    156. * 开始下载文件
    157. *
    158. * @param listener
    159. *            监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null
    160. * @return 已下载文件大小
    161. * @throws Exception
    162. */
    163. public int download(SmartDownloadProgressListener listener)
    164. throws Exception {
    165. try {
    166. RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw");
    167. if (this.fileSize > 0)
    168. randOut.setLength(this.fileSize);
    169. randOut.close();
    170. URL url = new URL(this.downloadUrl);
    171. if (this.data.size() != this.threads.length) {
    172. this.data.clear();// 清除数据
    173. for (int i = 0; i < this.threads.length; i++) {
    174. this.data.put(i + 1, 0);
    175. }
    176. }
    177. for (int i = 0; i < this.threads.length; i++) {
    178. int downLength = this.data.get(i + 1);
    179. if (downLength < this.block && this.downloadSize < this.fileSize) { // 该线程未完成下载时,继续下载
    180. this.threads[i] = new SmartDownloadThread(this, url,
    181. this.saveFile, this.block, this.data.get(i + 1), i + 1);
    182. this.threads[i].setPriority(7);
    183. this.threads[i].start();
    184. } else {
    185. this.threads[i] = null;
    186. }
    187. }
    188. this.fileService.save(this.downloadUrl, this.data);
    189. boolean notFinish = true;// 下载未完成
    190. while (notFinish) {// 循环判断是否下载完毕
    191. Thread.sleep(900);
    192. notFinish = false;// 假定下载完成
    193. for (int i = 0; i < this.threads.length; i++) {
    194. if (this.threads[i] != null && !this.threads[i].isFinish()) {
    195. notFinish = true;// 下载没有完成
    196. if (this.threads[i].getDownLength() == -1) {// 如果下载失败,再重新下载
    197. this.threads[i] = new SmartDownloadThread(this,
    198. url, this.saveFile, this.block, this.data.get(i + 1), i + 1);
    199. this.threads[i].setPriority(7);
    200. this.threads[i].start();
    201. }
    202. }
    203. }
    204. if (listener != null)
    205. listener.onDownloadSize(this.downloadSize);
    206. }
    207. fileService.delete(this.downloadUrl);
    208. } catch (Exception e) {
    209. print(e.toString());
    210. throw new Exception("file download fail");
    211. }
    212. return this.downloadSize;
    213. }
    214. /**
    215. * 获取Http响应头字段
    216. *
    217. * @param http
    218. * @return
    219. */
    220. public static Map<String, String> getHttpResponseHeader(
    221. HttpURLConnection http) {
    222. Map<String, String> header = new LinkedHashMap<String, String>();
    223. for (int i = 0;; i++) {
    224. String mine = http.getHeaderField(i);
    225. if (mine == null)
    226. break;
    227. header.put(http.getHeaderFieldKey(i), mine);
    228. }
    229. return header;
    230. }
    231. /**
    232. * 打印Http头字段
    233. *
    234. * @param http
    235. */
    236. public static void printResponseHeader(HttpURLConnection http) {
    237. Map<String, String> header = getHttpResponseHeader(http);
    238. for (Map.Entry<String, String> entry : header.entrySet()) {
    239. String key = entry.getKey() != null ? entry.getKey() + ":" : "";
    240. print(key + entry.getValue());
    241. }
    242. }
    243. // 打印日志
    244. private static void print(String msg) {
    245. Log.i(TAG, msg);
    246. }
    247. public interface SmartDownloadProgressListener {
    248. public void onDownloadSize(int size);
    249. }
    250. }

代码实现 参考 http://hao3100590.iteye.com/blog/1295903

时间: 2024-08-12 08:42:25

断点续传和下载原理分析的相关文章

BT下载原理分析

版权声明:本文为博主原创文章,未经博主允许不得转载. BitTorrent协议. BT全名为BitTorrent,是一个p2p软件,你在下载download的同时,也在为其他用户提供上传upload,因为大家是“互相帮助”,所以不会随着用户数的增加而降低下载速度. 下面是一般用ftp,http等分享流程: 下面是用BitTorrent分享的流程: 其实跟ED也十分相似,ED跟BT不同的地方有: ED--要连上一个固定server BT--没有固定server,只要分享者制作出该分享档案的.tor

BT原理分析(转)

BT种子文件结构分析,参考:http://www.cnblogs.com/EasonJim/p/6601047.html BitTorrent协议 BT全名为BitTorrent,是一个p2p软件,你在下载download的同时,也在为其他用户提供上传upload,因为大家是“互相帮助”,所以不会随着用户数的增加而降低下载速度. 下面是一般用ftp,http等分享流程: 下面是用BitTorrent分享的流程: 其实跟ED也十分相似,ED跟BT不同的地方有: ED--要连上一个固定server

Android多线程断点续传下载原理及实现

这段时间看了看工作室的工具库的下载组件,发现其存在一些问题: 1.下载核心逻辑有 bug,在暂停下载或下载失败等情况时有概率无法顺利完成下载.2.虽然原来的设计是采用多线程断点续传的设计,但打了一下日志发现其实下载任务都是在同一个线程下串行执行,并没有起到加快下载速度的作用. 考虑到原来的代码并不复杂,因此对这部分下载组件进行了重写.这里记录一下里面的多线程断点续传功能的实现. 请查看完整的PDF版(更多完整项目下载.未完待续.源码.图文知识后续上传github.)可以点击关于我联系我获取完整P

深入理解HTTP协议、HTTP协议原理分析

深入理解HTTP协议.HTTP协议原理分析 目录(?)[+] http协议学习系列 1. 基础概念篇 1.1 介绍 HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写.它的发展是万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Engineering Task Force)合作的结果,(他们)最终发布了一系列的RFC,RFC 1945定义了HTTP/1.0版本.其中最著名的就是RFC 26

android脱壳之DexExtractor原理分析[zhuan]

http://www.cnblogs.com/jiaoxiake/p/6818786.html内容如下 导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1.  需要动态调试 2.  对抗反调试方案 为了提高工作效率, 我们不希望把宝贵的时间浪费去和加固的安全工程师去做对抗.作为一个高效率的逆向分析师, 笔者是忍不了的,所以我今天给大家带来一种的新的脱壳方法——DexExtractor脱壳法. 资源地址: Dex

android脱壳之DexExtractor原理分析

导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1.  需要动态调试 2.  对抗反调试方案 为了提高工作效率, 我们不希望把宝贵的时间浪费去和加固的安全工程师去做对抗.作为一个高效率的逆向分析师, 笔者是忍不了的,所以我今天给大家带来一种的新的脱壳方法--DexExtractor脱壳法. 资源地址: DexExtractor源码:https://github.com/bunnyblue/DexExtracto

Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解

概况 Android在4.3的版本中(即API 18)加入了NotificationListenerService,根据SDK的描述(AndroidDeveloper)可以知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法.同时在Android 4.4 中新增了Notification.extras 字段,也就是说可以使用NotificationListenerService获取系统通知具体信息,这在以前是需要用反射来实现的. 转载请

黄金价格的变化及其经济学原理分析

目录 1.黄金价格的变化 1.1 黄金价格的概念 1.2国际市场金价的变化 1.3中国市场的黄金价格的变化 2. 黄金的历史演变和属性演变 2.1金本位的确立 2.2 布雷顿森林体系的建立与崩溃 3.黄金的属性 3.1财富属性 3.2 货币属性 3.3商品属性 3.4 投资品属性 4.黄金的定价机制及其对黄金价格的影响 4.1伦敦黄金市场的定价机制 4.2苏黎士黄金市场的定价机制 4.3纽约黄金市场的定价机制 4.4香港金银业贸易场的定价机制 4.5上海黄金交易所定价模式 5. 黄金价格及其影响

多线程断点下载原理

上传到服务器原理 商议客户端将文件长度Length.文件名Name.Id文件id以协议的形式发送到服务器.服务器判断Id是否为空,不为空时表示是断点上传,从存储断点上传文件的数据库中根据文件Id查询文件保存的Path并将Path返回,根据Path从上次的历史文件中读取上传的断点位置(断点位置记录在临时文件的*.log文件里的Properties部分了) 如果Id为空,则为初次上传,生成id,将id和path添加到数据库里 然后向客户端发送协议头Id(?包括记录断点位置的变量position,=0