iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Android入门之利用OKHttp实现断点续传功能
  • 203
分享到

Android入门之利用OKHttp实现断点续传功能

摘要

目录简介课程目标断点下载的原理自定义Android里的ProgressBar的样式项目结构前端代码后端代码DbOpeerateHelper.javaDBService.javaDow

简介

今天我们将继续使用OkHttp组件并制作一个基于多线程的可断点续传的下载器来结束Android OkHttp组件的所有知识内容。在这一课里我们会在上一次课程的基础上增加sqlite的使用以便于我们的App可以暂存下载时的实时进度,每次下载开始都会判断是覆盖式还是续传式下载。同时由于Android自带的进度条太丑了,我们对它稍稍进行了一些美化。可以说今天这篇教程也是一篇阶段性的功能整合实验。

下面开始进入课程。

课程目标

1.使用SQLite进行下载时的进度信息的暂存;

2.自定义ProgressBar的样式;

断点下载的原理

如果你认真的在看完了上篇教程后并且脱离我的Sample代码自己动手实现了一个多线程下载器的话那么今天这篇教程对于你来说会变得相当的简单。

因为所谓的断点下载就是把每一条线程当前在下载的信息存入一个SQLite的表内。而断点下载就是通过暂存的信息去改变RandoMacessFile在写入时的seek。

当然这里面还伴随着一些小技巧,我们需要我们的APP的“STOP”动作可以打断正在下载的进度,打断后如果再次点击了“DOWNLOAD”按钮,此时各子线程做的任务为“续传”,续传的进度是否完成了呢这也需要子线程和主线程间进行状态通信。

需要知道每个子线程运行是否已经结束了

这边并不是需要知道每个子线程的返回、中间态。我们只是需要知道每一个子线程是否运行完了。

在平时开发中我们经常会面临这样的一种情况。比如说我们外部需要长时间的等待?或者也有开发搞了一个全局的栈去计算、也有用future接口的。很多时候往往为了取一个状态,开发创造了一堆的“轮子”,导致了整个项目代码过于复杂以及不好调试。因此这些手法都不是很优雅。今天笔者给各位推荐一种更为优雅的写法,以便于在外部判断每一个子线程是否都运行完毕了。

使用状态反转来不断check子线程状态

其实它的核心思路是:

  • 在外部有一个无限while 循环,while(notFinish);
  • 循环入口上手就把循环终止, notFinish=false;
  • 接着依次检查每一个子线程内的一个状态值-finish,这个值在每个子线程内任务结束后会设为true。只要这个值在外部被检测到不为true,那么把外部循环的状态再改为notFinish=true,以使得外部循环不断运行直到所有子线程检测下来都确为finish,此时外部的while循环跳出;

每个子线程下载的实时信息存储

我们设计了一个这样的表结构用来存储下载的实时信息。

  • 每次下载进程开始时,先根据下载URL去该表中查出所有的下载信息。比如说我们开启了3个线程,那么对于同一个URL:/test.zip可以根据download_path查出3条数据。把3条数据的download_length相加拼在一起,如果<当前远程文件size说明上次下载没有完成,那么继续下载。否则新建一个空文件并把这个空文件的长度设定为远程资源文件的长度;
  • 每个子线程在下载时不断根据download_path update这张表里的数据把当前的实时进度写进去;
  • 下载完后根据download_path清空这个表里的数据;

Http Get请求如何支持断点续传

Request.addHeader("Range", "bytes=" + startPos + "-" + endPos)
假设线程编号从1开始,开了3个子线程,共有1-3个线程,线程编号为1-3,此处的startPos和endPos的计算公式如下:

  • startPos=每个线程分页下载文件大小*线程编号+上一次下载进度,如果线程为1号线程那么startPos=上一次的下载进度;
  • endPos=每个线程分页下载文件大小*当前线程编号-1,-1代表“不计算文件末尾结束符”;
int startPos = block * (threadId - 1) + downLength;//开始位置
int endPos = block * threadId - 1;//结束位置

自定义Android里的ProgressBar的样式

第一步:

