使用Service进行后台下载

来源:互联网 发布:知乎 个性签名 编辑:程序博客网 时间:2024/05/16 12:51

今天学习service一章,现在总结其中知识点并通过实例进行分析:
一、在学习service之前首先讲解了关于多线程的知识,因为service在主线程中开启,但往往自身会开启线程进行UI等其他操作。
多线程调用方法不在多说,调用Runnable接口实现run方法即可。andriod中关于多线程的问题主要包括UI操作这部分,UI操作不可在子线程中进行,需要用特殊的处理方法。
1.使用Handler
大致使用方式:

首先声明handler实例并重写handleMessage方法进行message处理,这里的处理其实都在主线程中。

 private Handler handler=new Handler(){            @Override            public void handleMessage(Message msg) {                switch(msg.what)                {                    case 1:                        //这里进行UI操作...                }            }        };

然后在子线程中设置message并调用sendMessage方法将message传递过去。

Runnable runnable=new Runnable() {            @Override            public void run() {            ......                Message message=new Message();                message.what=1;                handler.sendMessage(message);            }        };

其实这种异步消息处理机制有四个部分:Message、Handler、MessageQueue、Looper。
具体机制如下:
异步消息传递机制
2.使用AsyncTask
也是基于异步消息处理机制,只不过是安卓封装的一个类。
基本方法:
onPreEexcute():初始化
doInBackground(Params…):处理耗时逻辑,比如文件读写。可以通过publishProgress(progress)方法传递进度。
onProgressUpdate(Progress…):传递的进度值会传入到这里,这里根据进度值变化做出相关操作。比如UI的更新。
onPostExecute():doInBackground方法返回时调用,进行收尾操作,包括UI处理,关闭前台服务等。
3.使用runOnUiThread
机制相同,不做过多说明。

二、 Service学习主要通过下载文件的实例来总结
如何实现给定下载地址,将文件下载到手机指定路径中:
Layout布局:三个按钮,分别执行开始下载,暂停,取消操作

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <Button        android:text="START"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:id="@+id/start_download"        />    <Button        android:text="PAUSE"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:id="@+id/pause_download" />    <Button        android:text="CANCEL"        android:id="@+id/cancel_download"        android:layout_width="match_parent"        android:layout_height="wrap_content"         /></LinearLayout>

MainActivity代码:定义downloadBinder和serviceConnection,创建活动和服务绑定的intent,当调用bindService绑定成功时,downloadBinder可从onServiceConnected方法中向下转型实例化。然后可通过单击按钮监听事件调用downloadBinder的方法进行下载的各种操作。
DownloadBinder类是在使用的自定义Service类里头定义的。

import android.Manifest;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.content.pm.PackageManager;import android.os.IBinder;import android.support.annotation.NonNull;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;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 serviceConnection=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);        Button pauseDownload= (Button) findViewById(R.id.pause_download);        Button cancelDownload= (Button) findViewById(R.id.cancel_download);        startDownload.setOnClickListener(this);        pauseDownload.setOnClickListener(this);        cancelDownload.setOnClickListener(this);        Intent intent=new Intent(this,DownloadService.class);        startService(intent);        bindService(intent,serviceConnection,BIND_AUTO_CREATE);//绑定服务和活动        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);        }//申请读写权限权限以保存下载文件    }    @Override    public void onClick(View v) {//通过downloadBinder的相关方法进行下载操作        switch (v.getId())        {            case R.id.start_download:                String url="https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";                downloadBinder.startDownload(url);                break;            case R.id.pause_download:                downloadBinder.pauseDownload();                break;            case R.id.cancel_download:                downloadBinder.cancelDownload();                break;            default:                break;        }    }    @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, "permission denied", Toast.LENGTH_SHORT).show();                    finish();                }                break;            default:                break;        }    }    @Override    protected void onDestroy() {//活动退出时取消绑定        super.onDestroy();        unbindService(serviceConnection);    }}

那么自定义Service做了什么事情?
基本就是两点:
1.定义downloadBinder内部类并在onBind中返回实例。
这个downloadBinder继承于Binder类,主要内容是调用downloadTask来处理下载的操作,为何要用downloadTask?因为downloadTask是继承于AsyncTask,可以异步处理UI操作和下载操作,并回调下载监听接口downloadListener的方法。downloadBinder只是负责阐明服务所做的事情,而downloadTask用于具体实现。
2.设置下载的监听接口downloadListener
downloadTask回调的接口downListener在这里定义,用于通知下载情况。

import android.annotation.SuppressLint;import android.app.Notification;import android.app.NotificationManager;import android.app.PendingIntent;import android.app.Service;import android.content.Intent;import android.graphics.BitmapFactory;import android.os.Binder;import android.os.Environment;import android.os.IBinder;import android.support.annotation.Nullable;import android.support.v4.app.NotificationCompat;import android.widget.Toast;import java.io.File;/** * Created by 邓苏桃 on 2017/7/9. */public class DownloadService extends Service {    private DownloadTask downloadTask;    private String downloadUrl;    private DownloadBinder mBinder=new DownloadBinder();    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("Download Success",-1));            Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();        }        @Override        public void onFailed() {            stopForeground(true);            getNotificationManager().notify(1,getNotification("Download Failed",-1));            Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();        }        @Override        public void onPause() {            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 Notification getNotification(String title, int progress) {//用于创建通知,显示下载进度和成功与否等信息,通知的创建基本就是以下步骤        Intent intent=new Intent(this,MainActivity.class);        PendingIntent pending=PendingIntent.getActivity(this,0,intent,0);        NotificationCompat.Builder builder=new NotificationCompat.Builder(this);        builder.setSmallIcon(R.mipmap.ic_launcher);        builder.setContentIntent(pending);        builder.setContentInfo(title);        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));        if (progress>0)        {            builder.setContentText(progress+"%");            builder.setProgress(100,progress,false);        }        return builder.build();    }    @SuppressLint("ServiceCast")    private NotificationManager getNotificationManager() {//获取系统NotificationManager        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    public class DownloadBinder extends Binder{//DownloadBinder定义,用于调用downloadTask执行下载操作并在通知栏显示进度和情况        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(fileName+directory);                    if (file.exists())                    {                        file.delete();                    }                    getNotificationManager().cancel(1);                    stopForeground(true);                    Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();                }            }        }    }}

接着就是DownloadTask的具体内容:
构造方法里引入监听接口downloadListener以便调用。
然后在doInBackground中实现下载的具体操作:首先设定下载的文件名和路径,文件名从函数参数中获得,这个参数其实是在上边调用downloadTask.execute(String)中传来的。然后判断文件大小以及是否下载完毕,来确定接下来的下载位置或者直接返回,通过Okhttp返回的数据进行下载,每次将数据存入本地前都会判断是否暂停或者取消,如果中途暂停或取消会直接返回相应结果。并将下载进度publish。
在onProgressUpdate以及onPostExecute中更新通知栏进度和Toast。

import android.os.AsyncTask;import android.os.Environment;import android.os.Handler;import android.os.Message;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 邓苏桃 on 2017/7/8. */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 is_canceled=false;    private boolean is_paused=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 okhttpclient=new OkHttpClient();            Request request=new Request.Builder()                    .addHeader("RANGE","bytes="+downloadedLength+"-")                    .url(downloadUrl)                    .build();            Response response=okhttpclient.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 (is_canceled){                        return TYPE_CANCELED;                    }                    else if (is_paused){                        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 (IOException e) {            e.printStackTrace();        }        finally {            try{                if (is!=null)                {                    is.close();                }                if (savedFile!=null)                {                    savedFile.close();                }                if(is_canceled&&file!=null)                {                    file.delete();                }            } catch (IOException 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 status) {        switch (status)        {            case TYPE_SUCCESS:                listener.onSuccess();                break;            case TYPE_FAILED:                listener.onFailed();                break;            case TYPE_PAUSED:                listener.onPause();                break;            case TYPE_CANCELED:                listener.onCanceled();                break;            default:                break;        }    }    public void pauseDownload(){        is_paused=true;    }    public void cancelDownload(){        is_canceled=true;    }    public 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.close();                return contentLength;            }        } catch (IOException e) {            e.printStackTrace();        }        return 0;    }}

DownloadListener接口:

public interface DownloadListener {    void onProgress(int progress);    void onSuccess();    void onFailed();    void onPause();    void onCanceled();}

以上是Service的基本实例,其次还有它生命周期的相关知识,简单来说,startService会调用onCreate(仅未创建服务是调用)和onStartConmand方法,stopService会调用onDestory方法,其他bindService也会牵扯到,目前碰到问题较少,暂不细说。
另外关于IntentService,它是Service的一个封装,会自己开启线程并在最后服务,以后用到再细聊。

原创粉丝点击