OKHttp实现大文件的断点续传
来源:互联网 发布:大华网络监控安装步骤 编辑:程序博客网 时间:2024/06/06 14:21
本文的亮点:
(1)网络请求用OKHttp进行下载大文件
(2)实现了大文件的断点续传
(3)取消下载时,删除已经下载的文件。
实现效果图:
直接给出工程:
(1)定义一个接口:DownloadListener.java
package com.example.servicebestpractice;/** * Created by Administrator on 2017/2/23. */public interface DownloadListener { /** * 通知当前的下载进度 * @param progress */ void onProgress(int progress); /** * 通知下载成功 */ void onSuccess(); /** * 通知下载失败 */ void onFailed(); /** * 通知下载暂停 */ void onPaused(); /** * 通知下载取消事件 */ void onCanceled();}(2)开启一个异步下载任务:DownloadTask.java
package com.example.servicebestpractice;import android.os.AsyncTask;import android.os.Environment;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;/** * Created by Administrator on 2017/2/23. *//** * String 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。 * Integer 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。 * Integer 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。 */public class DownloadTask extends AsyncTask<String,Integer,Integer> { public static final int TYPE_SUCCESS=0; public static final int TYPE_FAILED=1; public static final int TYPE_PAUSED=2; public static final int TYPE_CANCELED=3; private DownloadListener listener; private boolean isCanceled=false; private boolean isPaused=false; private int lastProgress; public DownloadTask(DownloadListener listener) { this.listener = listener; } /** * 这个方法中的所有代码都会在子线程中运行,我们应该在这里处理所有的耗时任务。 * @param params * @return */ @Override protected Integer doInBackground(String... params) { InputStream is=null; RandomAccessFile savedFile=null; File file=null; long downloadLength=0; //记录已经下载的文件长度 //文件下载地址 String downloadUrl=params[0]; //下载文件的名称 String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/")); //下载文件存放的目录 String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); //创建一个文件 file=new File(directory+fileName); if(file.exists()){ //如果文件存在的话,得到文件的大小 downloadLength=file.length(); } //得到下载内容的大小 long contentLength=getContentLength(downloadUrl); if(contentLength==0){ return TYPE_FAILED; }else if(contentLength==downloadLength){ //已下载字节和文件总字节相等,说明已经下载完成了 return TYPE_SUCCESS; } OkHttpClient client=new OkHttpClient(); /** * HTTP请求是有一个Header的,里面有个Range属性是定义下载区域的,它接收的值是一个区间范围, * 比如:Range:bytes=0-10000。这样我们就可以按照一定的规则,将一个大文件拆分为若干很小的部分, * 然后分批次的下载,每个小块下载完成之后,再合并到文件中;这样即使下载中断了,重新下载时, * 也可以通过文件的字节长度来判断下载的起始点,然后重启断点续传的过程,直到最后完成下载过程。 */ Request request=new Request.Builder() .addHeader("RANGE","bytes="+downloadLength+"-") //断点续传要用到的,指示下载的区间 .url(downloadUrl) .build(); try { Response response=client.newCall(request).execute(); if(response!=null){ is=response.body().byteStream(); savedFile=new RandomAccessFile(file,"rw"); savedFile.seek(downloadLength);//跳过已经下载的字节 byte[] b=new byte[1024]; int total=0; int len; while((len=is.read(b))!=-1){ if(isCanceled){ return TYPE_CANCELED; }else if(isPaused){ return TYPE_PAUSED; }else { total+=len; savedFile.write(b,0,len); //计算已经下载的百分比 int progress=(int)((total+downloadLength)*100/contentLength); //注意:在doInBackground()中是不可以进行UI操作的,如果需要更新UI,比如说反馈当前任务的执行进度, //可以调用publishProgress()方法完成。 publishProgress(progress); } } response.body().close(); return TYPE_SUCCESS; } } catch (IOException e) { e.printStackTrace(); }finally { try{ if(is!=null){ is.close(); } if(savedFile!=null){ savedFile.close(); } if(isCanceled&&file!=null){ file.delete(); } }catch (Exception e){ e.printStackTrace(); } } return TYPE_FAILED; } /** * 当在后台任务中调用了publishProgress(Progress...)方法之后,onProgressUpdate()方法 * 就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以 * 对界面进行相应的更新。 * @param values */ protected void onProgressUpdate(Integer...values){ int progress=values[0]; if(progress>lastProgress){ listener.onProgress(progress); lastProgress=progress; } } /** * 当后台任务执行完毕并通过Return语句进行返回时,这个方法就很快被调用。返回的数据会作为参数 * 传递到此方法中,可以利用返回的数据来进行一些UI操作。 * @param status */ @Override protected void onPostExecute(Integer status) { switch (status){ case TYPE_SUCCESS: listener.onSuccess(); break; case TYPE_FAILED: listener.onFailed(); break; case TYPE_PAUSED: listener.onPaused(); break; case TYPE_CANCELED: listener.onCanceled(); break; default: break; } } public void pauseDownload(){ isPaused=true; } public void cancelDownload(){ isCanceled=true; } /** * 得到下载内容的大小 * @param downloadUrl * @return */ private long getContentLength(String downloadUrl){ OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder().url(downloadUrl).build(); try { Response response=client.newCall(request).execute(); if(response!=null&&response.isSuccessful()){ long contentLength=response.body().contentLength(); response.body().close(); return contentLength; } } catch (IOException e) { e.printStackTrace(); } return 0; }}
(3)开启一个服务:DownloadService.java
package com.example.servicebestpractice;import android.app.Notification;import android.app.NotificationManager;import android.app.PendingIntent;import android.app.Service;import android.content.Context;import android.content.Intent;import android.graphics.BitmapFactory;import android.os.Binder;import android.os.Environment;import android.os.IBinder;import android.support.v4.app.NotificationCompat;import android.widget.Toast;import java.io.File;/** * 为了保证DownloadTask可以一直在后台运行,我们还需要创建一个下载的服务。 */public class DownloadService extends Service { private DownloadTask downloadTask; private String downloadUrl; private DownloadListener listener=new DownloadListener() { /** * 构建了一个用于显示下载进度的通知 * @param progress */ @Override public void onProgress(int progress) { //NotificationManager的notify()可以让通知显示出来。 //notify(),接收两个参数,第一个参数是id:每个通知所指定的id都是不同的。第二个参数是Notification对象。 getNotificationManager().notify(1,getNotification("Downloading...",progress)); } /** * 创建了一个新的通知用于告诉用户下载成功啦 */ @Override public void onSuccess() { downloadTask=null; //下载成功时将前台服务通知关闭,并创建一个下载成功的通知 stopForeground(true); getNotificationManager().notify(1,getNotification("Download Success",-1)); Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show(); } /** *用户下载失败 */ @Override public void onFailed() { downloadTask=null; //下载失败时,将前台服务通知关闭,并创建一个下载失败的通知 stopForeground(true); getNotificationManager().notify(1,getNotification("Download Failed",-1)); Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show(); } /** * 用户暂停 */ @Override public void onPaused() { downloadTask=null; Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_SHORT).show(); } /** * 用户取消 */ @Override public void onCanceled() { downloadTask=null; //取消下载,将前台服务通知关闭,并创建一个下载失败的通知 stopForeground(true); Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_SHORT).show(); } }; private DownloadBinder mBinder=new DownloadBinder(); @Override public IBinder onBind(Intent intent) { return mBinder; } /** * 为了要让DownloadService可以和活动进行通信,我们创建了一个DownloadBinder对象 */ class DownloadBinder extends Binder{ /** * 开始下载 * @param url */ public void startDownload(String url){ if(downloadTask==null){ downloadUrl=url; downloadTask=new DownloadTask(listener); //启动下载任务 downloadTask.execute(downloadUrl); startForeground(1,getNotification("Downloading...",0)); Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show(); } } /** * 暂停下载 */ public void pauseDownload(){ if(downloadTask!=null){ downloadTask.pauseDownload(); } } /** * 取消下载 */ public void cancelDownload(){ if(downloadTask!=null){ downloadTask.cancelDownload(); }else { if(downloadUrl!=null){ //取消下载时需要将文件删除,并将通知关闭 String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/")); String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); File file=new File(directory+fileName); if(file.exists()){ file.delete(); } getNotificationManager().cancel(1); stopForeground(true); Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show(); } } } } /** * 获取NotificationManager的实例,对通知进行管理 * @return */ private NotificationManager getNotificationManager(){ return (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } /** * * @param title * @param progress * @return */ private Notification getNotification(String title,int progress){ Intent intent=new Intent(this,MainActivity.class); //PendingIntent是等待的Intent,这是跳转到一个Activity组件。当用户点击通知时,会跳转到MainActivity PendingIntent pi=PendingIntent.getActivity(this,0,intent,0); /** * 几乎Android系统的每一个版本都会对通知这部分功能进行获多或少的修改,API不稳定行问题在通知上面凸显的尤其严重。 * 解决方案是:用support库中提供的兼容API。support-v4库中提供了一个NotificationCompat类,使用它可以保证我们的 * 程序在所有的Android系统版本中都能正常工作。 */ NotificationCompat.Builder builder=new NotificationCompat.Builder(this); //设置通知的小图标 builder.setSmallIcon(R.mipmap.ic_launcher); //设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标 builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)); //当通知被点击的时候,跳转到MainActivity中 builder.setContentIntent(pi); //设置通知的标题 builder.setContentTitle(title); if(progress>0){ //当progress大于或等于0时,才需要显示下载进度 builder.setContentText(progress+"%"); builder.setProgress(100,progress,false); } return builder.build(); }}
(4)MainActivity.java
package com.example.servicebestpractice;import android.Manifest;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.content.pm.PackageManager;import android.os.Bundle;import android.os.IBinder;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private DownloadService.DownloadBinder downloadBinder; private ServiceConnection connection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder=(DownloadService.DownloadBinder) service; } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button startDownload=(Button) findViewById(R.id.start_download); startDownload.setOnClickListener(this); Button pauseDownload=(Button) findViewById(R.id.pause_download); pauseDownload.setOnClickListener(this); Button cancelDownload=(Button)findViewById(R.id.cancel_download); cancelDownload.setOnClickListener(this); Intent intent=new Intent(this,DownloadService.class); //这一点至关重要,因为启动服务可以保证DownloadService一直在后台运行,绑定服务则可以让MaiinActivity和DownloadService //进行通信,因此两个方法的调用都必不可少。 startService(intent); //启动服务 bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务 /** *运行时权限处理:我们需要再用到权限的地方,每次都要检查是否APP已经拥有权限 * 下载功能,需要些SD卡的权限,我们在写入之前检查是否有WRITE_EXTERNAL_STORAGE权限,没有则申请权限 */ if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } } public void onClick(View v){ if(downloadBinder==null){ return; } switch (v.getId()){ case R.id.start_download: //String url="http://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe"; String url="http://10.0.2.2:8080/ChromeSetup.exe"; downloadBinder.startDownload(url); break; case R.id.pause_download: downloadBinder.pauseDownload(); break; case R.id.cancel_download: downloadBinder.cancelDownload(); break; default: break; } } /** * 用户选择允许或拒绝后,会回调onRequestPermissionsResult * @param requestCode 请求码 * @param permissions * @param grantResults 授权结果 */ @Override public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) { switch (requestCode){ case 1: if(grantResults.length>0&&grantResults[0]!= PackageManager.PERMISSION_GRANTED){ Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show(); finish(); } break; } } @Override protected void onDestroy() { super.onDestroy(); //解除绑定服务 unbindService(connection); }}(5)简单的布局文件:activity_main.xml
<?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:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.servicebestpractice.MainActivity"> <Button android:id="@+id/start_download" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start Download"/> <Button android:id="@+id/pause_download" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Pause Download"/> <Button android:id="@+id/cancel_download" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Cancel Download"/></LinearLayout>(6)AndroidMainfest.xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.servicebestpractice"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".DownloadService" android:enabled="true" android:exported="true"></service> </application></manifest>(7)build.gradle
compile 'com.squareup.okhttp3:okhttp:3.4.1'(8)最后给出项目下载地址:
https://github.com/Microstrong0305/OKHttp_DownloadFile
0 0
- OKHttp实现大文件的断点续传
- 大文件的断点续传
- OkHttp使用+文件的上传+断点续传
- 【Android】- OkHttp实现断点续传
- OkHttp实现断点续传
- OkHttp实现断点续传
- 用 Chukeh 结合 Delphi 实现大文件上传的断点续传
- 对大文件的断点续传
- Android okhttp+rxjava实现多文件下载和断点续传
- Android okhttp+rxjava实现多文件下载和断点续传
- Android okhttp+rxjava实现多文件下载和断点续传
- service+okhttp实现断点续传下载
- curl 实现大文件断点续传下载
- Asp.net 实现断点续传 下载大文件
- 类似迅雷下载实现大文件断点续传
- 使用OkHttp实现下载的进度监听和断点续传
- 大文件断点续传
- html 大文件 断点续传
- 迷你TXT小说阅读器 V2.8 内测3 发布!
- 用Tensorflow拟合线性函数
- 【PAT】1119. Pre- and Post-order Traversals
- 新的一年,SEO之路如何走更好
- 1081. Rational Sum
- OKHttp实现大文件的断点续传
- POJ 1651 Multiplication Puzzle(区间DP)
- Java中静态代码块和构造代码块的对比
- 剑指offer 输入一棵二叉树,判断该二叉树是否是平衡二叉树。
- [连载]嵌入式实时操作系统AIOS设计与实现 – 准备
- Qt打开文件对话框同时选中多个文件
- 字符串转换成NSData
- Java中普通代码块,构造代码块,静态代码块区别及代码示例
- QList简单使用