res\values\colors.xml文件中加入一个ProgressBar的底色theme_progressbar

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
    <color name="theme_progressbar">#D0E3F7</color>
</resources>

这是一个很浅很淡的蓝色。

第二步:

res\drawable\下,新建一个progressbar_color.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
 
    <!-- 背景  gradient是渐变,corners定义的是圆角 -->
    <item android:id="@android:id/background">
        <shape>
            <corners android:radius="3dp"/>
 
            <solid android:color="@color/theme_progressbar" />
        </shape>
    </item>
    <!-- 进度条 -->
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <corners android:radius="3dp"/>
                <solid android:color="#FF51AAE6" />
            </shape>
        </clip>
    </item>
 
</layer-list>

第三步:

在activity_main.xml文件里定义progressbar时引用这个progressbar_color.xml文件。

 <ProgressBar
        android:id="@+id/progressBarDownload"
        style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:max="100"
        android:progressDrawable="@drawable/progressbar_color"
        android:visibility="visible" />

以上内容都准备好了,我们就可以进入全代码了。

项目结构

前端代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
    <Button
        android:id="@+id/buttonDownload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="download"
        android:layout_marginRight="10dp"
        android:textSize="20sp" />
 
    <Button
        android:id="@+id/buttonStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stop"
        android:textSize="20sp" />
    </LinearLayout>
    <ProgressBar
        android:id="@+id/progressBarDownload"
        style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:max="100"
        android:progressDrawable="@drawable/progressbar_color"
        android:visibility="visible" />
</LinearLayout>

后端代码

DbOpeerateHelper.java

package org.mk.android.demo.http;
 
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
 
public class DbOperateHelper extends SQLiteOpenHelper {
    private static final String TAG = "DemoContinueDownload";
    private static final String DB_NAME = "dw_manager.db";
    private static final String DB_TABLE = "dw_infor";
    private static final int DB_VERSION = 1;
    public DbOperateHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
 
    private static final String DB_CREATE =
 
            "CREATE TABLE dw_infor ("
                    +"dw_id    INTEGER PRIMARY KEY AUTOINCREMENT,"
                    +"download_path    VARCHAR,"
                    +"thread_id    INTEGER,"
                    +"download_length    INTEGER);";
 
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.i(TAG, ">>>>>>execute create table->" + DB_CREATE);
        db.execSQL(DB_CREATE);
        Log.i(TAG, ">>>>>>db init successfully");
    }
 
    @Override
    public void onUpgrade(SQLiteDatabase db, int _oldVersion, int _newVersion) {
        //db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
        //onCreate(_db);
        db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
        onCreate(db);
 
    }
}

DBService.java

package org.mk.android.demo.http;
 
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
 
public class DBService {
    private static final String TAG = "DemoContinueDownload";
    private DbOperateHelper dbHelper;
    private static final String DB_NAME = "dw_manager.db";
    private static final String DB_TABLE = "dw_infor";
    private static final int DB_VERSION = 1;
 
    public DBService(Context ctx){
        dbHelper=new DbOperateHelper(ctx,DB_NAME,null,DB_VERSION);
    }
    
