第一行代码——第十章:后台默默的劳动者——探究服务

目录:

10.1 服务是什么

10.2 Android 多线程编程

10.2.1 线程的基本用法

10.2.2 在子线程中更新UI

10.2.3 解析异步消息处理机制

10.2.4 使用AsyncTask

10.3 服务的基本用法

10.3.1 定义一个服务

10.3.2 启动和停止服务

10.3.3 活动和服务进行通信

10.4 服务的声明周期

10.5 服务的更多技巧

10.5.1 使用前台服务

10.5.2 使用IntentService

10.6 服务的最佳实战——完整版的下载实例

10.7 小结与点评


知识点:

10.1 服务是什么

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。
服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。
服务并不会自动开启线程,所有代码默认运行在主线程中。

10.2 Android 多线程编程

当我们执行一些耗时操作,如发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,若不将这类操作放在子线程中运行,会导致主线程被阻塞,从而影响软件的使用。下面就来学习下 Android 多线程编程。

10.2.1 线程的基本用法

Android 多线程编程并不比 Java 多线程编程特殊,基本都是使用相同的语法。

继承 Thread 类
新建一个类继承自 Thread,然后重写父类的 run() 方法:

    class MyThread extends Thread{
        @Override
        public void run() {
            // 处理具体的逻辑
        }
    }

    // 启动线程,run()方法中的代码就会在子线程中运行了
    new MyThread().run(); 

实现 Runnable 接口
新建一个类实现 Runnable 接口,启动再 new Thread():

   class MyThread2 implements Runnable{
        @Override
        public void run() {
            // 处理具体的逻辑
        }
    }

    // 启动线程
    MyThread2 myThread2 = new MyThread2();
    new Thread(myThread2).start();

当然也可用匿名类方式实现 Runnable 接口:

    // 匿名类方式实现
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 处理具体的逻辑
        }
    }).start();

 

10.2.2 在子线程中更新UI

Android 的 UI 是线程不安全的,若想要更新应用程序的 UI 元素,必须在主线程中进行,否则会出现异常。

这应该在之前的系列文章中提到过。

一是:使用Handler 

public class UpdateUITestActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn_change_text;
    private TextView tv_text;
    
    // 定义一个整型常量用于表示更新TextView这个动作
    public static final int UPDATE_TEXT = 1;
    
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case  UPDATE_TEXT:
                    // 在这里可以进行 UI 操作
                    tv_text.setText("你好世界");
                    break;
                
                 default:
                     break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_update_uitest);

        tv_text = (TextView) findViewById(R.id.tv_text);
        btn_change_text = (Button) findViewById(R.id.btn_change_text);
        btn_change_text.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
         switch (v.getId()){
             case R.id.btn_change_text:
                 new Thread(new Runnable() {
                     @Override
                     public void run() {
                         // 创建Message对象,并将它的what字段指定为UPDATE_TEXT
                         Message message = new Message();
                         message.what = UPDATE_TEXT;
                         handler.sendMessage(message);//将Message对象发送出去
                     }
                 }).start();
                 break;

             default:
                 break;
         }
    }
}

二是:使用 runOnUiThread

  new Thread(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText();
                    }
                });
            }
        }).start();

10.2.3 解析异步消息处理机制

Android 中的一部消息处理主要由4个部份组成:Message、Handler、MessageQueue和Looper。

Message:包含描述和任意数据对象的消息,用于发送给Handler。

Handler:主要是用于发送和处理消息的。发送消息一般用它的 sendMessage() 方法,发送的消息经过一系列地辗转处理后,最终传递到它的 handleMessage() 方法中。

MessageQueue:顾名思义,消息队列。内部存储着一组消息。对外提供了插入和删除的操作。MessageQueue内部是以单链表的数据结构来存储消息列表的。

Looper:主要用于给一个线程轮询消息的。线程默认没有Looper,在创建Handler对象前,我们需要为线程创建Looper。

使用Looper.prepare()方法创建Looper,使用Looper.loop()方法运行消息队列。

更多内容 请看 源码分析(面试常客):

https://blog.csdn.net/lfdfhl/article/details/53332936

10.2.4 使用AsyncTask

Andorid提供的工具,实现原理也是基于异步消息处理机制的。

