这次的这个demo想要实现一个后台下载文件的功能,下载的时候会有一个告知进度的通知,

使用的依赖库就一个:

compile 'com.squareup.okhttp3:okhttp:3.9.0'

大体思路是创建一个AsyncTask运行在Service中,然后活动和Service进行通信,实现开始、暂停、取消下载的功能

所以先创建一个接口:

public interface DownloadListener {
//用于通知当前下载进度
void onProgress(int progress); //用于通知下载成功
void onSuccess(); //用于通知下载失败
void onFailed(); //用于通知下载暂停
void onPaused(); //用于通知下载取消
void onCanceled();
}

然后是下载这个行为的AsyncTask,AsyncTask是一个可以十分简单的从子线程切换到主线程的工具,AsyncTask是一个抽象类所以要创建一个类去继承它。

首先AsyncTask有三个泛型参数,第一个Params是传入的参数的类型,这里需要传入的是文件下载的地址,所以设定为String类型,第二个参数是Progress后台任务执行的时候,需要显示进度时使用这里的泛型作为进度单位,所以这里设为Integer,第三个参数是Result用于反馈执行结果,这里依旧使用Integer

还有经常需要重写的四个方法,onPreExecute()这个方法是在任务开始前进行的,它由UI线程(主线程)调用,即可以进行UI操作,这里我想要在Service中执行这个下载的任务就不需要这个方法了就略过,然后是最重要的doInBackground(Params...),这个方法是onPreExecute()完成后,立即在后台进行的,用以执行任务,并将Result传给onPostExecute(Result)。另外,在此期间,可以调用publishProgress(Progress...),这个方法能够传递一些数据给onProgressUpdate(Progress...),所以这里就需要执行下载,保存,反馈进度所有的下载逻辑,再然后就是onProgressUpdate(Progress...)这里可以使用来自doInBackground的数据,然后UI的操作,这里就用来修改通知的进度条就行了,最后就是onPostExecute(Result)根据来自doInBackground的结果用于通知最后的结果了。

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;
} @Override
protected Integer doInBackground(String... params) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try{
long downloadedLength = 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()){
downloadedLength = file.length();
}
long contentLength = getContentLength(downloadUrl);
if(contentLength == 0){
return TYPE_FAILED;
}else if(contentLength == downloadedLength){
//已下载字节与总文件字节相等 说明已经下载完成
return TYPE_SUCCESS;
}
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();
savedFile = new RandomAccessFile(file, "rw");
savedFile.seek(downloadedLength);//跳过已经下载好的字节
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 + downloadedLength) * 100 / contentLength);
publishProgress(progress);
}
}
response.body().close();
return TYPE_SUCCESS;
}
}catch (Exception 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;
} @Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if(progress > lastProgress){
listener.onProgress(progress);
lastProgress = progress;
}
} @Override
protected void onPostExecute(Integer 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;
default:
break;
}
} public void pauseDownload(){
isPaused = true;
} public void cancelDownload(){
isCanceled = true;
} private long getContentLength(String downloadUrl) throws IOException{
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.body().close();
return contentLength;
}
return 0;
}
}

具体其中下载时候 保存、断点下载、中断的逻辑下次再说吧==。

然后是Service,Service的使用同样需要创建一个类来继承Service,这次的主题是Service,其实应该写点小demo探究生命周期什么的==,算了以后弄把,这次改标题就好了。然后这次是想要使用服务执行一个任务,然后让活动绑定这个服务,然后使用服务中的提供的接口实现开始、暂停、取消的功能,

绑定服务是 Service 类的实现,可让其他应用与其绑定和交互。要提供服务绑定,您必须实现 onBind() 回调方法。该方法返回的 IBinder 对象定义了客户端用来与服务进行交互的编程接口。
客户端可通过调用 bindService() 绑定到服务。调用时,它必须提供 ServiceConnection 的实现,后者会监控与服务的连接。bindService() 方法会立即无值返回,
但当 Android 系统创建客户端与服务之间的连接时,会对 ServiceConnection 调用 onServiceConnected(),向客户端传递用来与服务通信的 IBinder。