    public List<DWManagerInfor> getData(String downloadPath)
    {
        //获得可读数据库句柄,通常内部实现返回的其实都是可写的数据库句柄
        //根据下载的路径查询所有现场的下载数据,返回的Cursor指向第一条记录之前
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select thread_id, download_length from dw_infor where download_path=?",
                new String[]{downloadPath});
        List<DWManagerInfor> data=new ArrayList<DWManagerInfor>();
        try {
            //从第一条记录开始遍历Cursor对象
            //cursor.moveToFirst();
            while (cursor.moveToNext()) {
                DWManagerInfor dwInfor =new DWManagerInfor();
                dwInfor.setThreadId(cursor.getInt(cursor.getColumnIndexOrThrow("thread_id")));
                dwInfor.setDownloadLength(cursor.getInt(cursor.getColumnIndexOrThrow("download_length")));
                data.add(dwInfor);
            }
        }catch(Exception e){
            Log.e(TAG,">>>>>>getData from db error: "+e.getMessage(),e);
        }finally{
            try {
                cursor.close();//关闭cursor,释放资源;
            }catch(Exception e){}
            try {
                db.close();
            }catch(Exception e){}
        }
        return data;
    }
 
    
 
    public void save(String downloadPath, Map<Integer,Integer> map)
    {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        db.beginTransaction();
        try{
 
            //使用增强for循环遍历数据集合
            for(Map.Entry<Integer, Integer> entry : map.entrySet())
            {
 
                db.execSQL("insert into dw_infor(download_path, thread_id, download_length) values(?,?,?)",
                        new Object[]{downloadPath, entry.geTKEy(),entry.getValue()});
            }
            //设置一个事务成功的标志,如果成功就提交事务,如果没调用该方法的话那么事务回滚
            //就是上面的数据库操作撤销
            db.setTransactionSuccessful();
        }catch(Exception e){
            Log.e(TAG,">>>>>>save download infor into db error: "+e.getMessage(),e);
        }finally{
            //结束一个事务
            db.endTransaction();
            try{
                db.close();
            }catch(Exception e){}
        }
    }
 
    public int updateItem(DWManagerInfor dwInfor)throws Exception{
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        try{
            ContentValues newValues = new ContentValues();
            newValues.put("download_length",dwInfor.getDownloadLength());
            newValues.put("thread_id",dwInfor.getThreadId());
            newValues.put("download_path",dwInfor.getDownloadPath());
            return db.update(DB_TABLE,newValues,"thread_id='"+dwInfor.getThreadId()+"' and download_path='"+dwInfor.getDownloadPath()+"'",null);
        }catch(Exception e){
            Log.e(TAG,"update item error: "+e.getMessage(),e);
            throw new Exception("update item error: "+e.getMessage(),e);
        }finally{
            try{
                db.close();
            }catch(Exception e){}
        }
    }
    public void delete(String path)
    {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        try {
            String deleteSql = "delete from dw_infor where download_path=?";
            db.execSQL(deleteSql, new Object[]{path});
        }catch(Exception e){
            Log.e(TAG,">>>>>>delete from path->"+path+" error: "+e.getMessage(),e);
        }finally{
            try{
                db.close();
            }catch(Exception e){}
        }
    }
    public long addItem(DWManagerInfor dwInfor)throws Exception{
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        try{
            ContentValues newValues = new ContentValues();
            newValues.put("download_path", dwInfor.getDownloadPath());
            newValues.put("thread_id", dwInfor.getThreadId());
            newValues.put("download_length", dwInfor.getDownloadLength());
            Log.i(TAG, "addItem successfully");
            return db.insert(DB_TABLE, null, newValues);
        }catch(Exception e){
            Log.e(TAG,">>>>>>addItem into db error: "+e.getMessage(),e);
            throw new Exception(">>>>>>addItem into db error: "+e.getMessage(),e);
        }finally{
            try{
                db.close();
            }catch(Exception e){}
        }
    }
}

DownloadProgressListener.java

package org.mk.android.demo.http;
 
public interface DownloadProgressListener {
    public void onDownloadSize(int size);
}

DWManagerInfor.java

package org.mk.android.demo.http;
 
import java.io.Serializable;
 
public class DWManagerInfor implements Serializable {
    private int dwId=0;
    private int threadId=0;
 
 
    public int getDwId() {
        return dwId;
    }
 
    public void setDwId(int dwId) {
        this.dwId = dwId;
    }
 
    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;
    }
 
    public String getDownloadPath() {
        return downloadPath;
    }
 
    public void setDownloadPath(String downloadPath) {
        this.downloadPath = downloadPath;
    }
 
    private int downloadLength=0;
    private String downloadPath="";
 
}

DownloadService.java

这是一个主要的用于启动多线程下载和操作断点信息的类,在这个类内会分出3个子线程,每个子线程内又把这个类的this传入在子线程内进行回调、写下载时的实时信息入库、传递子线程状态,因此它是一个核心类。

package org.mk.android.demo.http;
 
import android.content.Context;
import android.os.Environment;
import android.util.Log;
 
import androidx.annotation.NonNull;
 
