多线程多任务断点续传

来源:互联网 发布:如何软件下载 编辑:程序博客网 时间:2024/04/29 10:34
      多线程下载的原理是这样的:通常服务器同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。如果你通过多个线程同时与服务器连接,那么你就可以榨取到较高的带宽了。
效果图:
                

FileDownload
任务类:
1:联网获取下载文件的信息,在SD卡上建立相应文件
2:给每个任务创建固定数目的线程,并给每个线程分配任务,然后开启
3:暂停、重启线程,根据每个线程的运行情况,通过Handler反馈信息到主线程(进度、是否结束)
源码:

public class FileDownloader {private static final String TAG = "FileDownloader"; //方便调试语句的编写private String urlStr; //文件下载位置private int fileLength; //下载文件长度private int downloadLength; //文件已下载长度private File saveFile; //SD卡上的存储文件private int block; //每个线程下载的大小private int threadCount = 2; //该任务的线程数目private DownloadThread[] loadThreads; private Handler handler;private Boolean isFinished = false; //该任务是否完毕private Boolean isPause = false;private Context context;RandomAccessFile accessFile;protected synchronized void append(int size){ //多线程访问需加锁this.downloadLength += size;}/*protected synchronized void update(int threadId , int thisThreadDownloadlength){ //写入数据库}*/public FileDownloader(String urlStr, Handler handler,Context context){this.urlStr = urlStr;this.handler = handler; this.loadThreads = new DownloadThread[threadCount];this.context = context;try {URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(3000); //设置超时if(conn.getResponseCode() == 200){fileLength = conn.getContentLength(); //获取下载文件长度String tmpFile = urlStr.substring(urlStr.lastIndexOf('/')+1); //获取文件名print(tmpFile);saveFile = new File(Environment.getExternalStorageDirectory(),tmpFile);accessFile= new RandomAccessFile(saveFile,"rws");accessFile.setLength(fileLength); //设置本地文件和下载文件长度相同accessFile.close();}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();} }public void Download(CompleteListener listener) throws Exception{URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(3000); //设置超时if(conn.getResponseCode() == 200){//发送消息设置进度条最大长度Message msg = new Message();msg.what = 0;msg.getData().putInt("filelength", fileLength);handler.sendMessage(msg);block = fileLength%threadCount == 0? fileLength/threadCount : fileLength/threadCount+1; //计算线程下载量print(Integer.toString(block));for(int i = 0 ; i < threadCount ; i++){print("哈哈,你妹啊");this.loadThreads = new DownloadThread(context,this,urlStr,saveFile,block,i); //新建线程开始下载print("嘿嘿");this.loadThreads.start();print("线程:"+Integer.toString(i)+" 开始下载");}while(!isPause && !this.isFinished){this.isFinished = true; Thread.sleep(900);for(int i = 0 ; i < threadCount ; i++){if(loadThreads != null && !loadThreads.isFinished()){this.isFinished = false;}}Message msg2 = new Message();msg2.what = 1;msg2.getData().putInt("currentlength", downloadLength); //handler.sendMessage(msg2); //发送 消息更新进度条//print(Integer.toString(downloadLength));}if(this.isFinished && listener != null){listener.isComplete(downloadLength);}}}private void print(String msg){ //打印提示消息Log.d(FileDownloader.TAG,msg);}public void setPause(){isPause = true; //该任务暂停for(int i = 0 ;i < threadCount ; i++){if(loadThreads!= null && !loadThreads.isFinished()){loadThreads.setPause(); //设置该线程暂停print(Integer.toString(i)+"草泥马");}}}public void setResume(final CompleteListener listener) throws Exception{isPause = false;this.downloadLength = 0;this.Download(new CompleteListener(){public void isComplete(int size) {listener.isComplete(size);print("listener");}});}public Boolean isFinished(){return this.isFinished;}}



DownloadThread
下载线程类:
1:需要参数:下载地址 、存储位置、起始结束下载位置、结束位置。  (时间、数据库等信息)
2:读取数据库判断是否有下载记录,然后根据情况计算下载的起始与结束位置,进行下载
3:每读取一定数据需要通过任务类更新任务显示,并写入数据库(多线程访问数据库需要加锁)
4:根据任务类的需要,暂停则让该线程运行结束,而非阻塞该线程(考虑到android内存的需要)
源码:

public class DownloadThread extends Thread{private String urlStr; //下载地址private int startPosition; //开始下载位置private int downloadLength=0; //已下载字节数private int endPosition; //结束位置private File saveFile; //文件存储位置private int threadId; //线程IDprivate FileDownloader fileDownloader; //文件任务类private Boolean isFinished = false; //文件快是否下载完毕private DatabaseUtil databaseUtil;private Boolean isPause = false; //默认为运行(没有暂停)private Context context;public DownloadThread(String urlStr){this.urlStr = urlStr;}public DownloadThread(Context context,FileDownloader fileDownloader,String urlStr , File file , int block ,int threadId){this.context = context;this.fileDownloader = fileDownloader; //this.urlStr = urlStr;this.saveFile = file;this.threadId = threadId;this.startPosition = threadId * block; //计算起始下载位置this.endPosition = (threadId+1) * block -1; //计算下载结束位置}public void run(){try{RandomAccessFile accessFile = new RandomAccessFile(saveFile,"rwd");databaseUtil = new DatabaseUtil(context); //context设置需思考ItemRecord record = databaseUtil.query(urlStr, threadId); //查询是否有记录存在if(record != null){ downloadLength = record.getDownloadLength(); //读取已下载字节数 fileDownloader.append(downloadLength); //更新总下载字节数print("线程"+Integer.toString(threadId)+"存在记录"+Integer.toString(downloadLength));}else{synchronized(DatabaseUtil.lock){databaseUtil.insert(urlStr, threadId, downloadLength); //插入未完成线程任务print("线程"+Integer.toString(threadId)+"不存在记录");}}accessFile.seek(startPosition+downloadLength); //设置写入起始位置if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){print(Integer.toString(endPosition)+"endPosition");print(Integer.toString(startPosition)+"startPosition");print(Integer.toString(downloadLength)+"downloadLength");isFinished = true; //更新下载标志print("线程:"+Integer.toString(threadId)+" 曾成功下载");}else{URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(3000); //设置超时conn.setRequestMethod("GET");int tmpStartPosition = startPosition + downloadLength;conn.setRequestProperty("Range", "bytes=" + tmpStartPosition + "-" + endPosition);if(conn.getResponseCode()==206){InputStream inStream = conn.getInputStream();byte[] buffer = new byte[1024];int len = 0;while(!isPause && (len = inStream.read(buffer))!= -1){accessFile.write(buffer, 0, len); //写入文件fileDownloader.append(len); //动态更新下载数、downloadLength += len; //已下载数更新//写入数据库synchronized(DatabaseUtil.lock){databaseUtil.update(urlStr, threadId, downloadLength);}}if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){print(Integer.toString(endPosition)+"endPosition");print(Integer.toString(startPosition)+"startPosition");print(Integer.toString(downloadLength)+"downloadLength");isFinished = true; //更新下载标志print("线程:"+Integer.toString(threadId)+" 下载完毕");}else{print(Integer.toString(endPosition)+"endPosition");print(Integer.toString(startPosition)+"startPosition");print(Integer.toString(downloadLength)+"downloadLength");print("线程:"+Integer.toString(threadId)+" 未下载完!");}}else{print(conn.getResponseMessage());print(Integer.toString(conn.getResponseCode()));print("擦,线程:"+Integer.toString(threadId)+" 没开始下载");}}}catch(Exception e){e.printStackTrace();}}//返回下载结束的标志public Boolean isFinished(){return this.isFinished;}//设置暂停public void setPause(){this.isPause = true;}private void print(String msg){Log.d("DownloadThrea",msg);}}


DatabaseHelper
建立数据库文件,并建表
源码:

public class DatabaseHelper extends SQLiteOpenHelper{//private SQLiteDatabase database;public DatabaseHelper(Context context , String name ,CursorFactory factory , int version) {super(context, "downloadFile.db", null,1);// TODO Auto-generated constructor stub}@Overridepublic void onCreate(SQLiteDatabase db) {Cursor cursor = null;try {Log.d("哈哈哈","leixun");cursor = db.rawQuery("SELECT * FROM sqlite_master WHERE type = ? AND name = ?", new String[]{"table","info"});if(cursor.moveToNext()){Log.d("DatabaseHelper","该表已经存在");}else{Log.d("DatabaseHelper","该表不存在 ,马上建立");db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " +"downloadlength INTEGER , PRIMARY KEY(path,threadid))");}cursor.close();} catch (Exception e) {e.printStackTrace();}/*db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " +"downloadlength INTEGER , PRIMARY KEY(path,threadid))");*/}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {// TODO Auto-generated method stub}}



DatabaseUtil
1:增删改查任务列表
2:通过静态常量lock实现数据库写入时的并发锁功能
源码:
public class DatabaseUtil {private DatabaseHelper helper;public static final String lock = "访问";public DatabaseUtil(Context context){helper = new DatabaseHelper(context, "downloadFile.db", null,1);}//添加记录public void insert(String path , int threadid , int downloadlength){SQLiteDatabase db = helper.getWritableDatabase();db.execSQL("INSERT INTO info(path,threadid,downloadlength) VALUES (?,?,?)", new Object[]{path,threadid,downloadlength});print("成功添加");db.close();}//删除记录public void delete(String path , int threadid){SQLiteDatabase db = helper.getWritableDatabase();db.execSQL("DELETE FROM info WHERE path = ? AND threadid = ?",new Object[]{path,threadid});print("成功删除");db.close();}//更新记录public void update(String path , int threadid , int downloadlength){SQLiteDatabase db = helper.getWritableDatabase();//print("准备更新1");db.execSQL("UPDATE info SET downloadlength = ? WHERE path = ? AND threadid = ?",new Object[]{downloadlength,path,threadid});//print("成功更新2");db.close();}//查询线程是否存在public ItemRecord query(String path, int threadid){SQLiteDatabase db = helper.getWritableDatabase();Cursor c = db.rawQuery("SELECT path,threadid,downloadlength FROM info WHERE path = ? AND threadid = ?", new String[]{path,Integer.toString(threadid)});ItemRecord record = null;if(c.moveToNext()){record = new ItemRecord(c.getString(0),c.getInt(1),c.getInt(2));}c.close();db.close();return record;}//查询未完成任务public List<String> query(String path){print("List<String> query 开始");SQLiteDatabase db = helper.getWritableDatabase();Cursor c = db.rawQuery("SELECT DISTINCT path FROM info", new String[]{path});List<String> arrayList = new ArrayList<String>();while(c.moveToNext()){arrayList.add(c.getString(0));}c.close();db.close();print("List<String> query 结束");return arrayList;}//调试信息输出private void print(String msg){Log.d("DatabaseUtil",msg);}}

ItemRecord
任务记录类,方便数据库操作而建立的任务记录类

public class ItemRecord {private String path;private int threadId;private int downloadLength;public ItemRecord(String path,int threadId,int downloadLength){this.path = path;this.threadId = threadId;this.downloadLength = downloadLength;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public int getThreadId() {return threadId;}public void setThreadId(int threadId) {this.threadId = threadId;}public int getDownloadLength() {return downloadLength;}public void setDownloadLength(int downloadLength) {this.downloadLength = downloadLength;}}


ApplicationUrlSpinner操作用来完成应用名称与下载地址的对应关系源码:

public class ApplicationUrl {private String applicationName = "";private String applicationUrl = "";public String getApplicationName() {return applicationName;}public void setApplicationName(String applicationName) {this.applicationName = applicationName;}public String getApplicationUrl() {return applicationUrl;}public void setApplicationUrl(String applicationUrl) {this.applicationUrl = applicationUrl;}public String toString() {// TODO Auto-generated method stubreturn applicationName;}}


CompleteListener下载完成判断接口:

public interface CompleteListener {   public void isComplete(int size);}



MainActivity
1:动态添加下载任务
2:根据任务动态添加布局,并给相应按钮添加监听,开启下载任务
源码:

public class MaintActivity extends Activity{private LayoutInflater inflater;private LinearLayout root;private Spinner spinnerApp;private List<ApplicationUrl> appUrl;private ArrayAdapter appAdapter;private Button startButton;FileDownloader fileDownloader;private String url5 = "http://update.android.doplive.com.cn/dopoolv2.4.player.apk";private String url5App = "Dopool手机电视";private String url = "http://www.ipmsg.org.cn/downloads/ipmsg.apk";private String urlApp = "飞鸽传书";private String url2 = "http://down1.cnmo.com/app/a135/aiku_2.0.apk";private String url2App = "爱酷天气";private ApplicationUrl userChoose;@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main_layout);inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);root = (LinearLayout)findViewById(R.id.root);spinnerApp = (Spinner)findViewById(R.id.urlSpinner);startButton = (Button)findViewById(R.id.startBtn);//createDownloadTask(url); //创建任务 添加View//createDownloadTask(url2);init();}public void init(){//-----------------给Spinner制作数据----------------appUrl = new ArrayList<ApplicationUrl>(); ApplicationUrl[] tmp = new ApplicationUrl[3];for(int i = 0 ; i<3;i++){tmp = new ApplicationUrl();}tmp[0].setApplicationName(urlApp);tmp[0].setApplicationUrl(url);tmp[1].setApplicationName(url2App);tmp[1].setApplicationUrl(url2);tmp[2].setApplicationName(url5App);tmp[2].setApplicationUrl(url5);appUrl.add(tmp[0]);appUrl.add(tmp[1]);appUrl.add(tmp[2]);userChoose = new ApplicationUrl();userChoose.setApplicationName(tmp[0].getApplicationName());userChoose.setApplicationUrl(tmp[0].getApplicationUrl());appAdapter = new ArrayAdapter(MaintActivity.this,android.R.layout.simple_spinner_item,appUrl);appAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);spinnerApp.setAdapter(appAdapter);spinnerApp.setSelection(0, true);spinnerApp.setOnItemSelectedListener(new OnItemSelectedListener(){@Overridepublic void onItemSelected(AdapterView<?> arg0, View arg1,int position, long arg3) {print("onItemSelected:"+appUrl.get(position).getApplicationName()+Integer.toString(position));userChoose.setApplicationName(appUrl.get(position).getApplicationName());userChoose.setApplicationUrl(appUrl.get(position).getApplicationUrl());}@Overridepublic void onNothingSelected(AdapterView<?> arg0) {// TODO Auto-generated method stub}});startButton.setOnClickListener(new View.OnClickListener() { @Overridepublic void onClick(View v) {if(!appUrl.isEmpty()){createDownloadTask(userChoose);}else{Toast.makeText(MaintActivity.this, "没有应用程序可下载!", Toast.LENGTH_LONG).show();}int count = appUrl.size();print(Integer.toString(count));for(int i = 0 ;i< count;i++){if(appUrl.get(i).getApplicationName().equals(userChoose.getApplicationName())){print("+++++++++++++++++"+Integer.toString(i)+"++++++++++++++++");printappUrl();appUrl.remove(i);printappUrl();count = appUrl.size();print("for循环找到"+Integer.toString(count));appAdapter.notifyDataSetChanged();if(!appUrl.isEmpty()){spinnerApp.setSelection(0,true);userChoose.setApplicationName(appUrl.get(0).getApplicationName()); //擦 必须这样赋值userChoose.setApplicationUrl(appUrl.get(0).getApplicationUrl());}printappUrl();print(userChoose.getApplicationName()+"数据源已更新"+Integer.toString(count));}else{print("数据源未更新");}}}});}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.main_layout, menu);return true;}private void createDownloadTask(ApplicationUrl path){LayoutInflater inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE); //获取系统服务layoutinflaterLinearLayout linearLayout = (LinearLayout)inflater.inflate(R.layout.download_layout, null);LinearLayout childLayout = (LinearLayout)linearLayout.getChildAt(0);ProgressBar progressBar = (ProgressBar)childLayout.getChildAt(0); //获取进度条TextView textView = (TextView)childLayout.getChildAt(1); //获取textviewButton button = (Button)linearLayout.getChildAt(1); //获取下载按钮try{button.setOnClickListener(new MyListener(progressBar,textView,path));root.addView(linearLayout); //将创建的View添加到主线程 的布局页面中}catch(Exception e){e.printStackTrace();}}private final class MyListener implements OnClickListener{private ProgressBar pb ;private TextView tv;private String url;private FileDownloader fileDownloader;private String name ; public MyListener(ProgressBar pb ,TextView tv, ApplicationUrl url){this.pb = pb;this.tv = tv;this.url = url.getApplicationUrl();this.name = url.getApplicationName();}public void onClick(View v) {final Button pauseButton = (Button)v;final Handler mainHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch(msg.what){case 2:pauseButton.setText("安装");break;}}};if(pauseButton.getText().equals("开始")){pauseButton.setText("暂停");new Thread(){public void run(){try{fileDownloader = new FileDownloader(url,handler,MaintActivity.this);fileDownloader.Download(new CompleteListener(){public void isComplete(int size) {Message msg = new Message();msg.what = 2;mainHandler.sendMessage(msg);}});}catch(Exception e){e.printStackTrace();}}}.start();}else if(pauseButton.getText().equals("暂停")){print("暂停");fileDownloader.setPause();pauseButton.setText("继续");}else if(pauseButton.getText().equals("继续")){print("继续");pauseButton.setText("暂停");new Thread(){public void run(){try{fileDownloader.setResume(new CompleteListener(){public void isComplete(int size) {print("妹纸");Message msg = new Message();msg.what = 2;mainHandler.sendMessage(msg);}});}catch(Exception e){e.printStackTrace();}}}.start();}else if(pauseButton.getText().equals("安装")){Intent intent = new Intent(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);String installPath = Environment.getExternalStorageDirectory() + "/"+url.substring(url.lastIndexOf('/')+1);print(installPath);intent.setDataAndType(Uri.fromFile(new File(installPath)), "application/vnd.android.package-archive");MaintActivity.this.startActivity(intent);}}private Handler handler = new Handler(){int fileLength = 1;@Overridepublic void handleMessage(Message msg) {switch(msg.what){case 0: //得到进度条的最大长度fileLength = msg.getData().getInt("filelength");pb.setMax(fileLength); //设置进度条最大长度break;case 1: //设置进度条现在的长度int currentLength = msg.getData().getInt("currentlength");pb.setProgress(currentLength); //设置当前进度条长度tv.setText(name+" 已下载:"+currentLength*100 / fileLength+"%");if(currentLength == fileLength){tv.setText(name+" 下载完成:"+currentLength*100 / fileLength+"%");}break;default: print("handleMessage msg.what 没有曲子好");}}};}/*** 调试信息输出* @param msg*/private void print(String msg){Log.d("MainActivity",msg);}private void printappUrl(){Log.d("=========================","=========================");for(int i = 0;appUrl!=null && i<appUrl.size();i++){print(appUrl.get(i).getApplicationName());}Log.d("=========================","=========================");}}

源码下载


0 0
原创粉丝点击