这是google文档中的描述,意思就是这个子类中要重写的方法只有onBind(),这个方法会返回一个IBinder对象,意思就是创建一个内部类继承自Binder,这个类里面的方法就需要实现开始、暂停、取消。然后使用这个类创建一个名为的IBinder对象在onBind()中返回就好了

public class DownloadService extends Service {

    private  DownloadTask downloadTask;

    private  String downloadUrl;

    private DownloadListener listener = new DownloadListener() {
@Override
public void onProgress(int progress) {
getNotificationManager().notify(1, getNotification("Downloading...", progress));
} @Override
public void onSuccess() {
downloadTask =null;
//下载成功后将前台服务通知关闭,并创建一个下载成功的通知
stopForeground(true);
getNotificationManager().notify(1, getNotification("下载中", -1));
Toast.makeText(DownloadService.this, "下载成功", Toast.LENGTH_SHORT).show();
} @Override
public void onFailed() {
downloadTask =null;
//下载失败后将前台服务通知关闭,并创建一个下载成功的通知
stopForeground(true);
getNotificationManager().notify(1, getNotification("下载失败", -1));
Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_SHORT).show();
} @Override
public void onPaused() {
downloadTask = null;
Toast.makeText(DownloadService.this, "下载暂停", Toast.LENGTH_SHORT).show();
} @Override
public void onCanceled() {
downloadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this, "下载取消", Toast.LENGTH_SHORT).show();
}
}; private DownloadBinder mBinder = new DownloadBinder(); @Override
public IBinder onBind(Intent intent) {
return mBinder;
} class DownloadBinder extends Binder{ 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, "下载中", 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, "取消", Toast.LENGTH_SHORT).show();
}
}
}
} private NotificationManager getNotificationManager(){
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
} private Notification getNotification(String title, int progress){
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 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){
//当进度大于0或者等于0的时候才显示下载进度
builder.setContentText(progress + "%");
builder.setProgress(100, progress , false);
}
return builder.build();
}
}

然后就是活动了,活动中要实现的就是控制服务中的任务,

        Intent intent = new Intent(this, DownloadService.class);
startService(intent);//启动服务
bindService(intent, connection, BIND_AUTO_CREATE);//绑定服务

首先是启动和绑定这个服务。其中的connection是一个需要重写的匿名类

    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (DownloadService.DownloadBinder) iBinder;
} @Override
public void onServiceDisconnected(ComponentName componentName) { }
};

downloadBinder就是在服务中创建的类,这里的绑定只需要把服务返回的iBinder实例化即可。

整体的代码是这样:

public class MainActivity extends AppCompatActivity {

    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (DownloadService.DownloadBinder) iBinder;
} @Override
public void onServiceDisconnected(ComponentName componentName) { }
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startDownload = (Button) findViewById(R.id.start_download);
Button pauseDownload = (Button) findViewById(R.id.pause_download);
Button cancelDownload = (Button) findViewById(R.id.cancel_download); Intent intent = new Intent(this, DownloadService.class);
startService(intent);//启动服务
bindService(intent, connection, BIND_AUTO_CREATE);//绑定服务
if(ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
} startDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
downloadBinder.startDownload(url);
}
}); pauseDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
downloadBinder.pauseDownload();
}
}); cancelDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
downloadBinder.cancelDownload();
}
});
} @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED){
Toast.makeText(this, "拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
} @Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}

然后还有注意权限的申请

<manifest
...
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
</manifest>

