Android入门——你的第一个Service实例(实现后台下载)

Android入门——后台下载的Service实例

本文代码以及相关注释均来自于《第一行代码——Android》郭霖著,对书中365-378页的【完整版的下载实例】代码进行逻辑梳理以及对初学者的相关概念拓展。

希望在我自我巩固的同时,也能对你有所帮助,加深对Service的理解。

相信你能通过本文掌握Service的基本实现。

目录

Android入门——后台下载的Service实例

1 准备步骤

1.1 添加依赖:

1.2 声明权限:

1.3修改布局文件

2 新建回调接口:DownloadListener

2.1 作用

2.2 代码解析

2.3 用法

2.4 代码实例

3 新建AsyncTask:DownloadTask

3.1 作用

3.2 代码解析

3.2.1 参数

3.2.2 构造方法

3.2.3 重写

3.3 用法

3.4 代码实例

4 新建服务:DownloadService。

4.1 作用

4.2 代码解析

4.2.1 参数

4.2.2 类

4.2.3 方法

4.3 用法

4.4代码实例

5 修改MainActivity

5.1 作用

5.2 代码解析

5.2.1 参数

5.2.2 方法

5.3 用法

5.4 代码实例


1 准备步骤

1.1 添加依赖:

'com.squareup.okhttp3:okhttp:3.4.1'

1.2 声明权限:


    

1.3修改布局文件




2 新建回调接口:DownloadListener

2.1 作用

用于实现AsyncTask和Service之间的通信

2.2 代码解析

定义五个方法,分别对应:正在加载、成功、失败、暂停、取消、和错误。

其中,正在加载方法要求传入一个int类型的表示当前进度的参数。

2.3 用法

在DownloadTask中调用接口中的方法,返回当前状态,

在DownloadService中实现相应的方法,进行UI操作。

2.4 代码实例

public interface DownloadListener {
    void onProgress(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();

    void onError();
}

3 新建AsyncTask:DownloadTask

3.1 作用

用于耗时网络操作的子线程。

3.2 代码解析

3.2.1 参数

a.定义五个int类型常量,用于在doInBackground方法中,返回当前状态,传递给onPostExecute方法。

b.定义一个成员变量DownloadListener,用于在doInBackground方法中回调当前状态。

c.定义两个boolean类型参数,和int型参数,分别用于表示当前状态,和当前下载进度。

3.2.2 构造方法

DownTask类的构造方法,要求传入一个Download类型参数,将DownloadTask中的下载状态通过这个参数进行回调。

3.2.3 重写

a.泛型参数

DownloadTask的父类AsyncTask有三个泛型参数,

params:参数,本例中将params的类型设为String,用于表示资源下载地址。

通过Download.execute()方法传入,在doInBackground方法中作为参数调用。

progress:进度,本例中将progress的类型设为Integer,用于表示当前异步任务完成的进度。

通常由doInBackground方法中的publishProgress(progress)方法传递出,由onProgressUpdate方法作为参数调用。

result:结果,本例中将result的类型设为Integer,用于表示当前异步任务的状态。

通常为doInBackground方法的返回值,由onPostExecute方法作为参数调用。

b.重写方法

DownloadTask的父类AsyncTask通常有几个需要重写的方法

在doInBackground()方法中,编写后台异步任务具体执行的操作。

在onProgressUpdate()方法中,编写进度更新的操作。

在onPostExecute()方法中,编写异步任务结束后的操作。(结束包含成功结束以及暂停取消失败等。总之通过判断doInBackground()方法的返回值,执行相应的操作)

c.定义方法

定义了一些新的方法包括pauseDownload(),cancelDownload(),getContentLength().分别是暂停下载,取消下载,和获取下载目标文件大小。具体原理和方法参见代码。

3.3 用法

作为DownloadService的成员变量。

3.4 代码实例

public class DownloadTask 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;
    public static final int TYPE_ERROR = 4;

    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_ERROR;
        }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 (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;
    }

    @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.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            case TYPE_ERROR:
                listener.onError();
                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;
    }

}

