单线程断点续传(数据库方式)
来源:互联网 发布:淘宝店怎么运营起来 编辑:程序博客网 时间:2024/06/07 22:11
先上代码
效果图
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private TextView textView; private Button mBtnstart; private Button mBtnstop; private ProgressBar progressBar; //url必须带前缀,也就是协议,否则会报错 public static final String url = "http://www.imooc.com/mobile/imooc.apk"; private FileInfo fileInfo; private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(DownloadService.ACTION_UPDATE.equals(action)){ int finished = intent.getIntExtra("finished", 0); progressBar.setProgress(finished); textView.setText("下载进度"+String.valueOf(finished)+"%"); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); mBtnstart = (Button) findViewById(R.id.btn_start); mBtnstop = (Button) findViewById(R.id.btn_stop); mBtnstop.setOnClickListener(this); mBtnstart.setOnClickListener(this); progressBar = (ProgressBar) findViewById(R.id.progressBar); progressBar.setMax(100); fileInfo = new FileInfo(0,url,"imooc.apk",0,0); //注册广播 IntentFilter filter = new IntentFilter(DownloadService.ACTION_UPDATE); registerReceiver(receiver, filter); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_start: Intent intent = new Intent(this, DownloadService.class); intent.setAction(DownloadService.ACTION_START); intent.putExtra("fileinfo",fileInfo); startService(intent); break; case R.id.btn_stop: Intent intent2 = new Intent(this, DownloadService.class); intent2.setAction(DownloadService.ACTION_STOP); startService(intent2); break; } } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(receiver); }}
public class DownloadService extends Service { private static final String TAG = "DownloanService"; public static final String ACTION_START = "ACTION_START"; public static final String ACTION_STOP = "ACTION_STOP"; public static final String ACTION_UPDATE = "ACTION_UPDATE"; public static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory() .getAbsolutePath()+"/downloads/"; public static final int MSG_INIT_THREAD = 0; private DownloadTask mTask = null; public static boolean isStarted = false; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case MSG_INIT_THREAD: FileInfo fileInfo = (FileInfo) msg.obj; Log.d(TAG, "length: "+fileInfo.length); //将文件信息传给下载任务 mTask = new DownloadTask(DownloadService.this,fileInfo); mTask.download();//启动任务下载 break; } } }; @Override public int onStartCommand(Intent intent, int flags, int startId) { if(ACTION_START.equals(intent.getAction())){ //获取文件信息 FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileinfo"); if(!isStarted) { new initThread(fileInfo).start(); isStarted = true; } }else if(ACTION_STOP.equals(intent.getAction())){ if(mTask!=null){ mTask.isPause = true; isStarted = false; } } return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } class initThread extends Thread{ private FileInfo fileInfo; private RandomAccessFile raf; private HttpURLConnection conn; public initThread(FileInfo fileInfo){ this.fileInfo = fileInfo; } @Override public void run() { super.run(); try { URL url = new URL(fileInfo.url);//www.imooc.com/mobile/imooc.apk conn = (HttpURLConnection) url.openConnection();//打开链接 conn.setConnectTimeout(3000); conn.setReadTimeout(3000); int len = -1; if(conn.getResponseCode() == 200){ len = conn.getContentLength();//获取服务器文件长度 } if(len < 0){ return; } File dir = new File(DOWNLOAD_PATH); if(!dir.exists()){ dir.mkdir();//目录不存在创建目录 } File file = new File(dir,fileInfo.fileName); //在指定路径下创建一个个服务器文件大小一样的文件 raf = new RandomAccessFile(file,"rwd"); raf.setLength(len);//设置临时文件的长度为服务器文件长度 fileInfo.length = len;//设置文件信息 handler.obtainMessage(MSG_INIT_THREAD,fileInfo).sendToTarget(); }catch (Exception e){ e.printStackTrace(); }finally { if(conn!=null) conn.disconnect(); try { if(raf!=null) raf.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
public class DownloadTask { private Context mContext; //要下载的文件信息, private FileInfo fileInfo; private ThreadDao dao; public boolean isPause = false; //完成的进度 private int finished; public DownloadTask(Context mContext, FileInfo fileInfo) { this.mContext = mContext; this.fileInfo = fileInfo; dao = new ThreadDaoImpl(mContext); } public void download(){ //每次下载任务前,根据url查询线程信息 List<ThreadInfo> threadInfos = dao.queryThread(fileInfo.url); ThreadInfo threadInfo; if(threadInfos.size() == 0){//第一次,创建文件信息 threadInfo = new ThreadInfo(0,0,fileInfo.length,fileInfo.url,0); }else{ //以后从集合中取出文件信息 threadInfo = threadInfos.get(0); } new DownloadThread(threadInfo).start(); } class DownloadThread extends Thread{ private ThreadInfo threadinfo; private RandomAccessFile raf; private HttpURLConnection conn; public DownloadThread(ThreadInfo threadInfo){ this.threadinfo = threadInfo; } @Override public void run() { super.run(); //第一次数据库中不存在信息,向数据库写入信息 if(!dao.isExists(threadinfo.url, threadinfo.id)){ dao.insertThread(threadinfo); } //设置下载位置 try { URL url = new URL(threadinfo.url);//下载链接 conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(5000); conn.setConnectTimeout(4000); //当前下载位置等于起始位置加上已经下载的进度 int start = threadinfo.start+ threadinfo.finished; //下载的范围为起始位置到文件长度,因为是单线程下载 conn.setRequestProperty("Range","byte = "+start+"-"+ threadinfo.end); File file = new File(DownloadService.DOWNLOAD_PATH,fileInfo.fileName); raf = new RandomAccessFile(file,"rwd"); raf.seek(start);//指定从某个位置起 Intent intent = new Intent(DownloadService.ACTION_UPDATE); finished += threadinfo.finished;//更新完成的进度 //开始下载 if(conn.getResponseCode() == 200){ //读取数据 int len = -1; long time = System.currentTimeMillis(); InputStream stream = conn.getInputStream(); byte[] buffer = new byte[1024<<2];//每次赌徒多少个字节 while ((len = stream.read(buffer))!=-1){ //写入文件 raf.write(buffer,0,len); finished += len; if(System.currentTimeMillis() - time >200) { time = System.currentTimeMillis(); //通知Activity更新进度条 intent.putExtra("finished", finished *100/ threadinfo.end); mContext.sendBroadcast(intent); } //下载暂停,保存进度到数据库 if(isPause){ //将当前的信息保存到数据库 dao.updateThread(threadinfo.url, threadinfo.id+"",finished); return; } } //下载完成删除删除下载信息 dao.deleteThread(threadinfo.url, threadinfo.id+""); } } catch (Exception e) { e.printStackTrace(); dao.updateThread(threadinfo.url, threadinfo.id+"",finished); DownloadService.isStarted = false; }finally { { conn.disconnect(); try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } } }}
public class DBHelper extends SQLiteOpenHelper { public static final String DB_NAME = "download.db"; public static final int DB_VERSION = 1; //id 必须是integer,否则会报错 public static final String CREATE_TABLE = "create table thread_info(id integer primary key autoincrement," + "thread_id integer,url text,start integer,end integer,finished integer)"; public static final String DROP_TABLE = "drop table if exists thread_info"; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP_TABLE); db.execSQL(CREATE_TABLE); }}
public class FileInfo implements Serializable{ public int id; public String url; public String fileName; public int finished; public int length; public FileInfo() { } public FileInfo(int id, String url, String fileName, int finished, int length) { this.id = id; this.url = url; this.fileName = fileName; this.finished = finished; this.length = length; } @Override public String toString() { return "FileInfo{" + "id=" + id + ", url='" + url + '\'' + ", fileName='" + fileName + '\'' + ", finished=" + finished + ", length=" + length + '}'; }}
public class ThreadInfo { public int id; public int start; public int end; public String url; public int finished; public ThreadInfo() { } public ThreadInfo(int id, int start, int end, String url,int finished) { this.id = id; this.start = start; this.end = end; this.url = url; this.finished = finished; } @Override public String toString() { return "ThreadInfo{" + "id=" + id + ", start=" + start + ", end=" + end + ", url='" + url + '\'' + '}'; }}
public interface ThreadDao { void insertThread(ThreadInfo threadInfo); void deleteThread(String url,String thread_id); void updateThread(String url,String thread_id,int finished); List<ThreadInfo> queryThread(String url); boolean isExists(String url,int thread_id);}
public class ThreadDaoImpl implements ThreadDao { DBHelper dbHelper; public ThreadDaoImpl(Context context){ dbHelper = new DBHelper(context); } @Override public void insertThread(ThreadInfo threadInfo) { SQLiteDatabase db = dbHelper.getReadableDatabase(); ContentValues values = new ContentValues(); values.put("thread_id",threadInfo.id); values.put("url",threadInfo.url); values.put("start",threadInfo.start); values.put("end",threadInfo.end); values.put("finished",threadInfo.finished); db.insert("thread_info",null,values); db.close(); } @Override public void deleteThread(String url, String thread_id) { SQLiteDatabase db = dbHelper.getReadableDatabase(); int count = db.delete("thread_info","url = ? and thread_id = ?",new String[]{url,thread_id} ); System.out.println("========count: "+count); db.close(); } @Override public void updateThread(String url, String thread_id, int finished) { SQLiteDatabase db = dbHelper.getReadableDatabase(); ContentValues values = new ContentValues(); values.put("finished",finished); db.update("thread_info",values,"url = ? and thread_id = ?",new String[]{url,thread_id}); } @Override public List<ThreadInfo> queryThread(String url) { List<ThreadInfo> list = new ArrayList<>(); SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.query("thread_info",null,"url = ?", new String[]{url},null,null,null); while (cursor.moveToNext()){ ThreadInfo threadInfo = new ThreadInfo(); threadInfo.id = cursor.getInt(cursor.getColumnIndex("thread_id")); threadInfo.url= cursor.getString(cursor.getColumnIndex("url")); threadInfo.start = cursor.getInt(cursor.getColumnIndex("start")); threadInfo.end = cursor.getInt(cursor.getColumnIndex("end")); threadInfo.finished = cursor.getInt(cursor.getColumnIndex("finished")); list.add(threadInfo); } cursor.close(); db.close(); return list; } public boolean isExists(String url,int thread_id){ SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.query("thread_info",null,"url = ? and thread_id = ?", new String[]{url,thread_id+""},null,null,null); boolean exists = cursor.moveToNext(); cursor.close(); db.close(); return exists; }}
总结
涉及到的知识点
Activity和Service交互:Activity通过Intent向Service传递,
Service通过广播,另外还可以选择Handler或者进程间通信方式
文件如何下载的;从数据库中取出文件上次下载的位置,使用Range属性去请求
//当前下载位置等于起始位置加上已经下载的进度 int start = threadinfo.start+ threadinfo.finished; //下载的范围为起始位置到文件长度,因为是单线程下载 conn.setRequestProperty("Range","byte = "+start+"-"+ threadinfo.end);
使用RandomAccessFile的seek方法从指定位置开始写
raf = new RandomAccessFile(file,"rwd"); raf.seek(start);//指定从某个位置起
进度如何更新的:在下载文件过程中,通过广播把当前下载的进度发送给Activity,更新界面
如何断点续传的;
下载暂停的时候把进度保存到数据库中去,通过线程的url,id,
//下载暂停,保存进度到数据库 if(isPause){ //将当前的信息保存到数据库 dao.updateThread(threadinfo.url, threadinfo.id+"",finished); return; }
下次继续下载的时候从数据库中查找线程信息,接着上次进度下载
//每次下载任务前,根据url查询线程信息 List<ThreadInfo> threadInfos = dao.queryThread(fileInfo.url); ...... finished += threadinfo.finished;//更新完成的进度,必须要加上数据库中查询到的文件信息
Service在整个过程中起到什么作用?
其实在Activity中开启线程下载也是可以的,那我们为什么要使用Service呢?
因为Activity属于一个前台的组件,只要是前台组件就有可能被用户关闭,也有可能切换到后台,被系统回收,一旦Activity被回收,那么在Activity中启动的线程就无法跟踪,管理,会导致很多问题,没法对线程进行操作。
Service属于后台的组件,不是直接和用户交互的,用户没法直接关闭,Service优先级比较高,一般不会被系统回收,线程的操作放在Service里面比较保险。
最后给出源码下载位置
点此下载源码
0 0
- 单线程断点续传(数据库方式)
- xUtils单线程断点续传
- andorid单线程断点续传下载
- Android XUtils单线程断点续传
- C# 单线程断点续传下载核心代码
- 单线程支持断点续传文件下载
- Android 断点续传基础之单线程下载
- 单线程下载,并实现断点续传
- (一二六)单线程文件下载与断点续传
- Android 实现文件的单线程断点续传下载
- Android多线程+单线程+断点续传+进度条显示下载
- 单线程程序处理消息的方式!
- C# 断点续传的例子 单线程 ,支持按钮暂停和开始
- asp.net下载,支持中文名,断点续传,控制单线程下载速度
- asp.net下载,支持中文名,断点续传,控制单线程下载速度
- Android线程断点续传
- 谈disruptor的单线程数据库操作
- JDBC单例模式连接数据库方式
- android从零开始播放视频
- HDOJ1316 大肥波数
- delphi 出现update failed
- Binder与Service
- hadoop HDFS URI
- 单线程断点续传(数据库方式)
- 使用achartengine-1.1.0画折线图
- java在linux系统下文件夹出现中文乱码的问题
- ruby安装步骤
- 信息增益,信息增益率,Gini
- boost::bind绑定成员函数时,第一个参数传递对象的特殊情况
- sublime text 2中Emmet 常用的技巧
- 界面工具:GPU呈现模式分析
- c#解压压缩文件和文件夹