Android(安卓)启动服务配合AsyncTask 使用OKHttp 实现断点下载大文件实例
前言:最近重读了下“第一行代码”,看到“第一行代码”的一个小项目,特写这篇博客梳理下流程。这个项目实现了使用OKHttp 断点下载大文件,通过服务在下载的过程中暂停和取消并更新通知消息,下面看下效果图:
首先总结一句话,在Android的多线程处理中,尽量做到在子线程中进行耗时操作,在主线程中更新界面UI。好了,下面开始写这个项目。
一。首先创建一个回调接口,用于对下载过程中的各种状态进行监听和回调,代码如下:
public interface DownloadListener { // 通知下载进度 void onProgress(int progress); // 通知下载成功 void onSuccess(); // 通知下载失败 void onFailed(); // 通知下载暂停 void onPaused(); //通知下载失败 void onCanceled();}
好吧,这里插一点题外话,回调函数。主要说一下回调函数的机制
(1)。设置接口,定义回调函数
public interface MyListener { void onClick();}
(2)。提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。
public class Realize { private MyListener myListener; public void setMyListener(MyListener myListener) { this.myListener = myListener; } public void doSth() { myListener.onClick(); }}
(3)。当特定的时间或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
public class Test { public static void main(String[] args) { Realize realize = new Realize(); realize.setMyListener(new MyListener() { @Override public void onClick() { System.out.println("特定事件或条件发生了"); } }); realize.doSth(); }}
上面就是一个回调函数的流程,仔细理解下,就能理解回调的实现了,下面回到正题。
二。使用AsyncTask实现下载功能的处理。
emmm,这里还要插入一下的AsyncTask的解释.AsyncTask的英文一种轻量级的异步任务类,这个类可以执行后台操作,并在用户界面上发布结果,而不必处理线程和处理程序.OK,先举个例子:
public class DownloadTask extends AsyncTask { /** * 刚开始执行的时候调用,可以用于进行一些界面上的初始化操作,比如说显示一个进度条对话框 */ @Override protected void onPreExecute() { super.onPreExecute(); } /** * 这里的代码会在子线程中执行,可以执行耗时操作 * * @param voids * @return */ @Override protected Boolean doInBackground(Void... voids) { // 反馈当前任务的进度,执行完这个方法会调用onProgressUpdate 方法 publishProgress(50); return null; } /** * 根据返回的数据更新UI * * @param values */ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } /** * 后台任务执行完毕通过return语句进行返回时 也可以根据返回的数据更新UI * * @param aBoolean */ @Override protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); }}
简单的来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。如果想要启动这个任务,只需编写以下代码即可:
new DownloadTask()。execute();
继续回到正题(这样讲着讲着会不会歪楼呀)先在app / build.gradle文件中的依赖中添加OKHttp依赖
编译'com.squareup.okhttp3:okhttp:3.4.1'
下面编写下载功能,新建一个DownloadFileTask继承AsyncTask。
// 第一个参数 传给后台参数 第二个 使用整型数据作为进度显示单位 第三个 使用整型数据反馈执行结果public class DownloadFileTask extends AsyncTask { // 下载成功 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 isCancelled = false; // 是否暂停 private boolean isPaused = false; // 当前进度 private int lastProgress; /** * 带监听的构造函数 * * @param listener */ public DownloadFileTask(DownloadListener listener) { this.listener = listener; } /** * 在后台执行具体的下载逻辑 是在子线程里面 可以执行耗时操作 */ @Override protected Integer doInBackground(String... strings) { // 文件输入流 InputStream is = null; RandomAccessFile accessFile = null; File file = null; // 记录已下载的文件长度 long downloadedLength = 0; // 获取下载的URL地址 String downloadUrl = strings[0]; // 从URL下载地址中截取下载的文件名 String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); // 获取SD卡的Download 目录 String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); // 得到要保存的文件 file = new File(directory + fileName); // 如果文件已经存在 获取文件的长度 if (file.exists()) { downloadedLength = file.length(); } try { // 获取待下载文件的字节长度 long contentLength = getContentLength(downloadUrl); // 如果待下载文件的字节长度为0 说明待下载文件有问题 if (contentLength == 0) { return TYPE_FAILED; } else if (contentLength == downloadedLength) { // 已下载字节和文件总字节相等 说明已经下载完了 return TYPE_SUCCESS; } // 获取OkHttpClient 对象 OkHttpClient client = new OkHttpClient(); // 创建请求 Request request = new Request.Builder() // 断点下载,指定从哪个字节开始下载 .addHeader("RANGE", "bytes=" + downloadedLength + "-") // 设置下载地址 .url(downloadUrl) .build(); // 获取响应 Response response = client.newCall(request).execute(); if (response != null) { // 读取服务器响应的数据 is = response.body().byteStream(); // 获取随机读取文件类 可以随机读取一个文件中指定位置的数据 accessFile = new RandomAccessFile(file, "rw"); // 跳过已下载的字节 accessFile.seek(downloadedLength); //指定每次读取文件缓存区的大小为1KB byte[] b = new byte[1024]; int total = 0; int len; // 每次读取的字节长度 while ((len = is.read(b)) != -1) { if (isCancelled) { return TYPE_CANCELED; } else if (isPaused) { return TYPE_PAUSED; } else { // 读取的全部字节的长度 total += len; // 写入每次读取的字节长度 accessFile.write(b, 0, len); // 计算已下载的百分比 int progress = (int) ((total + downloadedLength) * 100 / contentLength); // 更新进度条 publishProgress(progress); } } // 关闭连接 返回成功 response.body().close(); return TYPE_SUCCESS; } } catch (IOException e) { e.printStackTrace(); } finally { try { // 关闭输入流 if (is != null) { is.close(); } // 关闭文件 if (accessFile != null) { accessFile.close(); } Log.d("TAG", "这里永远都会执行 "); // 如果是取消的 就删除掉文件 if (isCancelled && file != null) { file.delete(); } } catch (IOException e) { e.printStackTrace(); } } return null; } /** * 获取下载文件的长度 * * @param downloadUrl * @return * @throws IOException */ private long getContentLength(String downloadUrl) throws IOException { // 获取OkHttpClient OkHttpClient client = new OkHttpClient(); // 创建请求 Request request = new Request.Builder() .url(downloadUrl) .build(); // 获取响应 Response response = client.newCall(request).execute(); // 如果响应是成功的话 if (response != null && response.isSuccessful()) { // 获取文件的长度 清除响应 long contentLength = response.body().contentLength(); response.close(); return contentLength; } return 0; } /** * 在界面上更新当前的下载进度 * * @param values */ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); int progress = values[0]; if (progress > lastProgress) { listener.onProgress(progress); lastProgress = progress; } } /** * 用于通知最后的下载结果 * * @param integer */ @Override protected void onPostExecute(Integer integer) { super.onPostExecute(integer); switch (integer) { case TYPE_SUCCESS: listener.onSuccess(); break; case TYPE_FAILED: listener.onFailed(); break; case TYPE_PAUSED: listener.onPaused(); break; case TYPE_CANCELED: listener.onCanceled(); break; } } /** * 暂停下载 */ public void pauseDownload() { isPaused = true; } /** * 取消下载 */ public void cancelDownload() { isCancelled = true; }}
三。创建一个下载的服务
没错,还得简单的介绍下服务(这么墨迹的吗)。一般我们都调用startService()方法来启动服务,并调用stopService()方法来停止这个服务,但这样启动服务后,活动无法干预到服务到底执行了怎样的逻辑。这时候就要用onBind()方法了,对服务进行绑定之后,就可以调用服务里的Binder提供的方法了.OK,下面举个个子子。首先创建一个MyService,代码如下:
public class MyService extends Service { private static final String TAG = "MyService"; private DownloadBinder mBinder = new DownloadBinder(); // 创建 DownloadBinder 实例 随便定义了两个方法 class DownloadBinder extends Binder { public void startDownload() { Log.d(TAG, "startDownload: executed"); } public int getProgress() { Log.d(TAG, "getProgress: executed"); return 0; } } /** * 返回这个DownloadBinder 实例 * * @param intent * @return */ @Override public IBinder onBind(Intent intent) { return mBinder; } }
然后在活动中绑定服务,实现活动去指挥服务去干什么。
public class FirstActivity extends AppCompatActivity { private Button btnBind; private Button btnUnBind; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); btnBind = (Button) findViewById(R.id.btnBind); btnUnBind = (Button) findViewById(R.id.btnUnBind); btnBind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 绑定服务 Intent intent = new Intent(FirstActivity.this, MyService.class); bindService(intent, connection, BIND_AUTO_CREATE); } }); btnUnBind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 解绑服务 unbindService(connection); } }); } private MyService.DownloadBinder downloadBinder; ServiceConnection connection = new ServiceConnection() { /** * 活动与服务绑定成功后 * @param name * @param service */ @Override public void onServiceConnected(ComponentName name, IBinder service) { // 向下转型获得DownloadBinder 实例 就能调用DownloadBinder的方法 进而控制服务的逻辑 downloadBinder = (MyService.DownloadBinder) service; downloadBinder.startDownload(); downloadBinder.getProgress(); } /** * 活动与服务解绑后 * @param name */ @Override public void onServiceDisconnected(ComponentName name) { } };}
好的,了解到活动如何去控制服务以后,下面正式写下载的服务,代码如下:
public class DownloadService extends Service { // 下载的异步操作类 private DownloadFileTask downloadFileTask; // 下载地址 private String downloadUrl; private static final String TAG = "DownloadService"; // 下载状态的回调 private DownloadListener listener = new DownloadListener() { /** * 更新下载进度状态 * @param progress */ @Override public void onProgress(int progress) { Log.d(TAG, "onProgress: -------" + progress); getNotificationManager().notify(1, getNotification("Downloading", progress)); } /** * 下载成功 */ @Override public void onSuccess() { Log.d(TAG, "onSuccess: -------------"); downloadFileTask = null; //下载成功时将前台服务通知关闭,并创建一个下载成功的通知 stopForeground(true); getNotificationManager().notify(1, getNotification("Download Success", -1)); Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show(); } /** * 下载失败 */ @Override public void onFailed() { Log.d(TAG, "onFailed: -------------"); downloadFileTask = null; // 下载失败时将前台服务通知关闭,并创建一个下载失败的通知 stopForeground(true); getNotificationManager().notify(1, getNotification("Download Failed", -1)); Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show(); } /** * 下载暂停 */ @Override public void onPaused() { Log.d(TAG, "onPaused: -------------"); downloadFileTask = null; Toast.makeText(DownloadService.this, "Download Paused", Toast.LENGTH_SHORT).show(); } /** * 下载取消 */ @Override public void onCanceled() { Log.d(TAG, "onCanceled: -------------"); downloadFileTask = null; stopForeground(true); Toast.makeText(DownloadService.this, "Download Canceled", Toast.LENGTH_SHORT).show(); } }; DownloadBinder mBinder = new DownloadBinder(); /** * 返回这个DownloadBinder 实例 * * @param intent * @return */ @Override public IBinder onBind(Intent intent) { return mBinder; } // 创建 DownloadBinder 实例 class DownloadBinder extends Binder { // 开始下载 public void startDownload(String url) { Log.d(TAG, "startDownload--------: 开始下载"); if (downloadFileTask == null) { downloadUrl = url; downloadFileTask = new DownloadFileTask(listener); downloadFileTask.execute(downloadUrl); startForeground(1, getNotification("DownLoading", 0)); Toast.makeText(DownloadService.this, "Downloading", Toast.LENGTH_SHORT).show(); } } // 暂停下载 public void pauseDownload() { Log.d(TAG, "pauseDownload--------: 暂停下载"); if (downloadFileTask != null) { downloadFileTask.pauseDownload(); } } // 取消下载 public void cancelDownload() { Log.d(TAG, "cancelDownload--------: 取消下载---" + downloadFileTask); if (downloadFileTask != null) { downloadFileTask.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(); } } } } /** * 获取通知栏管理器 * * @return */ public NotificationManager getNotificationManager() { return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } /** * 设置通知栏的样式 并获取通知栏的实例 * * @param title * @param progress * @return */ private Notification getNotification(String title, int progress) { Intent[] intents = new Intent[]{(new Intent(this, DownloadActivity.class))}; PendingIntent pi = PendingIntent.getActivities(this, 0, intents, 0); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); builder.setContentIntent(pi); builder.setContentTitle(title); if (progress > 0) { builder.setContentText(progress + "%"); builder.setProgress(100, progress, false); } return builder.build(); } }
四,接下来在活动中绑定这个服务就可以了。
1.先实现下界面,修改xml中的代码,创建三个按钮。启动暂停取消
<?xml version="1.0" encoding="utf-8"?>
2.在活动中绑定服务,让活动与服务进行通信,因为要对文件下载,还要动态的申请写文件的权限,代码如下:
public class DownloadActivity extends AppCompatActivity implements View.OnClickListener { // 开始下载按钮 private Button btnStart; // 暂停下载按钮 private Button btnPause; // 取消下载按钮 private Button btnCancel; // 服务 private Intent intent; // 下载操作的实例 private DownloadService.DownloadBinder downloadBinder; private ServiceConnection serviceConnection = new ServiceConnection() { /** * 活动与服务绑定成功后 * @param name * @param service */ @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder = (DownloadService.DownloadBinder) service; } /** * 活动与服务解绑后 * @param name */ @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_download); btnStart = (Button) findViewById(R.id.btnStart); btnPause = (Button) findViewById(R.id.btnPause); btnCancel = (Button) findViewById(R.id.btnCancel); btnStart.setOnClickListener(this); btnPause.setOnClickListener(this); btnCancel.setOnClickListener(this); // 启动服务并绑定 intent = new Intent(this, DownloadService.class); startService(intent); bindService(intent, serviceConnection, BIND_AUTO_CREATE); // 获取写的权限 if (ContextCompat.checkSelfPermission(DownloadActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(DownloadActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } } @Override public void onClick(View v) { if (downloadBinder == null) { return; } switch (v.getId()) { case R.id.btnStart: String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe"; downloadBinder.startDownload(url); break; case R.id.btnPause: downloadBinder.pauseDownload(); break; case R.id.btnCancel: downloadBinder.cancelDownload(); break; } } /** * 申请权限的返回结果 * * @param requestCode * @param permissions * @param grantResults */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, 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(serviceConnection); stopService(intent); }}
最后在AndroidManifest.xml中文件中声明使用的权限,还有别忘了服务也是需要声明的。
这个项目的总结就到这里就结束了......,最后附上源代码 源码下载地址
更多相关文章
- 安全篇 - 隐式配置 KeyStore 签名信息
- 一个关于android游戏下载静默安装功能的思路以及实现
- 在Android(安卓)studio里面使用AIDL
- 巧用apache httpcore-nio实现android推送服务
- android apk包反编译、破解心得
- 服务器端向Android客户端的推送
- Android(安卓)MVP设计架构:Model层数据传递到View层
- Android(安卓)程序读写Office文件
- android解析xml文件的方式之SAX方式