Android四大组件之Service全面学习

来源:互联网 发布:中国科学院科学数据库 编辑:程序博客网 时间:2024/05/21 18:44

什么是服务

Android 的服务是 Android 四大组件之一,其他的三大组件是Activity、Broadcast Receiver、Content Provider。

它是程序运行在后台的解决方案。

服务依赖于应用程序进程,它创建它的进程被杀掉时,服务就会停止。

服务不会主动创建在子线程中,需要我们自己手动在服务内部创建子线程,否则主线程就有可能被阻塞住。

所以在学习服务之前,需要补充一点多线程编程的知识。

Android 多线程编程

线程的多种用法

Android 的多线程基本上跟 Java 是一致的。

创建一个类,继承自 Thread并重写父类的 run()方法:

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

调用这个线程:

new MyThread().start();

使用继承的方式耦合性较高,可以选择实现Runnable接口的方式来定义一个线程:

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

实现 Runnable 接口的方式定义一个线程,调用它跟之前的继承的调用有些区别:

MyThread myThread = new MyThread();new Thread(myThread).start();

如果连实现接口都觉得麻烦,可以使用匿名类的方式,这种方法更加普遍:

new Thread(new Runnable(){  @Override  public void run(){    // 处理具体的逻辑  }}).start();

在子线程中更新 UI

Android 的 UI 也是线程不安全的,也就是说它的 UI 元素必须在主线程中进行。

我们可以通过一个例子,来演示 UI 元素如何在子线程中更新就是不安全的,会报错的。

这个例子是这样的,有两个控件,一个按钮,一个 textView,点击按钮改变textView的文字,不过改变文字是在子线程中进行的。

首先在res/layout/activity_main.xml中添加按钮和textView:

  <Button        android:id="@+id/change_text"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Change Text"/>    <TextView        android:id="@+id/text"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:text="Hello world"        android:textSize="20sp" />

然后在主程序中开辟线程,在线程中更新UI

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private TextView text;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        text = (TextView)findViewById(R.id.text);        Button changeText = (Button)findViewById(R.id.change_text);        changeText.setOnClickListener(this);    }    @Override    public void onClick(View v){        switch (v.getId()){            case R.id.change_text:                new Thread(new Runnable() {                    @Override                    public void run() {                        text.setText("Nice to see you");                    }                }).start();                break;            default:                break;        }    }}

结果当然是,app崩溃。

但是我们确实需要在子线程中执行一些耗时任务,然后根据结果更新UI,应该怎么办?

Android提供了一套异步消息处理机制,完美地解决了在子线程中更新UI的问题。

我们将代码修改如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    public static final int UPDATA_TEXT = 1;        // 添加    private Handler handler = new Handler(){        // 添加        public void handleMessage(Message msg){     // 添加            switch(msg.what){                case UPDATA_TEXT:                    text.setText("Nice to meet you");                    break;                default:                    break;            }        }    };    private TextView text;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        text = (TextView)findViewById(R.id.text);        Button changeText = (Button)findViewById(R.id.change_text);        changeText.setOnClickListener(this);    }    @Override    public void onClick(View v){        switch (v.getId()){            case R.id.change_text:                new Thread(new Runnable() {                    @Override                    public void run() {                        Message message = new Message();    // 添加                        message.what = UPDATA_TEXT;         // 添加                        handler.sendMessage(message);       // 添加                    }                }).start();                break;            default:                break;        }    }}

解析异步消息处理机制

通过异步消息的处理,可以成功的在子线程中更新UI了(本质上是传递给Handler的handleMessage()在主线程中进行)。

Android的异步消息处理包括4个部分:Message、Handler、MessageQueue和Looper。

其中Message和Handler在之前已经接触过了。

接下来详细解析这几个部分:

  • Message:之前已经接触了它的what字段,它还可以使用arg1和arg2携带整型,使用obj携带Object对象。
  • Handler:它的使用方式是先在子线程中sendMessage(),最终传递到Handler的handleMessage()方法中。
  • MessageQueue:用于存放Handler发送的所有消息,这些消息放在队列中,等待被处理。每个线程中只有一个MessageQueue对象。
  • Looper:Looper就是MessageQueue的管家,调用Looper的loop()方法后,就会进行死循环中,然后发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中,每个线程中同样也只有一个Looper对象。

异步处理机制可以很好的解决子线程中更新UI的问题,Android将它封装成了一个更简单的接口:

runOnUiThread()

使用AsyncTask

为了方便在子线程中对UI进行操作,Android还提供了另外一些好用的工具,比如AsyncTask。

借助AsyncTask,即使你对异步消息处理机制不了解,也可以很简单的从子线程切换到主线程。

AsyncTask的背后也是基于异步消息处理机制的,只是Android帮我们做了很好的封装。

AsyncTask基本用法

AsyncTask是一个抽象类,如果要使用它就要创建一个子类去继承它。

在继承时,可以为AsyncTask指定3个泛型参数。这3个参数的用途如下:

  • Params。执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  • Progress。后台任务执行时,如果需要在界面上显示进行,就使用这个参数作为进度单位。
  • Result。任务执行完毕时,如果需要结果返回,则可以使用这里指定的泛型作为返回值类型。

一个最简单的AsyncTask可以写成如下形式:

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

这只是一个空任务,需要重写几个方法才能完成对方法的定制。

// 在任务开始前执行,主要是对界面的一些初始化操作onPreExecute() 
// 这个方法中的所有代码都会在子线程中运行,应该在这里处理耗时任务。// 如果AsyncTask的第三个参数是Void,可以不用返回。// 这个方法是不可以进行UI操作的。doInBackground(Params...)
// 在执行doInBackground(Params...)时,如果要更新UI元素,比如说任务进度,可以在这个方法中完成publishProgress(Progress...)
// 如果在后台任务中调用了publishProgress(Progress...),这个方法很快就会被调用。// 它的参数就是后台任务中传递过来的。// 在这个方法中可以进行UI操作。onProgressUpdate(Progress...)
// 当后台任务执行完毕后,这个方法很快就会被调用。// 返回的参数会作为参数传递到这个方法中。onPostExecute(Result)
// 如果要启动这个任务new Download().execute();

服务的基本用法

定义一个服务

右键包名/New/Service/Service,就可以新建一个继承于Services的类了。

新建页上面有个属性Exported,是指是否允许给其他应用使用该服务,Enabled是指是否开启这个服务。

下面就是标准的服务代码:

public class MyService extends Service {    public MyService() {    }    @Override    // 这个方法是Service中唯一的抽象方法,必须在子类中实现。    public IBinder onBind(Intent intent) {        // TODO: Return the communication channel to the service.        throw new UnsupportedOperationException("Not yet implemented");    }    @Override    // 只会在服务第一次启动时调用。    public void onCreate(){        super.onCreate();        Log.d("MyService","onCreate executed");    }    @Override    // 它会在服务每次启动时调用。    public int onStartCommand(Intent intent,int flags,int startId){        Log.d("MyService","onStartCommand executed");        return super.onStartCommand(intent,flags,startId);    }    @Override    public void onDestroy(){        super.onDestroy();        Log.d("MyService","onDestroy executed");    }}

每一个服务需要进行注册才能生效。

Android的四大组件都需要进行注册才能生效,不过Android Studio已经帮我们做好这一切了,只要我们是使用标准向导创建的四大组件。

打开AndroidManifest.xml,已经可以看到注册好了。

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

启动和停止服务

启动和停止服务主要是借助Intent来实现的。

我们创建两个按钮,一个是启动Service的,一个是停止Service的。

在res/layout/activity_main.xml中添加两个按钮。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <Button        android:id="@+id/start_service"        android:text="Start Service"        android:layout_width="match_parent"        android:layout_height="wrap_content" />    <Button        android:id="@+id/end_service"        android:text="End Service"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></LinearLayout>

然后写点击按钮的逻辑。

public class MainActivity extends AppCompatActivity implements View.OnClickListener{    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button startService = (Button)findViewById(R.id.start_service);        Button endService = (Button)findViewById(R.id.end_service);        startService.setOnClickListener(this);        endService.setOnClickListener(this);    }    @Override    public void onClick(View v){        switch (v.getId()){            case R.id.start_service:                Intent startIntent = new Intent(this,MyService.class);                // 启动服务                 startService(startIntent);                break;            case R.id.end_service:                Intent endIntent = new Intent(this,MyService.class);                // 停止服务                 // 在MyService类中的任何地方调用stopSelf()也可以停止服务                 stopService(endIntent);                break;            default:                break;        }    }}

活动和服务进行通信

前面服务开启以后就会一直运行,似乎跟活动没啥关系了。也不需要活动去控制什么,一切都是自动化的。

那么要在活动中指挥服务去干什么,应该怎么办呢?

还记得前面重写的onBind()抽象方法吗?

它就是干这个的!

onBind()使用的一个小例子

假如服务提供了一个下载的功能,在活动中我想随时开始,以及查看进度,这时候就可以在服务中创建一个专门的Binder对象来对下载功能进行管理。

public class MyService extends Service {    public MyService() {    }    private  DownloadBinder mBinder = new DownloadBinder();//添加    class DownloadBinder extends Binder{    // 添加        public  void startDownload(){            Log.d("MyService","startDownload executed");        }        public int getProgress(){            Log.d("MyService","getProgress executed");            return 0;        }    }    @Override    public IBinder onBind(Intent intent) {        return mBinder;     // 添加    }    @Override    public void onCreate(){        super.onCreate();    }    @Override    // 它会在服务每次启动时调用。    public int onStartCommand(Intent intent,int flags,int startId){        return super.onStartCommand(intent,flags,startId);    }    @Override    public void onDestroy(){        super.onDestroy();    }}

那么如何在活动中去调用服务里的这些方法呢?

首先要创建两个按钮用来绑定活动和服务

 <Button        android:id="@+id/bind_service"        android:text="Bind Service"        android:layout_width="match_parent"        android:layout_height="wrap_content" />    <Button        android:id="@+id/unbind_service"        android:text="UnBind Service"        android:layout_width="match_parent"        android:layout_height="wrap_content" />

在主窗口中点击绑定按钮,就会绑定服务和活动,点击解绑按钮,就会解绑服务和活动

public class MainActivity extends AppCompatActivity implements View.OnClickListener{    private MyService.DownloadBinder downloadBinder;    private ServiceConnection connection = new ServiceConnection(){        @Override        public void onServiceConnected(ComponentName componentName, IBinder service) {            downloadBinder = (MyService.DownloadBinder)service;            downloadBinder.startDownload();            downloadBinder.getProgress();        }        @Override        public void onServiceDisconnected(ComponentName componentName) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button bindService = (Button)findViewById(R.id.bind_service);        Button unbindService = (Button)findViewById(R.id.unbind_service);        bindService.setOnClickListener(this);        unbindService.setOnClickListener(this);    }    @Override    public void onClick(View v){        switch (v.getId()){            case R.id.bind_service:                Intent bindIntent = new Intent(this,MyService.class);                bindService(bindIntent,connection,BIND_AUTO_CREATE);                break;            case R.id.unbind_service:                unbindService(connection);                break;            default:                break;        }    }}

服务的生命周期

以onCreate()(如果不是第一次就是以onStartCommand())开始,以stopService()或stopSelf()结束。

一个服务只要被启动或者被绑定以后,就会一直运行,除非两者都停止或解绑,服务才会被销毁,才会调用onDestory()方法。

服务的更多技巧

使用前台服务

后台服务的优先级比较低,当内存不足的时候,有可能就被释放了。

前台服务就不会被任意释放掉。

前台服务会有一个图标显示在系统的状态栏上。

下拉通知栏还会有详细的信息。

public class MyService extends Service {    public MyService() {    }    @Override    public void onCreate(){        super.onCreate();        Intent intent = new Intent(this,MainActivity.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);    }    @Override    // 它会在服务每次启动时调用?    public int onStartCommand(Intent intent,int flags,int startId){        return super.onStartCommand(intent,flags,startId);    }    @Override    public void onDestroy(){        super.onDestroy();    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return null;    }}

使用IntentService

服务默认是在主线程中运行的,所以我们要在服务的每个具体方法中开启一个子线程。

为了创建一个异步的、会自动停止的服务,我们可能要写如下代码:

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

但是这样写的问题的是,如果忘了开辟子线程或是忘记stopSelf(),服务就会产生问题。

会了更简单的处理这样的事,Android提供了一个IntentService()。

新建一个服务继承自IntentService:

public class MyIntentService extends IntentService {    public MyIntentService() {        super("MyIntentService");    }    @Override    // 这个方法已经在子线程中运行了。    protected void onHandleIntent(Intent intent) {        Log.d("MyIntentService","Thread id is" + Thread.currentThread().getId());    }    @Override    // 当子线程中的代码执行完,就会调用它。    public void onDestroy(){        super.onDestroy();        Log.d("MyIntentService","onDestroy executed");    }}

为了证明它会自动在子线程中运行,我们在onHandleIntent()方法里打印了当前线程号。

为了证明它会自动销毁服务,我们没有使用停止服务按钮,而是在onDestroy()方法中打印了一下,证明它在服务运行结束后,会自动停止。

使用向导创建服务会自动生成注册服务的代码:

<service android:name=".MyIntentService" />

但是使用向导创建IntentService会生成一大堆不需要的代码,所以采用手动的方式,记住要注册服务才会生效。

服务的最佳实践

这一节会写一个基于服务的下载器,里面会用到之前学过的内容,由于代码较多,我做好了注释,一起贴出来。

这个小 demo 里会用到okhttp3,它是用于Java和 Android 的下载框架。

在app/build.gradle里面的dependencies添加:

dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    testCompile 'junit:junit:4.12'    compile 'com.android.support:appcompat-v7:24.2.1'    compile 'com.squareup.okhttp3:okhttp:3.4.1'}

res/layout/activity_main里面添加三个按钮,分别用于下载、暂停、取消。

<Button        android:id="@+id/start_download"        android:text="start download"        android:layout_width="match_parent"        android:layout_height="wrap_content" />    <Button        android:id="@+id/pause_download"        android:text="pause_download"        android:layout_width="match_parent"        android:layout_height="wrap_content" />    <Button        android:text="cancel download"        android:id="@+id/cancel_download"        android:layout_width="match_parent"        android:layout_height="wrap_content" />

MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    // Binder:用于在活动中操作服务的对象    private DownloadService.DownloadBinder downloadBinder;    // 通过binder连接服务和活动    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);        // 按钮响应        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 onRequestPermissionsResult(int requestCode,String[] permisson,int[] grantResult){        switch (requestCode){            case 1:                if (grantResult.length > 0 && grantResult[0] != PackageManager.PERMISSION_GRANTED){                    Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();                    finish();                }                break;            default:        }    }    @Override    public void onClick(View v){        if (downloadBinder == null) return;        switch (v.getId()){            case R.id.start_download:                // 调用服务进行下载                String url = "http://sw.bos.baidu.com/sw-search-sp/software/6c864ea1874b3/setup_bd.exe";                downloadBinder.startDownload(url);                break;            case R.id.pause_download:                // 调用服务暂停下载                downloadBinder.pauseDownload();                break;            case R.id.cancel_download:                // 调用服务取消下载                downloadBinder.cancelDownload();                break;            default:                break;        }    }    @Override    protected void onDestroy(){        super.onDestroy();        // 销毁时取消服务和活动的绑定        unbindService(connection);    }}

DownloadListener,接口,用于在服务中回调:

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

DownloadService 继承自Service:

public class DownloadService extends Service {    public DownloadService() {    }    // 实现回调接口    // downloadtask下载、暂停等状态会发送消息给服务\    // 这里的实现就是收到状态信息后的处理    private DownloadListener listener = new DownloadListener() {        @Override        // downloadtask正在下载,通知栏显示downloading        public void onProgress(int progress) {            getNotificationManager().notify(1,getNotification("Downloading...",progress));        }        @Override        // downloadtask下载完毕,关闭task,停止前台服务,通知栏显示download success等        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        // downloadtask下载失败,关闭task,停止前台服务,通知栏显示download failed等        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        // downloadtask下载暂停,关闭task等        public void onPaused() {            downloadTask = null;            Toast.makeText(DownloadService.this,"Paused",Toast.LENGTH_SHORT).show();        }        @Override        // downloadtask下载取消,关闭task、停止前台服务等        public void onCanceled() {            downloadTask = null;            stopForeground(true);            Toast.makeText(DownloadService.this,"Canceld",Toast.LENGTH_SHORT).show();        }    };    // 继承于AsyncTask,用于处理下载任务    private DownloadTask downloadTask;    private String downloadUrl;    // binder对象,用于连接活动和服务,用于在活动中操作服务    private DownloadBinder mBinder = new DownloadBinder();    class DownloadBinder extends Binder {        // 开始下载        public void startDownload(String url){            if (downloadTask == null){                downloadUrl = url;                // 传递监听对象,用于在task状态更新时通知回调                downloadTask = new DownloadTask(listener);                // task开始执行,并在task中的doInBackground函数的子线程中执行代码                downloadTask.execute(downloadUrl);                // 开启前台服务,并在通知栏显示downloading                startForeground(1,getNotification("Downloading...",0));                Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();            }        }        public void pauseDownload(){            if (downloadTask != null){                // 标记改为pause,使doInBackground中的下载暂停                downloadTask.pauseDownload();            }        }        public void cancelDownload(){            if (downloadTask != null){                // 标记改为cancel,使doInBackground中的下载取消                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,"Canceld",Toast.LENGTH_SHORT).show();                }            }        }    }    @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){            builder.setContentText(progress + "%");            builder.setProgress(100,progress,false);        }        return builder.build();    }

DownloadTask,继承于AsyncTask:

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 boolean isCanceled = false;    private boolean isPaused = false;    private int lastProgress;    private DownloadListener listener;    // 接收一个listener对象,listener会在服务中实现接口,用于回调    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);                // 每次读1024个字节,然后不断接到下载的文件上                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    // static来自于doInBackground的返回值    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;           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.close();            return contentLength;        }        return 0;    }}

给这个示例程序作一个总结:
活动与服务之间有一个连接者binder,用于在活动中控制服务要做的事情。
服务处理任务时交给AsyncTask的子类,这个子类会在子线程中异步的处理任务。服务与这个子类之间有若干个接口方法实现监听子类的状态。
监听到的状态实时的反应到通知栏里。
就是这么简单。~

参考:

《第一行代码 Android 第2版》.郭霖.人民邮电出版社.2016.12