`
scb426ga
  • 浏览: 20427 次
社区版块
存档分类
最新评论

android中提供多线程下载思路

 
阅读更多

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);
  }
  }
分享到:
评论

相关推荐

    Android实现多线程下载文件的方法

    多线程下载大概思路就是通过Range 属性实现文件分段,然后用RandomAccessFile 来读写文件,最终合并为一个文件 首先看下效果图: 创建工程 ThreadDemo 首先布局文件 threaddemo.xml &lt;?xml version=1.0 encoding...

    android多线程断点续传

    首要问题(多线程下载)已经被我们解决了,支持断点下载想必大家也已经想到了。就是将下载的进度保存到文件中,但在Android中却不能这么做。通过老黎的试验,在Android平台中,我们需要向文件中写出下载的文件数据...

    Android开发三剑客_part1

    从整体上来看 一款Android产品分为设计 编码和测试三个阶段 《Android开发三剑客:UML 模式与测试》着眼于Android产品的两个重要环节 设计和测试 分为上篇 中篇和下篇 上篇以Android多线程断点续传下载器为例 细致入...

    Android开发三剑客_part2

    从整体上来看 一款Android产品分为设计 编码和测试三个阶段 《Android开发三剑客:UML 模式与测试》着眼于Android产品的两个重要环节 设计和测试 分为上篇 中篇和下篇 上篇以Android多线程断点续传下载器为例 细致入...

    android开发资料大全

    在Android中实现多线程断点下载 Android提高篇内容整理 android移动开发案例精选 Android通过画线实现button效果 Android如何防止apk程序被反编译 Android 之 AIDL 和远程 Service 调用 Android 相对布局技巧 ...

    android MP3

    这是一个基于android平台开发的一个MP3播放器,所使用的技术有AsyncTask 异步搜索sd卡下的mp3文件,ViewPager listview 做ui显示,启动服务播放MP3,服务器搜索MP3,多线程下载MP3,现在播放,上一首,下一首,暂停/...

    10大经典Android开源项目分享(学习android必备)

     如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式...

    android 10个开源项目的学习

     如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式...

    Android聊天室设计

    参照书上的例子模仿的,虽然只实现了基本的功能,但它的思路比较好,为以后的开发奠定了基础。欢迎大家拍砖 项目简介: MultieThreadServer为服务器端程序,是普通的Java项目;MultieThreadClient是客户端程序...

    深入理解Android:卷I--详细书签版

    本书大部分章节中都专门撰写了“拓展思路”的内容,希望这部分内容能激发读者对Android代码进行深入研 究的热情。    本书面向的读者  (1)Android应用开发工程师 . 对于Android应用开发工程师而言,本书中...

    Android项目面试题.rar

    1. 内容丰富:本面试题集包含了大量关于Android项目的问题,涵盖了UI设计、数据存储、网络编程、多线程处理等多个方面,既有基础知识也有实战技巧。 2. 结构清晰:所有问题按照不同的知识点进行分类整理,方便求职者...

    10个经典的Android开源应用项目

     如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式...

    Android中加载网络资源时的优化可使用(线程+缓存)解决

    下面提出一些优化: 1、采用线程池 2、内存缓存+文件缓存 3、内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4、对下载的图片进行按比例缩放,以减少内存的消耗 具体的...

    疯狂Android讲义源码

     13.1.4 加入多线程 483  13.2 使用URL访问网络资源 488  13.2.1 使用URL读取网络资源 489  13.2.2 使用URLConnection  提交请求 490  13.3 使用HTTP访问网络 496  13.3.1 使用HttpURLConnection 496  ...

    [Android实例] Android 竖着的SeekBar.zip

    目前大公司的app开发都要基于模块化、层次化、组件化、控件化的思路来设计架构,而这一切的基础都是Android Framework。 如今,各企业Android招聘的面试中,也常常涉及到Framework相关的问题,如: 进程通信你用过...

    Android代码-AppInit

    AppInit 用于解决美团收银 B 端 App 在业务演进过程中的实际问题,取得了不错的效果,因此我们决定将其开源,希望更多技术同行一起开发,应用到更广泛的场景里去。 背景 随着业务的快速发展,新项目新业务不断出现,...

    美食天下android客户端源码

    这篇android客户端源码我觉得写的非常好,因为这里面有很多线程处理,activity处理,以及有动态的listview 中adapter怎么去处理解析,觉得比mars老师写的代码好很多,思路好很多,mars老师写的东西只能适合初学者...

Global site tag (gtag.js) - Google Analytics