4 新建服务:DownloadService。

4.1 作用

作为程序后台执行的解决方案,执行那些不需要和用户交互而且还要求长期运行的服务。

需要注意的几点:

1.服务并不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。

2.不要被服务的后台概念所迷惑,实际上服务并不会自动开启进程,所有的代码都是默认运行在主线程中的。也就是说我们需要在服务的内部手动创建子线程(本例使用的是AsyncTask模式)

4.2 代码解析

4.2.1 参数

a.定义一个DownloadTask,在需要下载时实例化,在下载结束或手动取消时清空。

b.定义并实例化一个DownloadListener接口,并实现接口中的方法。(主要是执行一些UI操作和将downloadTask清空)

c.定义一个DownloadBinder类的实例mBinder,并初始化。

4.2.2 类

创建一个DownloadBinder类继承自Binder。作为Activity和Service之间的通讯工具。在该类中定义开始下载、暂停下载和取消下载的方法。用于在Activity中执行相应操作。

4.2.3 方法

重写onBind方法,返回mBinder。

定义一个getNoftificationManager()方法,用于获取系统通知管理类

定义一个getNotification(String title,int progress)方法,用于设置通知属性以及进度。

4.3 用法

1.在Activity中启动并绑定服务,在Activity被销毁时进行解绑。

2.在Activity中监听到下载相关的按钮操作,执行downloadBinder的对应方法(需要在Activity中定义一个DownloadBinder类)。

4.4代码实例

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("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();

        }

        @Override
        public void onError() {
            downloadTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this,"Download Error",Toast.LENGTH_SHORT).show();
        }
    };


    public DownloadService() {
    }

    private DownloadBinder mBinder = new DownloadBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    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){
            //当progress大于或者等于0时才需显示下载进度
            builder.setContentText(progress+"%");
            builder.setProgress(100,progress,false);
        }
        return builder.build();
    }

    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,"Download ...",Toast.LENGTH_SHORT).show();
        }
        public void pauseDownload(){
            if(downloadTask != null){
                downloadTask.pauseDownload();
            }
        }

        public void cancelDownload(){
            if (downloadTask !=null){
                downloadTask.cancelDownload();
            }
            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();
            }
        }
    }




}

5 修改MainActivity

5.1 作用

程序运行的主界面,在这里执行UI操作,以及Service的开启和绑定。

5.2 代码解析

5.2.1 参数

a.定义一个DownloadBinder实例,用于实现Service和Activity之间的通信。

b.定义一个ServiceConnection实例,用于实现Service和Activity之间的连接。并在onServiceConnected方法中,进行downloadBinder的实例化操作。

5.2.2 方法

a.重写onCreate()方法,并在其中进行

1).UI控件的初始化操作以及按钮监听绑定;

2).DownloadService的启动及绑定;

3).对写外部存储器操作的运行时权限申请。

b.重写onClick()方法,实现按钮监听操作,调用downloadBinder的相关方法。

c.重写onRequestPermissionResult()方法,当用户拒绝权限申请时弹出Toast。

d.重写onDestroy()方法,在该方法中进行服务解绑,否则会造成内存泄露。

5.3 用法

在Manifest.xml中注册,并编写对应的布局文件

5.4 代码实例

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 = findViewById(R.id.start_download);
        Button pauseDownload = findViewById(R.id.pause_download);
        Button cancelDownload = 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,connection,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) {
        if (downloadBinder == null){
            return;
        }
        switch (v.getId()){
            case R.id.start_download:
                String url="https://www.imooc.com/mobile/mukewang.apk";
                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,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
                default:
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

致谢:@guolin https://blog.csdn.net/guolin_blog

如对本文有任何疑问,可以在评论区提问。或致信[email protected] 我会倾尽解答。

你可能感兴趣的:(android)