android中提供多线程下载思路

xiaoxiao2021-03-01  12

android中提供多线程下载思路 2010年12月13日    单线程下载HTTP文件对我们来说是一件非常简单的事。那么,多线程断点下载的难点在哪里?1.多线程下载,2.支持断点。   多线程下载:   如何才能从文件的指定位置处开始下载文件?(比如从50MB开始)这一点我们可以通过HTTP请求信息头来设置,还记得HTTP请求信息头的“Range”属性吗?   断点:   首要问题(多线程下载)已经被我们解决了,支持断点下载想必大家也已经想到了。就是将下载的进度保存到文件中,但在Android中却不能这么做。在Android平台中,我们需要向文件中写出下载的文件数据,还需要向另一个文件中写出下载进度,这样会出错。这样会导致有一个文件的内容没有被写出。所以我们就不能以文件的方式来保存下载进度,但可以通过数据库的方式保存下载进度。这两大问题我们已经有了解决思路,那么就开始动手编写吧!(以下源码仅供学习)   1.创建Android工程   Project name:MulThreadDownloader   BuildTarget:Android2.1   Application name:多线程断点下载   Package name:com.changcheng.download   Create Activity:MulThreadDownloader   Min SDK Version:7   2.AndroidManifest.xml                                                         3.strings.xml         Hello World, DownloadActivity!   多线程断点下载   下载路径   下载   SDCard不存在或者写保护      4.main.xml                                       5.MulThreadDownloader   package com.changcheng.download;   import java.io.File;   import com.changcheng.net.download.DownloadProgressListener;   import com.changcheng.net.download.FileDownloader;   import com.changcheng.download.R;   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.EditText;   import android.widget.ProgressBar;   import android.widget.TextView;   import android.widget.Toast;   public class MulThreadDownloader extends Activity {   private EditText pathText;   private ProgressBar progressBar;   private TextView resultView;   private Handler handler = new Handler(){   @Override   public void handleMessage(Message msg) {   if(!Thread.currentThread().isInterrupted()){   switch (msg.what) {   case 1:   // 获取当前文件下载的进度   int size = msg.getData().getInt("size");   progressBar.setProgress(size);   int result = (int)(((float)size/(float)progressBar.getMax()) * 100);   resultView.setText(result+ "%");   if(progressBar.getMax() == size){   Toast.makeText(MulThreadDownloader.this, "文件下载完成", 1).show();   }   break;   case -1:   String error = msg.getData().getString("error");   Toast.makeText(MulThreadDownloader.this, error, 1).show();   break;   }   }   super.handleMessage(msg);   }   };   @Override   public void onCreate(Bundle savedInstanceState) {   super.onCreate(savedInstanceState);   setContentView(R.layout.main);   pathText = (EditText)this.findViewById(R.id.path);   progressBar = (ProgressBar)this.findViewById(R.id.downloadbar);   resultView = (TextView)this.findViewById(R.id.resultView);   Button button = (Button)this.findViewById(R.id.button);   button.setOnClickListener(new View.OnClickListener() {   @Override   public void onClick(View v) {   String path = pathText.getText().toString();   if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){   //下载文件需要很长的时间,主线程是不能够长时间被阻塞,如果主线程被长时间阻塞, 那么Android被回收应用   download(path, Environment.getExternalStorageDirectory());   }else{   Toast.makeText(MulThreadDownloader.this, R.string.sdcarderror, 1).show();   }   }   });   }   /**   * 下载文件   * @param path 下载路径   * @param saveDir 文件保存目录   */   //对于Android的UI控件,只能由主线程负责显示界面的更新,其他线程不能直接更新UI控件的显示   public void download(final String path, final File saveDir){   new Thread(new Runnable() {   @Override   public void run() {   FileDownloader downer = new FileDownloader(MulThreadDownloader.this, path, saveDir, 3);   progressBar.setMax(downer.getFileSize());//设置进度条的最大刻度   try {   downer.download(new DownloadProgressListener(){   @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();   }   }   6.FileDownload   package com.changcheng.net.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.changcheng.download.service.FileService;   import android.content.Context;   import android.util.Log;   /**   * 文件下载器   * @author lihuoming@sohu.com   *   */   public class FileDownloader {   private Context context;   private FileService fileService;   private static final String TAG = "FileDownloader";   /* 已下载文件大小 */   private int downloadSize = 0;   /* 原始文件大小 */   private int fileSize = 0;   /* 线程数 */   private DownloadThread[] threads;   /* 下载路径 */   private URL url;   /* 本地保存文件 */   private File saveFile;   /* 下载记录文件 */   private File logFile;   /* 缓存各线程最后下载的位置*/   private Map data = new ConcurrentHashMap();   /* 每条线程下载的大小 */   private int block;   private String downloadUrl;//下载路径   /**   * 获取线程数   */   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 FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {   try {   this.context = context;   this.downloadUrl = downloadUrl;   fileService = new FileService(context);   this.url = new URL(downloadUrl);   if(!fileSaveDir.exists()) fileSaveDir.mkdirs();   this.threads = new DownloadThread[threadNum];   HttpURLConnection conn = (HttpURLConnection) url.openConnection();   conn.setConnectTimeout(6*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 logdata = fileService.getData(downloadUrl);   if(logdata.size()>0){   data.putAll(logdata);   }   this.block = this.fileSize / this.threads.length + 1;   if(this.data.size()==this.threads.length){   for (int i = 0; i 0) randOut.setLength(this.fileSize);   randOut.seek(this.data.get(i+1));   this.threads = new DownloadThread(this, this.url, randOut, this.block, this.data.get(i+1), i+1);   this.threads.setPriority(7);   this.threads.start();   }else{   this.threads = null;   }   }   this.fileService.save(this.downloadUrl, this.data);   boolean notFinish = true;//下载未完成   while (notFinish) {// 循环判断是否下载完毕   Thread.sleep(900);   notFinish = false;//假定下载完成   for (int i = 0; i getHttpResponseHeader(HttpURLConnection http) {   Map header = new LinkedHashMap();   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 header = getHttpResponseHeader(http);   for(Map.Entry entry : header.entrySet()){   String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";   print(key+ entry.getValue());   }   }   private static void print(String msg){   Log.i(TAG, msg);   }   }   7.DownloadProgressListener   package com.changcheng.net.download;   public interface DownloadProgressListener {   public void onDownloadSize(int size);   }   8.FileService   package com.changcheng.download.service;   import java.util.HashMap;   import java.util.Map;   import android.content.Context;   import android.database.Cursor;   import android.database.sqlite.SQLiteDatabase;   /**   * 业务bean   *   */   public class FileService {   private DBOpenHelper openHelper;   public FileService(Context context) {   openHelper = new DBOpenHelper(context);   }   /**   * 获取线程最后下载位置   * @param path   * @return   */   public Map getData(String path){   SQLiteDatabase db = openHelper.getReadableDatabase();   Cursor cursor = db.rawQuery("select threadid, position from filedown where downpath=?", new String[]{path});   Map data = new HashMap();   while(cursor.moveToNext()){   data.put(cursor.getInt(0), cursor.getInt(1));   }   cursor.close();   db.close();   return data;   }   /**   * 保存下载线程初始位置   * @param path   * @param map   */   public void save(String path, Map map){//int threadid, int position   SQLiteDatabase db = openHelper.getWritableDatabase();   db.beginTransaction();   try{   for(Map.Entry entry : map.entrySet()){   db.execSQL("insert into filedown(downpath, threadid, position) values(?,?,?)",   new Object[]{path, entry.getKey(), entry.getValue()});   }   db.setTransactionSuccessful();   }finally{   db.endTransaction();   }   db.close();   }   /**   * 实时更新线程的最后下载位置   * @param path   * @param map   */   public void update(String path, Map map){   SQLiteDatabase db = openHelper.getWritableDatabase();   db.beginTransaction();   try{   for(Map.Entry entry : map.entrySet()){   db.execSQL("update filedown set position=? where downpath=? and threadid=?",   new Object[]{entry.getValue(), path, entry.getKey()});   }   db.setTransactionSuccessful();   }finally{   db.endTransaction();   }   db.close();   }   /**   * 当文件下载完成后,清掉该文件对应的下载记录   * @param path   */   public void delete(String path){   SQLiteDatabase db = openHelper.getWritableDatabase();   db.execSQL("delete from filedown where downpath=?", new Object[]{path});   db.close();   }   }   9.DownloadThread   package com.changcheng.net.download;   import java.io.InputStream;   import java.io.RandomAccessFile;   import java.net.HttpURLConnection;   import java.net.URL;   import android.util.Log;   public class DownloadThread extends Thread {   private static final String TAG = "DownloadThread";   private RandomAccessFile saveFile;   private URL downUrl;   private int block;   /* 下载开始位置 */   private int threadId = -1;   private int startPos;   private int downLength;   private boolean finish = false;   private FileDownloader downloader;   public DownloadThread(FileDownloader downloader, URL downUrl, RandomAccessFile saveFile, int block, int startPos, int threadId) {   this.downUrl = downUrl;   this.saveFile = saveFile;   this.block = block;   this.startPos = startPos;   this.downloader = downloader;   this.threadId = threadId;   this.downLength = startPos - (block * (threadId - 1));   }   @Override   public void run() {   if(downLength 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();   int max = block>1024 ? 1024 : (block>10 ? 10 : 1);   byte[] buffer = new byte[max];   int offset = 0;   print("线程 " + this.threadId + "从位置"+ this.startPos+ "开始下载 ");   while (downLength < block && (offset = inStream.read(buffer, 0, max)) != -1) {   saveFile.write(buffer, 0, offset);   downLength += offset;   downloader.update(this.threadId, block * (threadId - 1) + downLength);   downloader.saveLogFile();   downloader.append(offset);   int spare = block-downLength;//求剩下的字节数   if(spare < max) max = (int) spare;   }   saveFile.close();   inStream.close();   print("线程 " + this.threadId + "完成下载 ");   this.finish = true;   this.interrupt();   } catch (Exception e) {   this.downLength = -1;   print("线程"+ 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;   }   }   11.DBOpenHelper   package com.changcheng.download.service;   import android.content.Context;   import android.database.sqlite.SQLiteDatabase;   import android.database.sqlite.SQLiteOpenHelper;   public class DBOpenHelper extends SQLiteOpenHelper {   private static final String DBNAME = "download.db";   private static final int VERSION = 2;   public DBOpenHelper(Context context) {   super(context, DBNAME, null, VERSION);   }   @Override   public void onCreate(SQLiteDatabase db) {   db.execSQL("CREATE TABLE IF NOT EXISTS filedown (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, position INTEGER)");   }   @Override   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {   db.execSQL("DROP TABLE IF EXISTS filedown");   onCreate(db);   }   }
转载请注明原文地址: https://www.6miu.com/read-3200227.html

最新回复(0)