import org.apache.commons.io.FilenameUtils;
 
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.sql.Array;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
 
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
 
public class DownloadService {
    private static final String TAG = "DemoContinueDownload";
    private File saveFile;
    private int downloadedSize = 0;               //已下载的文件长度
    private Context context = null;
    private int threadCount = 3;
    private int fileSize = 0;
    private int block = 0;
    private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();  //缓存个条线程的下载的长度
    //private DBAdapter dbAdapter = null;
    private DBService dbService=null;
    private DownloadThread[] threads;        //根据线程数设置下载的线程池
    private boolean exited = false;
    private String downloadUrl = "";
 
    public DownloadService(Context context, String downloadUrl) {
        this.context = context;
        //dbAdapter = new DBAdapter(context);
        dbService=new DBService(context);
        this.threads = new DownloadThread[threadCount];
        this.downloadUrl = downloadUrl;
    }
 
    public int getFileSize() {
        return this.fileSize;
    }
 
    
    public void exit() {
        Log.i(TAG, ">>>>>>触发了exited");
        this.exited = true;    //将退出的标志设置为true;
    }
 
    public boolean getExited() {
        return this.exited;
    }
 
    
    protected synchronized void append(int size) {
        //把实时下载的长度加入到总的下载长度中
        downloadedSize += size;
    }
 
    
    protected synchronized void update(int threadId, int pos) {
        try {
            this.data.put(threadId, pos);
            //dbAdapter.open();
            DWManagerInfor dwInfor = new DWManagerInfor();
            dwInfor.setDownloadPath(this.downloadUrl);
            dwInfor.setThreadId(threadId);
            dwInfor.setDownloadLength(pos);
            //dbAdapter.updateItem(dwInfor);
            dbService.updateItem(dwInfor);
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>update error: " + e.getMessage(), e);
        }
        //把指定线程id的线程赋予最新的下载长度,以前的值会被覆盖掉
        this.data.put(threadId, pos);
        //更新数据库中制定线程的下载长度
 
    }
 