android ——后台下载的更多相关文章

  1. 【Android初级】如何实现一个“模拟后台下载”的加载效果(附源码)

    在Android里面,后台的任务下载功能是非常常用的,比如在APP Store里面下载应用,下载应用时,需要跟用户进行交互,告诉用户当前正在下载以及下载完成等. 今天我将通过使用Android的原生控 ...

  2. Android后台保活实践总结:即时通讯应用无法根治的“顽疾”

    前言 Android进程和Service的保活,是困扰Android开发人员的一大顽疾.因涉及到省电和内存管理策略,各厂商基于自家的理解,在自已ROOM发布于都对标准Android发行版作为或多或少的 ...

  3. Android异步下载图片并且缓存图片到本地

    Android异步下载图片并且缓存图片到本地 在Android开发中我们经常有这样的需求,从服务器上下载xml或者JSON类型的数据,其中包括一些图片资源,本demo模拟了这个需求,从网络上加载XML ...

  4. Android检查更新下载安装

    检查更新是任何app都会用到功能,任何一个app都不可能第一个版本就能把所有的需求都能实现,通过不断的挖掘需求迭代才能使app变的越来越好.检查更新自动下载安装分以下几个步骤: 请求服务器判断是否有最 ...

  5. Android后台执行的定时器实现

    Android后台运行定时器,方便我们运行定位跟踪等任务需求. 以下简要说明实现Android后台定时器的要点, 文章末尾能够下载到project代码,可直接编译运行. AndroidManifest ...

  6. iOS和Android后台机制对比

    转自:http://blog.csdn.net/zsch591488385/article/details/27232881 一.iOS的“伪后台”程序 首先,先了解一下ios 中所谓的「后台进程」到 ...

  7. iOS12 中的后台下载与上传

    严格意义上来说,iOS并不能像Android一样,真的在后台开启一个下载Service,一直下载.但是它可以进行在系统允许范围内的后台上传和下载. 当使用 NSURLSessionConfigurat ...

  8. android sdk下载

    android sdk下载 所有的离线包都有 http://mirrors.neusoft.edu.cn/android/repository/

  9. Android Studio下载及使用教程(转载)

    (一)下载及相关问题解决: Android Studio 下载地址,目前最新可下载地址,尽量使用下载工具. Android Studio正式发布,给Android开发者带来了不小的惊喜.但是下载地址却 ...

随机推荐

  1. Jpa 笔记

    ORM 思想 对象关系映射, 建立实体类和表的关系映射关系, 实体类和表中字段的映射关系,我们操作实体类底层是操作数据表, 进而自动的拼接出SQL语句 Jpa规范 Jpa(Java Persisten ...

  2. 学Redis这篇就够了

    Redis 简介 Redis 优势 Redis 数据类型 string hash list set Zset 小总结 基本命令 发布订阅 简介 实例 发布订阅常用命令 事务 实例 Redis 事务命令 ...

  3. html解析器:Html Agility Pack

    去掉注释.样式.和js代码: foreach(var script in doc.DocumentNode.Descendants("script").ToArray()) scr ...

  4. Jenkins部署Web项目到远程tomcat

    1.填加插件Deploy to container Plugin. 2.在构建任务中填加构建后操作.并做如下配置: WAR/EAR files:是war包的相对路径,如target/xxx.war c ...

  5. 异常:带有 CLSID {} 的 COM 对象无效或未注册

    今天处理调试打印程序的时候,看到这个异常: 代码: try { string strApplyEmpno=""; string strApplyDeptCode="&qu ...

  6. spring全局异常抓取validation校验信息

    @ControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler({Exception.class}) @Respons ...

  7. UTF-16 -- 顶级程序员也会忽略的系统编码问题,JDK 错了十年!

    Unicode(统一码.万国码.单一码)是计算机科学领域里的一项业界标准,包括字符集.编码方案等.Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一 ...

  8. sonarlint在idea&eclipse中安装与使用

    介绍 ​ 代码质量管理的开源平台,用于管理源代码的质量 通过插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管 ...

  9. 转 java - 如何判断单链表有环

    转自 https://blog.csdn.net/u010983881/article/details/78896293 1.穷举遍历 首先从头节点开始,依次遍历单链表的每一个节点.每遍历到一个新节点 ...

  10. Android Studio电脑不支持HAXM的解决办法

    Intel HAXM is required to run this AVD. Your CPU does not support required features (VT-x or SVM). U ...