用到时只需要 

new DownloadTask().execute();

3个泛型参数:

  • Params
    在执行 AsyncTask 时传入的参数,用于后台任务中使用

  • Progress
    后台任务执行时,若需在界面显示当前进度,则使用这里指定的泛型作为进度单位

  • Result
    当任务执行完毕后,若需对结果进行返回,则使用这里指定的泛型作为返回值类型

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

        //在后台任务执行前调用,用于一些界面上的初始化操作
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        //在子线程中运行,在这处理所有耗时操作
        //注意:不可进行 UI 操作,若需要可调用 publishProgress(Progress...)方法来完成
        @Override
        protected Boolean doInBackground(Void... voids) {
            while (true) {
                int downloadPercent=12;
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }


            return null;
        }

        //当后台任务中调用了 publishProgress(Progress...)方法后调用
        //返回的数据会作为参数传递到此方法中,可利用返回的数据进行一些 UI 操作
        @Override
        protected void onProgressUpdate(Integer... values) {
            Toast.makeText(OkHttpActivity.this, values[0]+"%", Toast.LENGTH_SHORT).show();


        }
        //当后台任务执行完毕并通过 return 语句进行返回时调用
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
        }
    }

10.3 服务的基本用法

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。

服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。

服务并不会自动开启线程,所有代码默认运行在主线程中。

10.3.1 定义一个服务

File -> New -> Service  -> Service 这样 将会在AndroidManifest.xml 中自动添加 Service

package com.dak.administrator.firstcode.service;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.dak.administrator.firstcode.R;

/**
 * Created by Administrator on 2018/11/1.
 */

public class MyService extends Service {
    private final String TAG = this.getClass().getName();

   /**
     * 在服务创建时调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("------MyService------", "onCreate: ");
    }

    /**
     * 在每次服务启动时调用
     * 若服务一旦启动就立刻执行某个动作,可以将逻辑写在此方法中
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("------MyService------", "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 在服务销毁时调用,回收不再使用的资源
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("------MyService------", "onDestroy: ");
    }

}

AndroidManifest.xml:

 <service
       android:name=".service.MyService"
       android:enabled="true" />

10.3.2 启动和停止服务

启动和停止服务的方法主要是借助 Intent 来实现的。下面就在项目中尝试去启动和停止服务。

在布局中添加两个按钮,分别用于启动和停止服务:

    <Button
        android:id="@+id/btn_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="StartButton" />

    <Button
        android:id="@+id/btn_stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="StopButton" />

Activity:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_service);

        Button btn_start= (Button) findViewById(R.id.start_service);
        Button btn_stop= (Button) findViewById(R.id.stop_service);
        btn_start.setOnClickListener(this);
        btn_stop.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_start:
                Intent startIntent = new Intent(this,MyService.class);
                startService(startIntent);// 启动服务
                break;
            case R.id.btn_stop:
                Intent stopIntent = new Intent(this,MyService.class);
                stopService(stopIntent);// 停止服务
                break;
            default:
                break;
        }
    }

注意:onCreate() 在服务第一次创建时调用,onStartCommand() 在每次启动服务时都会调用,上面第一次点击启动服务时两个方法都会执行,之后再点击启动服务按钮就只有 onStartCommant() 方法执行了。

10.3.3 活动和服务进行通信

借助OnBind() 方法,比如说,目前MyService中需要提供一个下载功能

 @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind(Intent intent)");

        return mBinder;
    }

    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder{
        void startDownload(){
            Log.e(TAG, "startDownload: executed");

        }

         int getProgress(){
            Log.e(TAG, "getProgress: executed");
            return 0;
        }
    }

然后修改我们的Activity:

package com.dak.administrator.firstcode.service;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.dak.administrator.firstcode.R;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class BackActivity extends AppCompatActivity {


    @BindView(R.id.btn_start)
    Button btnStart;
    @BindView(R.id.btn_stop)
    Button btnStop;
    @BindView(R.id.bind_service)
    Button bindService;
    @BindView(R.id.unbind_service)
    Button unbindService;
    @BindView(R.id.start_intentService)
    Button startIntentService;
    private MyService.DownloadBinder downloadBinder;

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

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_back);
        ButterKnife.bind(this);
    }


    @OnClick({R.id.btn_start, R.id.btn_stop,R.id.bind_service, R.id.unbind_service,R.id.start_intentService})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_start:
                startService(new Intent(BackActivity.this, MyService.class));
                break;
            case R.id.btn_stop:
                stopService(new Intent(BackActivity.this, MyService.class));
                break;
            case R.id.bind_service:
                bindService(new Intent(BackActivity.this, MyService.class),connection,BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                unbindService(connection);
                break;
            case R.id.start_intentService:
                startService(new Intent(BackActivity.this, MyIntentService.class));
                break;
        }
    }

}

10.4 服务的声明周期

具体如下图所示:

注意: 当我们对一个服务既调用了 startService() 方法,又调用了 bindService() 方法时,要同时调用 stopService() 和 unbindService() 方法,onDestroy() 方法才会执行。

10.5 服务的更多技巧

10.5.1 使用前台服务

服务几乎都是后台运行的,但是,当系统出现内存不足的情况是,就有可能会回收掉正在运行的后台服务,这时,就可以考虑使用前台服务。它和普通服务的区别就是,它会一直有一个正在运行的图标在系统的状态栏显示。

在MyServiece的onCreate中 创建:

  @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate()");

        Intent intent = new Intent(this,BackActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1, notification);

    }

10.5.2 使用IntentService

服务中的代码都是默认在主线程中的,如果直接在服务里面处理一些耗时的逻辑,很容易出现ANR,这时候就要用到多线程技术了。

public class MyService extends Service {
    . . .

   /**
     * 在每次服务启动时调用
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 处理具体的逻辑        
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
}

服务开启后会一直处于运行状态,必须调用 stopService() 或者 stopSelf() 才能停止服务,所以要实现一个服务在执行完毕后自动停止,可以这样写:

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 处理具体的逻辑        
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
 }

当然,为了可以简单地创建一个异步地、会自动停止地服务,Android 专门提供了一个 IntentService 类。

一个异步,会自动停止的服务。

package com.dak.administrator.firstcode.service;

import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;

import static android.content.ContentValues.TAG;

/**
 * Created by Administrator on 2018/11/2.
 */

public class MyIntentService extends IntentService {
    //异步,会自动停止的服务

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //打印当前线程id
        Log.e(TAG, "onHandleIntent: " + Thread.currentThread());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }


}

 

