Android 服务下载示例
最近重读了下《第一行代码》,看到《第一行代码》 的一个小项目,特写本文梳理下流程。首先终结一句话,在android 的多线程处理中,尽量做到在子线程中进行耗时操作,在主线程中更新界面ui。好了,下面开始写这个项目。
一. 首先创建一个回调接口,用于对下载过程中的各种状态进行监听和回调,代码如下:
public interface downloadlistener { // 通知下载进度 void onprogress(int progress); // 通知下载成功 void onsuccess(); // 通知下载失败 void onfailed(); // 通知下载暂停 void onpaused(); //通知下载失败 void oncanceled(); }
ok,这里插一点题外话,回调函数。主要说一下回调函数的机制
(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 文件中的 dependencies 中添加okhttp 依赖
compile '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 中的代码,创建三个按钮。启动 暂停 取消
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文件中声明使用的权限,还有别忘了服务也是需要声明的。
这个项目的总结就到这里就结束了。。。
上一篇: 喝蜂蜜可以减肥吗,里面蕴含有小知识哦!
下一篇: 这是给我制造借口吗