    private String generateFile(long fileLength, boolean generateFile) throws Exception {
        String end = downloadUrl.substring(downloadUrl.lastIndexOf("."));
        URL url = new URL(downloadUrl);
        //String downloadFilePath = "Cache_" + System.currentTimeMillis() + end;
        String urlFileName = FilenameUtils.getName(url.getPath());
        RandomAccessFile file = null;
        try {
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String fileName = Environment.getExternalStorageDirectory().getCanonicalPath() + "/" + urlFileName;
                Log.i(TAG, ">>>>>>需要操作的文件名为->" + fileName);
                Log.i(TAG,">>>>>>downloadedSize->"+downloadedSize+"  fileLength->"+fileLength);
                if (generateFile) {
                    if(downloadedSize==0||downloadedSize>=fileLength) {
                        Log.i(TAG,">>>>>>新建文件并设定长度->"+fileLength);
                        file = new RandomAccessFile(fileName, "rwd");
                        file.setLength(fileLength);
                        file.close();
                    }else{
                        Log.i(TAG,">>>>>>文件存在,返回文件名进行续传");
                    }
                }
                return fileName;
            } else {
                throw new Exception("SD卡不可读写");
            }
        } catch (Exception e) {
            throw new Exception("GenerateTempFile error: " + e.getMessage(), e);
        } finally {
            try {
                file.close();
            } catch (Exception e) {
            }
        }
 
    }
 
    public int getRemainDownloadLen(int threadCount, long fileLength) {
        int block = 0;
        try {
            block = (int) fileLength % threadCount == 0 ? (int) fileLength / threadCount :
                    (int) fileLength / threadCount + 1;
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>getRemainDownloadLen error: " + e.getMessage(), e);
        }
        return block;
    }
 
    public void download(boolean generateFile, DownloadProgressListener downloadProgressListener) throws Exception {
        try {
            fileSize = getDownloadFileSize(downloadUrl);
            //把所有的DB内已经存在的size放入全局的data中,以作缓存
            List<DWManagerInfor> dwInforList = new ArrayList<DWManagerInfor>();
            dwInforList = dbService.getData(downloadUrl);
            Log.i(TAG, ">>>>>>in download method the dwInforList size->" + dwInforList.size());
            if (dwInforList.size() > 0) {
                for (DWManagerInfor dwInfor : dwInforList) {
                    downloadedSize += dwInfor.getDownloadLength();
                    this.data.put(dwInfor.getThreadId(), dwInfor.getDownloadLength());
                }
            } else {
                for (int i = 0; i < threadCount; i++) {
                    this.data.put(i + 1, 0);
                }
            }
            this.block = getRemainDownloadLen(3, fileSize);
            Log.i(TAG,">>>>>>downloadSize->"+downloadedSize);
            String saveFileName = generateFile(this.fileSize, generateFile);//生成一个Random空文件并把文件长度设置好
            Log.i(TAG, ">>>>>>开始生成线程进行分: " + this.threadCount + " 条线程并行下载...每条线程的block->" + this.block);
            Log.i(TAG, ">>>>>>全局data size->" + data.size());
            for (int i = 0; i < this.threads.length; i++) {//开启线程进行下载
                int downLength = 0;
                if (data.size() > 0) {
                    downLength = this.data.get(i + 1);
                }
                Log.i(TAG, ">>>>>>开启前发觉当前下载进度为->" + downLength);
                //通过特定的线程id获取该线程已经下载的数据长度
                //判断线程是否已经完成下载,否则继续下载
                if (downLength < this.block && this.downloadedSize < this.fileSize) {
                    //初始化特定id的线程
                    //this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1),
                    // i+1);
                    this.threads[i] = new DownloadThread(this, downloadUrl, saveFileName, this.block,
                            this.data.get(i + 1), i + 1);
                    //设置线程优先级,Thread.NORM_PRIORITY = 5;
                    //Thread.MIN_PRIORITY = 1;Thread.MAX_PRIORITY = 10,数值越大优先级越高
                    this.threads[i].setPriority(7);
                    this.threads[i].start();    //启动线程
                } else {
                    Log.i(TAG, "当前线程不用下载,因为当前线程己下载长度downLength->" + downLength + " block->" + this.block);
                    this.threads[i] = null;   //表明线程已完成下载任务
                }
            }
            //dbAdapter.open();
            dbService.delete(downloadUrl);
            dbService.save(downloadUrl, this.data);
            //把下载的实时数据写入数据库中
            boolean notFinish = true;
            //下载未完成
            while (notFinish) {
                // 循环判断所有线程是否完成下载
                Thread.sleep(300);
                notFinish = false;
                for (int i = 0; i < threadCount; i++) {
                    if (this.threads[i] != null && !this.threads[i].isFinish()) {
                        //如果发现线程未完成下载
                        notFinish = true;
                        //设置标志为下载没有完成,以便于外层while循环不断check;
                    }
                }
                if (downloadProgressListener != null) {
                    downloadProgressListener.onDownloadSize(this.downloadedSize);
                }
                //通知目前已经下载完成的数据长度
            }
            if (downloadedSize == this.fileSize) {
                  dbService.delete(downloadUrl);
            }
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>download error: " + e.getMessage(), e);
            throw new Exception(">>>>>>download error: " + e.getMessage(), e);
        }
 
    }
 
    public int getDownloadFileSize(String downloadUrl) throws Exception {
        int size = -1;
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间
                .readTimeout(10, TimeUnit.SECONDS).build();//设置读取超时时间
        Request request = new Request.Builder().url(downloadUrl)//请求接口,如果需要传参拼接到接口后面
                .build(); //创建Request对象
        Response response = null;
        try {
            Call call = client.newCall(request);
            response = call.execute();
            if (200 == response.code()) {
                Log.d(TAG, ">>>>>>response.code()==" + response.code());
                Log.d(TAG, ">>>>>>response.message()==" + response.message());
                try {
                    size = (int) response.body().contentLength();
                    Log.d(TAG, ">>>>>>file length->" + size);
                    //fileSizeListener.onHttpResponse((int) size);
                } catch (Exception e) {
                    Log.e(TAG, ">>>>>>get remote file size error: " + e.getMessage(), e);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>open connection to path->" + downloadUrl + "\nerror: " + e.getMessage(), e);
            throw new Exception(">>>>>>getDownloadFileSize from->" + downloadUrl + "\nerror: " + e.getMessage(), e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {
            }
        }
        return size;
    }
 
}

DownloadThread.java

这个类就是每一个子线程的实现了。在这个类里每一个子线程会启动OkHttp并使用http-header: Range去做断点下载。

值得注意的是,如果你的http-header带着Rnage去做请求,你得到的response code不是200还是206即:partial content。

package org.mk.android.demo.http;
 
import android.content.Context;
import android.util.Log;
 
import androidx.annotation.NonNull;
 
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
 
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
 
public class DownloadThread extends Thread {
    private static final String TAG = "DemoContinueDownload";
    private String downloadUrl;              //下载的URL
    private int block;                //每条线程下载的大小
    private int threadId = 1;            //初始化线程id设置
    private int downLength;             //该线程已下载的数据长度
    private boolean finish = false;         //该线程是否完成下载的标志
    private DownloadService downloader;
    private String saveFileName = "";
 
    //private FileDownloadered downloader;      //文件下载器
    public DownloadThread(DownloadService downloader, String downloadUrl, String saveFileName, int block,
            int downLength, int threadId) {
        this.downloader = downloader;
        this.downloadUrl = downloadUrl;
        this.saveFileName = saveFileName;
        this.block = block;
        this.downLength = downLength;
        this.threadId = threadId;
    }
 
    @Override
    public void run() {
        Log.i(TAG, ">>>>>>downloadLength->" + downLength + " block->" + block);
        if (downLength < block) {
            int startPos = block * (threadId - 1) + downLength;//开始位置
            int endPos = block * threadId - 1;//结束位置
            OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间
                    .readTimeout(10, TimeUnit.SECONDS)//设置读取超时时间
                    .build();
            Request request = new Request.Builder().get().url(downloadUrl)//请求接口,如果需要传参拼接到接口后面
                    .addHeader("Referer", downloadUrl)
                    .addHeader("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, *
    public boolean isFinish() {
        return finish;
    }
 
    
    public long getDownLength() {
        return downLength;
    }
}

MainActivity.java

package org.mk.android.demo.http;
 
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
 
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
    private SQLiteDatabase db;
    private Context context;
    //private DBAdapter dbAdapter;
    private DBService dbService;
    private Button buttonDownload;
    private Button buttonStop;
    private DownloadTask downloadTask;
    private ProgressBar progressBarDownload;
    private static final String TAG = "DemoContinueDownload";
    //private static final String DOWNLOAD_URL = "http://www.jszjenergy.cn/data/upload/image/20191231/1577758425809614.jpg";
    private static final String DOWNLOAD_URL = "https://7-zip.org/a/7z2201.exe";
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = getApplicationContext();
        //dbAdapter = new DBAdapter(context);
        dbService=new DBService(context);
        progressBarDownload = (ProgressBar) findViewById(R.id.progressBarDownload);
        buttonDownload = (Button) findViewById(R.id.buttonDownload);
        buttonStop = (Button) findViewById(R.id.buttonStop);
        //progressBarDownload.setVisibility(View.GoNE);
        buttonDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                        Log.i(TAG, ">>>>>>version.SDK->" + Build.VERSION.SDK_INT);
                        if (!Environment.isExternalStorageManager()) {
                            Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                            startActivity(intent);
                            return;
                        }
                    }
                    downloadTask = new DownloadTask();
                    downloadTask.start();
                } catch (Exception e) {
                    Log.e(TAG, ">>>>>>downloadTest error: " + e.getMessage(), e);
                }
            }
        });
        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (downloadTask != null) {
                    downloadTask.exit();
                }
            }
        });
    }
 
    private Handler downloadHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            Log.i(TAG, ">>>>>>receive handler Message msg.what is: " + msg.what);
            switch (msg.what) {
                case 101:
                    progressBarDownload.setVisibility(View.VISIBLE);
                    //progressBarDownload.setProgress();
                    int inputNum = msg.getData().getInt("pgValue");
                    progressBarDownload.setProgress(inputNum);
                    if (inputNum >= 100) {
                        Toast.makeText(context, "下载完成", Toast.LENGTH_LONG).show();
                    }
                    break;
            }
            return false;
        }
    });
 
    private class DownloadTask extends Thread {
        private DownloadService loader;
 
        
        public void exit() {
            if (loader != null) {
                loader.exit();
            }
        }
 
        @Override
        public void run() {
            try {
                loader = new DownloadService(context, DOWNLOAD_URL);
                //dbAdapter = new DBAdapter(context);
                //dbAdapter.open();
                //dbAdapter.delete(DOWNLOAD_URL);
                loader.download(true, new DownloadProgressListener() {
                    @Override
                    public void onDownloadSize(int size) {
                        int fileSize=loader.getFileSize();
                        Log.i(TAG, ">>>>>>下载中,当前尺寸: " + size+" totalSize->"+fileSize);
                        float progress = ((float) size / (float) fileSize) * 100;
                        int pgValue = (int) progress;
                        Message msg = new Message();
                        msg.what = 101;
                        Bundle bundle = new Bundle();
                        bundle.putInt("pgValue", pgValue);
                        msg.setData(bundle);
                        downloadHandler.sendMessage(msg);
                    }
                });
            } catch (Exception e) {
                Log.e(TAG, ">>>>>>downloadTest error: " + e.getMessage(), e);
            } finally {
                //dbAdapter.close();
            }
        }
    }
}

