转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持!
多线程断点下载思路 1 加入断点功能 要知道每条线程的下载记录 数据库 SqliteDatabase SqliteOpenHepler download.db table _id threadid path downloadlength
//使用ContentProvider 设计业务方法 DownloadService 1 插入数据 insert() 2 更新数据 update() 3 查询 3.1 查询是否有下载记录boolean isExist() 3.2 查询每条记录的下载记录int queryByThreadid(int threadid) 3.3 查询总的下载记录 intqueryAll() 4 删除 delete()
1 在哪里修改代码? 在下载的时候,需要判断是否有下载记录 DownloadManager 更新下载记录在哪里呢?DownloadThread
2 暂停 MainActivity DownloadManager boolean flag = DownloadThread
对比之前学的多线程下载,我们今天学的断点多线程下载,加入了断点功能,之所以加入了断点功能,我们就要加入SQLite数据库进行存储每天线程的下载数据量,便于第一次没下载完,第二次读取数据,继续下载。因为使用到了SQLite数据库,所以我们自己也给自己提供了contentProvider,这样更好的简化了我们对数据库的操作以及不需要处理数据库的开关,没有频繁的操作数据库,这样使得我们的操作效率更高。 代码MainAtivity
package com.cym.multidon.ui; import com.cym.multidon.R; importcom.cym.multidon.download.DownloadManager; importcom.cym.multidon.inter.ProgressInter; 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.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class Mainctivity extends Activityimplements OnClickListener{ private Button bt_start; privateButton bt_stop; privateEditText et_url_value; privateProgressBar pb_progress; privateTextView tv_progress; privateDownloadManager dm; privatefinal static int SET_MAX = 0; privatefinal static int SET_LENGTH = 1; privatefinal static int SET_DOWNLOAD_LENGTH = 2; privateHandler mHandler = new Handler(){ public voidhandleMessage(android.os.Message msg) { switch (msg.what) { case SET_MAX: pb_progress.setMax((Integer)msg.obj); break; case SET_LENGTH: 改变进度条进度,以及算出进度百分比 int now = pb_progress.getProgress() +(Integer) msg.obj; pb_progress.setProgress(now); tv_progress.setText((int)((float)now/(float)pb_progress.getMax()*100)+ "%"); break; case SET_DOWNLOAD_LENGTH: pb_progress.setProgress((Integer)msg.obj); break; } }; }; privateProgressInter pi = new ProgressInter() { @Override public void setMax(int max) { //TODO Auto-generated method stub Messagemsg = new Message(); msg.what= SET_MAX; msg.obj= max; mHandler.sendMessage(msg); } @Override public void setLength(int length) { //TODO Auto-generated method stub Messagemsg = new Message(); msg.what= SET_LENGTH; msg.obj= length; mHandler.sendMessage(msg); } @Override public void setDownloadLength(int length){ //TODO Auto-generated method stub Messagemsg = new Message(); msg.what= SET_DOWNLOAD_LENGTH; msg.obj= length; mHandler.sendMessage(msg); } }; /**Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); bt_start = (Button) findViewById(R.id.bt_start); bt_stop = (Button) findViewById(R.id.bt_stop); et_url_value = (EditText) findViewById(R.id.et_url_value); pb_progress = (ProgressBar) findViewById(R.id.pb_progress); tv_progress = (TextView)findViewById(R.id.tv_progress); dm = new DownloadManager(this); bt_start.setOnClickListener(this); bt_stop.setOnClickListener(this); } @Override publicvoid onClick(View v) { // TODO Auto-generated method stub if(v == bt_start){ if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(this, "无SDCARD无法下载", 300).show(); }else { dm.setDwondlaod(true); bt_start.setEnabled(false); bt_stop.setEnabled(true); new Thread(){ public void run() { String path =et_url_value.getText().toString(); try { dm.download(path,pi, Environment.getExternalStorageDirectory()); } catch (Exception e) { //TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); } }else if(v == bt_stop){ dm.setDwondlaod(false); bt_start.setEnabled(true); bt_stop.setEnabled(false); } } }sqlite
public class DownloadDBHelper extends SQLiteOpenHelper { private final static String name = "download.db"; // 数据库名 private static SQLiteOpenHelper mOpenHelper; // 连接 // 单例模式,同步修饰符,保证数据的安全性 public synchronized static SQLiteOpenHelpergetInstance(Context context){ if(mOpenHelper == null){ mOpenHelper = new DownloadDBHelper(context, name, null, 1); } return mOpenHelper; } public DownloadDBHelper(Contextcontext, String name, CursorFactory factory, int version) { super(context, name, factory, version); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create tabledownload(_id integer primary key autoincrement," + "threadid integer," + "path text," + "downloadlength integer)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }contentprovider
public class DownloadProvider extends ContentProvider { 验证url private static UriMatchermatcher = new UriMatcher(UriMatcher.NO_MATCH); private static String authority ="com.cym.multidon.provider.DownloadProvider"; private final static int DOWNLOAD = 10; static { matcher.addURI(authority,"download", DOWNLOAD); } private SQLiteOpenHelper mOpenHelper; @Override public boolean onCreate() { mOpenHelper = DownloadDBHelper.getInstance(getContext()); return false; } @Override public Cursor query(Uri uri,String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub SQLiteDatabase db = mOpenHelper.getReadableDatabase(); Cursor ret = null; int code = matcher.match(uri); switch (code) { case DOWNLOAD: ret = db.query("download", projection, selection, selectionArgs, null, null, null); break; default: break; } return ret; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } @Override public Uri insert(Uri uri,ContentValues values) { // TODO Auto-generated method stub SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int code = matcher.match(uri); switch (code) { case DOWNLOAD: db.insert("download", "_id", values); break; default: break; } return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int code = matcher.match(uri); switch (code) { case DOWNLOAD: db.delete("download", selection,selectionArgs); break; default: break; } return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int code = matcher.match(uri); switch (code) { case DOWNLOAD: db.update("download", values, selection,selectionArgs); break; default: break; } return 0; } } DowloadService
package com.cym.multidon.service; import com.cym.multidon.domain.DownloadInfo; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; public class DownloadService { privateContext context; privateContentResolver cr; Private Uri uri =Uri.parse("content://com.cym.multidon.provider.DownloadProvider/download"); publicDownloadService(Context context){ this.context = context; this.cr = context.getContentResolver(); } /** * 插入记录 * @param info */ publicvoid insert(DownloadInfo info){ ContentValues values = newContentValues(); values.put("threadid",info.getThreadid()); values.put("path",info.getPath()); values.put("downloadlength",0); cr.insert(uri, values); } /** * 更新数据 * @param info */ publicvoid update(DownloadInfo info){ ContentValues values = new ContentValues(); values.put("downloadlength",info.getDownloadlength()); String where = "threadid = ? and path =?"; String[] selectionArgs = new String[]{info.getThreadid()+"",info.getPath()}; cr.update(uri, values, where, selectionArgs); } /** * 判断下载记录是否存在 * @param path * @return */ publicboolean isExist(String path){ boolean isExist = false; String selection = "path = ?"; String[] selectionArgs = newString[]{path}; Cursor cursor = cr.query(uri, newString[]{"*"}, selection, selectionArgs, null); if(cursor!=null){ if(cursor.moveToFirst()){ isExist = true; } cursor.close(); } return isExist; } /** * 查询单个线程的的下载长度 * @param info * @return */ publicint queryByThreadid(DownloadInfo info){ int downlaodlength = 0; String selection = "threadid = ? andpath = ?"; String[] selectionArgs = newString[]{info.getThreadid()+"",info.getPath()}; Cursor cursor = cr.query(uri, newString[]{"*"}, selection, selectionArgs, null); if(cursor != null){ if(cursor.moveToFirst()){ downlaodlength=cursor.getInt(cursor.getColumnIndex("downloadlength")) ; } cursor.close(); } return downlaodlength; } /** * 查询以一共的下载总量 * @param info * @return */ publicint queryAll(String path){ int downlaodlength = 0; String selection = "path = ?"; String[] selectionArgs = newString[]{path}; Cursor cursor = cr.query(uri, newString[]{"*"}, selection, selectionArgs, null); if(cursor != null){ while(cursor.moveToNext()){ downlaodlength +=cursor.getInt(cursor.getColumnIndex("downloadlength")) ; } cursor.close(); } return downlaodlength; } publicvoid delete(String path){ String where = "path = ?"; String[] selectionArgs = newString[]{path}; cr.delete(uri, where, selectionArgs); } }DownloadManager
package com.cym.multidon.download; import java.io.File; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.content.Context; import android.util.Log; importcom.cym.multidon.domain.DownloadInfo; importcom.cym.multidon.inter.ProgressInter; importcom.cym.multidon.service.DownloadService; public class DownloadManager { privatefinal static int threadSzie = 3; 线程数量 private boolean isDwondlaod; 是否开启下载 privateDownloadService ds; publicboolean isDwondlaod() { return isDwondlaod; } public void setDwondlaod(boolean isDwondlaod) { this.isDwondlaod =isDwondlaod; } public DownloadManager(Context context) { super(); this.ds = newDownloadService(context); } publicvoid download(String path, ProgressInter pi, File dir)throws Exception{ URLurl = new URL(path); HttpURLConnectionconn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); if(conn.getResponseCode()== 200){ int contentSize =conn.getContentLength(); File file = newFile(dir,getPathName(path)); RandomAccessFile raf = newRandomAccessFile(file, "rwd"); raf.setLength(contentSize); raf.close(); pi.setMax(contentSize); 如果有下载的记录那么就读取数据修改成上次进度 if(ds.isExist(path)){ int length = ds.queryAll(path); pi.setDownloadLength(length); }else { 如果的没有下载记录则创建记录 for (int threadid = 0; threadid <threadSzie; threadid++) { ds.insert(new DownloadInfo(threadid,path, 0)); } } 计算出每个线程下载量 int block =contentSize%threadSzie == 0 ? contentSize/threadSzie :contentSize/threadSzie+1; for (int threadid = 0; threadid <threadSzie; threadid++) { 启动三条线程开始下载 new DownloadThread(threadid, path, dir, block, pi, this,ds).start(); } } } 截取url后面的文件名 publicString getPathName(String path){ returnpath.substring(path.lastIndexOf("/")+1); } } DownloadThread
package com.cym.multidon.download; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import com.cym.multidon.domain.DownloadInfo; importcom.cym.multidon.inter.ProgressInter; importcom.cym.multidon.service.DownloadService; public class DownloadThread extends Thread{ privateint threadid; // 线程id privateString path; privateFile dir; privateint block; privateProgressInter pi; privateDownloadManager dm; privateDownloadService ds; privateint startPosition; privateint endPosition; publicDownloadThread(int threadid, String path, File dir, int block, ProgressInterpi, DownloadManager dm, DownloadService ds) { super(); this.threadid = threadid; this.path = path; this.dir = dir; this.block = block; this.pi = pi; this.dm = dm; this.ds = ds; 算出开始位置和结束位置 this.startPosition= threadid * block; this.endPosition =(threadid + 1) * block - 1; } @Override publicvoid run() { // TODO Auto-generated method stub super.run(); DownloadInfo info= new DownloadInfo(threadid, path, 0); 一开始查询该线程的下载量 int size =ds.queryByThreadid(info); File file = new File(dir,dm.getPathName(path)); 从上次记录的下载进度开始下载(默认为0) startPosition +=size; try { RandomAccessFileraf = new RandomAccessFile(file, "rwd"); 设置文件读取位置 raf.seek(startPosition); byte[]buffer = new byte[1024]; intlen = 0; URLurl = new URL(path); HttpURLConnectionconn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); 设置请求内容位置 conn.setRequestProperty("Range", "bytes="+ startPosition + "-" + endPosition); InputStreamis = conn.getInputStream(); while((len = is.read(buffer)) != -1) { 如果是暂停下载,boolean值则是false,则返回进入修改,及更改进度条 if(!dm.isDwondlaod()) { return; } 如果是下载则修改,及更新进度条进度 raf.write(buffer, 0, len); 不断的修改,没一次下载就修改一次下载量数据及进度条进度 size += len; info.setDownloadlength(size); ds.update(info); pi.setLength(len); } is.close(); raf.close(); } catch (Exception e) { //TODO Auto-generated catch block e.printStackTrace(); } } } 课后问题 多线程下载是怎么实现的? 开启多个线程下载一个文件 在这里我们使用哪些知识点? Thread、Handler、Http协议、ProgressBar、ContentProvider、SQLite。 断点功能是怎么实现的。 把数据存储在SQLite里面。 |