10.6 服务的最佳实战——完整版的下载实例

在这里我们要实现一个下载功能。

首先我们实现创建一个接口,用于对下载过程中的各种回调:

package com.dak.administrator.firstcode.service_best_practice;

/**
 * Created by Administrator on 2018/11/2.
 */

public interface DownloadListener {
    void progerss(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();
}

之后创建编写我们的下载功能

package com.dak.administrator.firstcode.service_best_practice;

import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;

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 Administrator on 2018/11/2.
 */

public class DownloadTask extends AsyncTask<String, Integer, Integer> {

    private static final int TYPE_SUCCESS = 0;
    private static final int TYPE_FAILED = 1;
    private static final int TYPE_PAUSED = 2;
    private static final int TYPE_CANCELED = 3;

    private DownloadListener listener;

    private boolean isCanceled = false;
    private boolean isPause = false;
    private int lastProgress;

    public DownloadTask(DownloadListener downloadListener) {
        listener = downloadListener;
    }

    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;

        try {
            long downloadLength = 0;//记录已经下载过文件的长度
            String downloadUrl = params[0];
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));//解析 url 文件名
            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();//sd卡 DownLoad 目录
            file = new File(directory + fileName);
            if (file.exists()) {
                //如果已经存在读取已下载的字节数
                downloadLength = file.length();
            }
            long contentLength = getContentLength(downloadUrl);//获取文件总长度
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadLength) {
                //已下载字节和文件总字节相等,说明已经下载完成了
                return TYPE_SUCCESS;
            }
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    //断点下载,指定从哪个字节开始下载
                    //告诉服务器 我想要从哪个字节开始下载
                    .addHeader("RANGE", "bytes=" + downloadLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();

            if (response != null) {
                is = response.body().byteStream();//使用文件流方式读取
                savedFile = new RandomAccessFile(file, "rw");
                savedFile.seek(downloadLength);//跳过已经下载的字节
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.read(b)) != -1) {
                    if (isCanceled) {
                        return TYPE_CANCELED;
                    } else if (isPause) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);

                        int progress = (int) ((total + downloadLength) * 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 (IOException e) {
                e.printStackTrace();
            }
        }


        return TYPE_FAILED;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        //和上次下载进度 比 有变化调用接口
        if (progress > lastProgress) {
            listener.progerss(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;
        }
    }

    void pauseDownload() {
        isPause = true;
    }

    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 conentLength = response.body().contentLength();
            response.body().close();
            return conentLength;
        }
        return 0;
    }


}