为了正确运行上述内容你需要在gradle的build文件内加入OkHttp和commons-io的依赖包。

implementation 'com.squareup.okhttp3:okhttp:3.10.0'

implementation group: 'commons-io', name: 'commons-io', version: '2.6'

运行后的效果

当你无论如何stop再download再stop或者下载完后多次再download,那么当文件被成功下载后,会在Android的资源列表里此处显示下载的资源。

它位于data\media\0下。

为了验证你下载的正确性,你可以把这个资源右键->另存出去。然后双击这个安装程序,如果它可以正确安装那么说明你的断点下载是正确了。

结束今天的课程,不妨自己动一下手试试看吧。

附、AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <!-- 在SDCard中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />
    <!-- 往SDCard写入数据权限 -->
    <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
    <!--外部存储的写权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!--外部存储的读权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:networkSecurityConfig="@xml/network_config"
        android:requestLegacyExternalStorage="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.DemoContinueDownloadProcess"
        tools:targetapi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
 
            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>
 
</manifest>

以上就是Android入门之利用OKHttp实现断点续传功能的详细内容,更多关于Android OKHttp断点续传的资料请关注编程网其它相关文章!

--结束END--

本文标题: Android入门之利用OKHttp实现断点续传功能

本文链接: https://www.lsjlt.com/news/177161.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Android入门之利用OKHttp实现断点续传功能
    目录简介课程目标断点下载的原理自定义Android里的ProgressBar的样式项目结构前端代码后端代码DbOpeerateHelper.javaDBService.javaDow...
    99+
    2023-01-09
    Android OKHttp断点续传功能 Android OKHttp断点续传 Android 断点续传 Android OKHttp
  • Android实现断点续传功能
    本文实例为大家分享了Android实现断点续传的具体代码,供大家参考,具体内容如下 断点续传功能,在文件上传中断时,下次上传同一文件时,能在上次的断点处继续上传,可节省时间和流量 总...
    99+
    2024-04-02
  • Golang实现断点续传功能
    本文实例为大家分享了Golang实现断点续传的具体代码,供大家参考,具体内容如下 1、将文件pic_src.jpg复制到pic_des.jpg文件; 2、读写过程中断时,读写的中断位...
    99+
    2024-04-02
  • android怎么实现多线程断点续传功能
    这篇文章主要介绍了android怎么实现多线程断点续传功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。具体内容如下布局:<xml version="...
    99+
    2023-05-30
    android 多线程
  • android原生实现多线程断点续传功能
    本文实例为大家分享了android实现多线程断点续传功能的具体代码,供大家参考,具体内容如下 需求描述: 输入一个下载地址,和要启动的线程数量,点击下载 利用多线程将文件下载到手机端...
    99+
    2024-04-02
  • Android中怎么利用FTP实现多线程断点续传下载上传功能
    Android中怎么利用FTP实现多线程断点续传下载上传功能,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。FTP下载原理FTP单线程断点续传FTP和传统的HTT...
    99+
    2023-05-30
    android
  • 怎么在Android中利用多线程实现一个断点续传下载功能
    怎么在Android中利用多线程实现一个断点续传下载功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。原理其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重...
    99+
    2023-05-31
    android 多线程 roi
  • Java中怎么实现断点续传功能
    这篇文章给大家介绍Java中怎么实现断点续传功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。(一)断点续传的原理   其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已。   打个比方,浏览器请...
    99+
    2023-06-03
  • C#中怎么实现断点续传功能
    本篇文章为大家展示了C#中怎么实现断点续传功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。以下是一个请求报文与相应的回复报文的例子:GET /image/index_r4_c1.jpg&...
    99+
    2023-06-17
  • Android实现多线程断点续传
    本文实例为大家分享了Android实现多线程断点续传的具体代码,供大家参考,具体内容如下 多线程下载涉及到的知识点: 1、Service的使用:我们在Service中去下载文件;2、...
    99+
    2024-04-02
  • HTML5如何实现文件断点续传功能
    这篇文章主要为大家展示了“HTML5如何实现文件断点续传功能”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“HTML5如何实现文件断点续传功能”这篇文章吧。HTM...
    99+
    2024-04-02
  • 通过Java实现文件断点续传功能
    目录什么是断点续传解决方案效果演示参考代码前端后端什么是断点续传 用户上传大文件,网络差点的需要历时数小时,万一线路中断,不具备断点续传的服务器就只能从头重传,而断点续传就是,允许用...
    99+
    2024-04-02
  • 如何在Android中利用OkHttp实现一个图片上传功能
    本篇文章给大家分享的是有关如何在Android中利用OkHttp实现一个图片上传功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。实现方法如下:object UploadFil...
    99+
    2023-05-31
    okhttp android roi
  • Android基于OkHttp实现文件上传功能
    本文实例为大家分享了Android基于OkHttp实现文件上传的具体代码,供大家参考,具体内容如下 一、相关概述 Android请求访问服务端大多数情况下依旧是使用http协议,故而...
    99+
    2024-04-02
  • Java实现断点续传功能的示例代码
    目录一、题目描述二、解题思路三、代码详解一、题目描述 题目实现:网络资源的断点续传功能。 二、解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节...
    99+
    2022-11-13
    Java实现断点续传 Java断点续传
  • Android如何实现简单断点续传和下载到本地功能
    这篇文章主要介绍了Android如何实现简单断点续传和下载到本地功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。具体内容如下效果展示导入依赖与权限依赖compile&nbs...
    99+
    2023-05-30
    android
  • 如何在Android中利用OkHttp与php实现一个图片上传功能
    这篇文章给大家介绍如何在Android中利用OkHttp与php实现一个图片上传功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。今天写项目的时候需要多图片上传,就用okhttp简单写一个例子。public class...
    99+
    2023-05-31
    android okhttp php
  • 怎么通过Java实现文件断点续传功能
    这篇文章主要介绍“怎么通过Java实现文件断点续传功能”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么通过Java实现文件断点续传功能”文章能帮助大家解决问题。什么是断点续传用户上传大文件,网络差...
    99+
    2023-06-30
  • 怎么在Android中实现一个多线程断点续传下载功能
    本篇文章给大家分享的是有关怎么在Android中实现一个多线程断点续传下载功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。1、布局实现具体布局内容如下:<LinearL...
    99+
    2023-05-30
    android
  • JavaScript利用切片实现大文件断点续传
    目录什么是断点续传实现思路需要后端提供的api获取已经上传的切片上传切片合并切片前端代码细节实现HASH值的获取方法切片处理总体html结构使用axios发送请求整体逻辑和代码实现效...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作