Android网络编程之——文件断点下载(暂停/继续/重新下载)

万事须己运,他得非我贤。这篇文章主要讲述Android网络编程之——文件断点下载(暂停/继续/重新下载)相关的知识,希望能为你提供帮助。
开头还是不说废话了直接进入主题吧!

Android网络编程之——文件断点下载(暂停/继续/重新下载)

文章图片

一: 关于断点下载所涉及到的知识点
1.对SQLite的增删改查(主要用来保存当前任务的一些信息)
2.HttpURLConnection的请求配置
HttpURLConnection connection = null; //设置下载请求属性 connection.setRequestProperty();

3.RandomAccessFile 对文件进行写入
RandomAccessFile rwd = null; //从文件的某一位置写入 rwd.seek();

4.基本的I/O流操作, 以及逻辑处理
Android网络编程之——文件断点下载(暂停/继续/重新下载)

文章图片

二: 第一步我们先来创建一张表用来保存我们的下载信息
public class DbHelper extends SQLiteOpenHelper {public static String TABLE = " file" ; //表名public DbHelper(Context context) { super(context, " download.db" , null, 1); }@ Override public void onCreate(SQLiteDatabase db) { //文件名, 下载地址, 下载文件的总长度, 当前下载完成长度 db.execSQL(" create table file(fileName varchar,url varchar,length integer,finished integer)" ); } }

三: 第二步同时既然是对数据库的操作, 那我们在DbHelper.class中来写好几个公用方法
/** * 插入一条下载信息 */ public void insertData(SQLiteDatabase db, FileInfo info) { ContentValues values = new ContentValues(); values.put(" fileName" , info.getFileName()); values.put(" url" , info.getUrl()); values.put(" length" , info.getLength()); values.put(" finished" , info.getFinished()); db.insert(TABLE, null, values); }/** * 是否已经插入这条数据 */ public boolean isExist(SQLiteDatabase db, FileInfo info) { Cursor cursor = db.query(TABLE, null, " url = ?" , new String[]{info.getUrl()}, null, null, null, null); boolean exist = cursor.moveToNext(); cursor.close(); return exist; }/** * 查询已经存在的一条信息 */ public FileInfo queryData(SQLiteDatabase db, String url) { Cursor cursor = db.query(TABLE, null, " url = ?" , new String[]{url}, null, null, null, null); FileInfo info = new FileInfo(); if (cursor != null) { while (cursor.moveToNext()) { String fileName = cursor.getString(cursor.getColumnIndex(" fileName" )); int length = cursor.getInt(cursor.getColumnIndex(" length" )); int finished = cursor.getInt(cursor.getColumnIndex(" finished" )); info.setStop(false); info.setFileName(fileName); info.setUrl(url); info.setLength(length); info.setFinished(finished); } cursor.close(); } return info; }/** * 恢复一条下载信息 */ public void resetData(SQLiteDatabase db, String url) { ContentValues values = new ContentValues(); values.put(" finished" , 0); values.put(" length" , 0); db.update(TABLE, values, " url = ?" , new String[]{url}); }

3.从上面方法中可以看出来还有一个FileInfo对象, 没错这是自己创建的一个下载任务实体类一起来看看吧
//保存下载任务信息 public class FileInfo { private String fileName; //文件名 private String url; //下载地址 private int length; //文件大小 private int finished; //下载以已完成进度 private boolean isStop = false; //是否暂停下载 private boolean isDownLoading = false; //是否正在下载 //...... //剩下的都是对应的get and set 方法就不贴出来了, 自动生成就好了

四: 第三步我们创建一个类DownLoaderManger来管理我们的下载任务包括、添加下载任务、开始下载、暂停下载、重新下载
public class DownLoaderManger {public static String FILE_PATH = Environment.getExternalStorageDirectory() + " /azhong" ; //文件下载保存路径 private DbHelper helper; //数据库帮助类 private SQLiteDatabase db; private OnProgressListener listener; //进度回调监听 private Map< String, FileInfo> map = new HashMap< > (); //保存正在下载的任务信息 private static DownLoaderManger manger; private DownLoaderManger(DbHelper helper, OnProgressListener listener) { this.helper = helper; this.listener = listener; db = helper.getReadableDatabase(); }/** * 单例模式 * * @ param helper数据库帮助类 * @ param listener 下载进度回调接口 * @ return */ public static synchronized DownLoaderManger getInstance(DbHelper helper, OnProgressListener listener) { if (manger = = null) { synchronized (DownLoaderManger.class) { if (manger = = null) { manger = new DownLoaderManger(helper, listener); } } } return manger; }/** * 开始下载任务 */ public void start(String url) { db = helper.getReadableDatabase(); FileInfo info = helper.queryData(db, url); map.put(url, info); //开始任务下载 new DownLoadTask(map.get(url), helper, listener).start(); }/** * 停止下载任务 */ public void stop(String url) { map.get(url).setStop(true); }/** * 重新下载任务 */ public void restart(String url) { stop(url); try { File file = new File(FILE_PATH, map.get(url).getFileName()); if (file.exists()) { file.delete(); } Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } db = helper.getWritableDatabase(); helper.resetData(db, url); start(url); }/** * 获取当前任务状态 */ public boolean getCurrentState(String url) { return map.get(url).isDownLoading(); }/** * 添加下载任务 * * @ param info 下载文件信息 */ public void addTask(FileInfo info) { //判断数据库是否已经存在这条下载信息 if (!helper.isExist(db, info)) { db = helper.getWritableDatabase(); helper.insertData(db, info); map.put(info.getUrl(), info); } else { //从数据库获取最新的下载信息 db = helper.getReadableDatabase(); FileInfo o = helper.queryData(db, info.getUrl()); if (!map.containsKey(info.getUrl())) { map.put(info.getUrl(), o); } } } }

五: 上面代码中OnProgressListener接口, 当然还有一个最最重要的DownLoadTask了这里面就是实现了如何断点下载的, 下面来一起看下里面的实现逻辑吧。。。
//下载进度接口 public interface OnProgressListener {void updateProgress(int max, int progress); }

六: 重点–下载线程
/** * 下载文件线程 * 从服务器获取需要下载的文件大小 */ public class DownLoadTask extends Thread { private FileInfo info; private SQLiteDatabase db; private DbHelper helper; //数据库帮助类 private int finished = 0; //当前已下载完成的进度 private OnProgressListener listener; //进度回调监听public DownLoadTask(FileInfo info, DbHelper helper, OnProgressListener listener) { this.info = info; this.helper = helper; this.db = helper.getReadableDatabase(); this.listener = listener; info.setDownLoading(true); }@ Override public void run() { getLength(); HttpURLConnection connection = null; RandomAccessFile rwd = null; try { URL url = new URL(info.getUrl()); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(" GET" ); connection.setConnectTimeout(3000); //从上次下载完成的地方下载 int start = info.getFinished(); //设置下载位置(从服务器上取要下载文件的某一段) connection.setRequestProperty(" Range" , " bytes= " + start + " -" + info.getLength()); //设置下载范围 //设置文件写入位置 File file = new File(DownLoaderManger.FILE_PATH, info.getFileName()); rwd = new RandomAccessFile(file, " rwd" ); //从文件的某一位置开始写入 rwd.seek(start); finished + = info.getFinished(); if (connection.getResponseCode() = = 206) {//文件部分下载, 返回码为206 InputStream is = connection.getInputStream(); byte[] buffer = new byte[1024 * 4]; int len; while ((len = is.read(buffer)) != -1) { //写入文件 rwd.write(buffer, 0, len); finished + = len; info.setFinished(finished); //更新界面显示 Message msg = new Message(); msg.what = 0x123; msg.arg1 = info.getLength(); msg.arg2 = info.getFinished(); handler.sendMessage(msg); //停止下载 if (info.isStop()) { info.setDownLoading(false); //保存此次下载的进度 helper.updateData(db, info); db.close(); return; } } //下载完成 info.setDownLoading(false); helper.updateData(db, info); db.close(); } } catch (Exception e) { e.printStackTrace(); } finally { if (connection != null) { connection.disconnect(); } try { if (rwd != null) { rwd.close(); } } catch (IOException e) { e.printStackTrace(); } } }/** * 首先开启一个线程去获取要下载文件的大小( 长度) */ private void getLength() { HttpURLConnection connection = null; try { //连接网络 URL url = new URL(info.getUrl()); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(" GET" ); connection.setConnectTimeout(3000); int length = -1; if (connection.getResponseCode() = = 200) {//网络连接成功 //获得文件长度 length = connection.getContentLength(); } if (length < = 0) { //连接失败 return; } //创建文件保存路径 File dir = new File(DownLoaderManger.FILE_PATH); if (!dir.exists()) { dir.mkdirs(); } info.setLength(length); } catch (Exception e) { e.printStackTrace(); } finally { //释放资源 try { if (connection != null) { connection.disconnect(); } } catch (Exception e) { e.printStackTrace(); } } }/** * 更新进度 */ private Handler handler = new Handler() { @ Override public void handleMessage(Message msg) { switch (msg.what) { case 0x123: if (listener != null) { listener.updateProgress(msg.arg1, msg.arg2); } break; } } }; }

七: 下载流程—> 首先获取要下载文件的总长度—> 然后指定从上次结束的位置开始下载文件。客官阅读需仔细哦, 精华都在注释里面哦! 个人认为重点部分如下两个:
//设置下载位置(从服务器上取要下载文件的某一段) connection.setRequestProperty(" Range" , " bytes= " + start + " -" + info.getLength()); //设置下载范围RandomAccessFile rwd = new RandomAccessFile(file, " rwd" ); //从文件的某一位置开始写入 rwd.seek(start);

八: 上面做了一系列准备工作之后, 就可以正式开始下载了让我们一起来瞧瞧
1.给主布局界面放两个按钮, 来开始/暂停/重新下载
< ?xml version= " 1.0" encoding= " utf-8" ?> < LinearLayout xmlns:android= " http://schemas.android.com/apk/res/android" xmlns:tools= " http://schemas.android.com/tools" android:id= " @ + id/activity_main" android:layout_width= " match_parent" android:layout_height= " match_parent" android:orientation= " vertical" android:paddingBottom= " @ dimen/activity_vertical_margin" android:paddingLeft= " @ dimen/activity_horizontal_margin" android:paddingRight= " @ dimen/activity_horizontal_margin" android:paddingTop= " @ dimen/activity_vertical_margin" tools:context= " com.azhong.downloader.MainActivity" > < com.azhong.downloader.view.NumberProgressBar android:id= " @ + id/pb" android:layout_width= " match_parent" android:layout_height= " wrap_content" /> < Button android:id= " @ + id/start" android:layout_width= " match_parent" android:layout_height= " wrap_content" android:layout_marginTop= " 16dp" android:text= " 开始下载" /> < Button android:id= " @ + id/restart" android:layout_width= " match_parent" android:layout_height= " wrap_content" android:layout_marginTop= " 10dp" android:text= " 重新下载" /> < /LinearLayout>

【Android网络编程之——文件断点下载(暂停/继续/重新下载)】2.关于NumberProgressBar的使用可以移驾至这里
3.记得在清单文件中加入 网络访问和内存读写权限哦!
4.既然我们封装了那么久, 那肯定用起来就会很简单了
public class MainActivity extends AppCompatActivity implements OnProgressListener {private NumberProgressBar pb; //进度条 private DownLoaderManger downLoader = null; private FileInfo info; @ Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pb = (NumberProgressBar) findViewById(R.id.pb); final Button start = (Button) findViewById(R.id.start); //开始下载 final Button restart = (Button) findViewById(R.id.restart); //重新下载 final DbHelper helper = new DbHelper(this); downLoader = DownLoaderManger.getInstance(helper, this); info = new FileInfo(" Kuaiya482.apk" , " http://downloadz.dewmobile.net/Official/Kuaiya482.apk" ); downLoader.addTask(info); start.setOnClickListener(new View.OnClickListener() { @ Override public void onClick(View v) { if (downLoader.getCurrentState(info.getUrl())) { downLoader.stop(info.getUrl()); start.setText(" 开始下载" ); } else { downLoader.start(info.getUrl()); start.setText(" 暂停下载" ); } } }); restart.setOnClickListener(new View.OnClickListener() { @ Override public void onClick(View v) { downLoader.restart(info.getUrl()); start.setText(" 暂停下载" ); } }); }@ Override public void updateProgress(final int max, final int progress) { pb.setMax(max); pb.setProgress(progress); } }

九: 断点下载的知识点在文章开头已经给出, 剩下的都是加入了自己的逻辑处理和代码封装。感兴趣的伙伴们一定要自己动手尝试着去写, 不然你以为你看了一遍会了其实到后来就完全不会了……Demo下载传送门

    推荐阅读