之后创建后台的服务

package com.dak.administrator.firstcode.service_best_practice;

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.v4.app.NotificationCompat;
import android.widget.Toast;

import com.dak.administrator.firstcode.R;

import java.io.File;

public class DownloadService extends Service {

    private DownloadTask downloadTask;

    private String downloadUrl;

    private DownloadListener listener = new DownloadListener() {
        @Override
        public void progerss(int progress) {
            getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

        @Override
        public void onSuccess() {
            downloadTask = null;
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Sucess", -1));
            Toast.makeText(DownloadService.this, "DownloadSucess", 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 onPaused", Toast.LENGTH_SHORT).show();
        }

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

    private Notification getNotification(String title, int progress) {
        Intent intent = new Intent(this, DownActivity.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();
    }

    private NotificationManager getNotificationManager(){
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }




    public DownloadService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return new DownloadBinder();
    }

    class DownloadBinder extends Binder{

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

        void pauseDownload(){
            if (downloadTask != null) {
                downloadTask.pauseDownload();
            }
        }

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

再然后创建 我们的页面,用于如何调用:

package com.dak.administrator.firstcode.service_best_practice;

import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
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.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.dak.administrator.firstcode.R;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class DownActivity extends AppCompatActivity {

    @BindView(R.id.start)
    Button start;
    @BindView(R.id.pause)
    Button pause;
    @BindView(R.id.cancel)
    Button cancel;

    private DownloadService.DownloadBinder mBinder;

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

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_down);
        ButterKnife.bind(this);

        Intent intent = new Intent(this, DownloadService.class);
        startService(intent);
        bindService(intent, connection, BIND_AUTO_CREATE);
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
                PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
    }

    @OnClick({R.id.start, R.id.pause, R.id.cancel})
    public void onClick(View view) {
        if (mBinder == null) {
            return;
        }

        switch (view.getId()) {
            case R.id.start:
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/" +
                        "master/eclipse-inst-win64.exe";
                mBinder.startDownload(url);
                break;
            case R.id.pause:
                mBinder.pauseDownload();
                break;
            case R.id.cancel:
                mBinder.cancelDownload();
                break;
        }
    }

    @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(connection);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start Button" />
    <Button
        android:id="@+id/pause"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="pause Button" />
    <Button
        android:id="@+id/cancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="cancel Button" />

</LinearLayout>

对于RandomAccessFile的了解:https://blog.csdn.net/akon_vm/article/details/7429245

10.7 小结与点评

郭霖总结:

在本章中,我们学习了很多与服务相关的重要知识点,包括Android多线程编程、服务的基本用法、服务的生命周期、前台服务和IntentService等。这些内容已经覆盖了大部分你在日常开发中可能用到的服务技术,再加上最佳实践部分学习的下载示例程序,相信以后不管遇到什么样的服务难题,你都能从容解决。

另外,本章同样是有里程碑式的纪念意义的,因为我们已经将Android中的四大组件全部学完,并且本书的内容也学习-大半了。 对于你来说,现在你已经脱离了Android初级开发者的身份,并应该具备了独立完成很多功能的能力。
那么后面我们应该再接再厉,争取进一步提升 自身的能力,所以现在还不是放松的时候,下一章中我们准备去学习一下 Android特色开发的相关内容。

我的总结:

服务这块内容是我一直没有搞明白的,但是看了这篇文章后,有了更多的认识。但是并不是很满足,因为这篇文章还是比较基础内容,或许在以后我会了解的更多更多,当然对于郭神还是要给一个大大的赞的~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值