Android学习笔记之Service应用

来源:互联网 发布:淘宝哪家店的檀香靠谱 编辑:程序博客网 时间:2024/06/07 19:23

Service(服务)是能够在后台执行长时间运行操作并且不提供用户界面的应用程序组件。其他应用程序组件能启动服务,并且即便用户切换到另一个应用程序,服务还可以在后台运行。此外,组件能够绑定到服务并与之交互,甚至执行进程间通信(IPC).

一、Service概述

1.Service的分类

服务从 本质上可以分为以下两种类型

①Started(启动):

当应用程序组件(如activity)通过调用startService()方法启动服务时,服务处于started状态。一旦启动,服务能在后台无限期运行,即使启动它的组件已经被销毁。通常,启动服务执行单个操作并且不会向调用者返回结果。例如,它可能通过网络下载或者上传文件。如果操作完成,服务需要停止自身。

②Bound(绑定):

当应用程序组件通过调用bindService()方法绑定到服务时,服务处于bound状态。绑定服务提供客户端-服务器接口,以允许组件与服务交互、发送请求、获得结果,甚至使用进程间通信(IPC)跨进程完成这些操作。仅当其他应用程序组件与之绑定时,绑定服务才运行。多个组件可以一次绑定到一个服务上,当它们都解绑定时,服务被销毁。


服务也可以同时属于这两种类型,既可以启动(无限期运行)也能绑定。其重点在于是否实现一些回调方法:onStartCommand()方法允许组件启动服务;onBind()方法允许组件绑定服务。


服务运行于管理它的进程的主线程,服务不会创建自己的线程,也不会运行于独立的进程(除非开发人员定义)。这意味着,如果服务要完成CPU密集工作或者阻塞操作,开发人员需要在服务中创建新线程来完成这些工作。通过使用独立的线程,能减少应用程序不响应(ANR)错误的风险,并且应用程序主线程仍然能用于用户与Activity的交互。


2.Service类中的重要方法

为了创建服务,开发人员需要创建Service类(或其子类)的子类。在实现类中,需要重写一些处理服务生命周期重要方面的回调方法,并根据需要提供组件绑定到服务的机制。需要重写的重要回调方法如下:

①onStartCommand()

当其他组件调用startService()方法请求服务启动时,系统调用该方法。一旦该方法执行,服务就启动并在后台无限期运行。如果开发人员实现该方法,则需要在任务完成时调用stopSelf()或stopService()方法停止服务(如果仅想提供绑定,则不必实现该方法)。

②onBind()

当其他组件调用bindService()方法想与服务绑定时(如执行RPC),系统调用该方法。在该方法的实现中,开发人员必须通过返回IBinder提供客户端用来与服务通信的接口。该方法必须实现,但是如果不想允许绑定,则返回null。

③onCreate()

当服务第一次创建时,系统调用该方法执行一次性建立过程(在系统调用onStartCommand()或onBind()方法前)。如果服务已经运行,该方法不被调用。

④onDestroy()

当服务不再使用并即将销毁时,系统调用该方法。服务应该实现该方法来清理诸如线程、注册监听器、接收者等资源。这是服务收到的最后调用。


如果组件调用startService()方法启动服务,服务需要使用stopSelf()方法停止自身,或者其他组件使用stopService()方法停止该服务。

如果组件调用bindService()方法创建,服务运行时间与组件绑定到服务的时间一样长。一旦服务从所有客户端解绑定,系统会将其销毁。

Android系统仅当内存不足并且必须回收系统资源来显示用户关注的activity时,才会强制停止服务。如果服务绑定到用户关注的activity,则会减少停止概率。如果服务被声明为前台运行,则基本不会停止。否则,如果服务是started状态并且长时间运行,则系统会随着时间推移降低其在后台任务列表中的位置并且有很大概率将其停止。如果服务是started状态,则必须设计系统重启服务。系统停止服务后,资源可用时会将其重启(但这也依赖于onStartCommand()方法的返回值)。


3.Service的声明

为了声明Service,需要向<application>标签中添加<service>子标签,<service>子标签的语法如下:

    <service

       android:enabled=["true"|"false"]

       android:exported=["true"|false"]

       android:icon="drawable resource"

       android:label="string resource"

       android:name="string"

       android:permission="string"

       android:process="string"

       ...

    >

    </service>

各个标签属性的说明如下:

①android:enabled

服务能否被系统实例化,true表示可以,false表示不可以,默认值是true。<application>标签也有自己的enabled属性,用于包括服务的全部应用程序组件。<application>和<service>的enabled属性必须同时设置成true(两者的默认值都是true)才能让服务可用。如果任何一个是false,服务被禁用并且不能实例化。

②android:exported

其他应用程序组件能否调用服务或者与其交互,true表示可以,false表示不可以。当该值是false时,只有同一个应用程序的组件或者具有相同用户ID的应用程序能启动或者绑定到服务。

默认值依赖于服务是否包含Intent过滤器。若没有过滤器,服务仅能能通过精确类名调用,这意味着服务仅用于应用程序内部(因为其他程序可能不知道类名)。此时,默认值是false;若存在至少一个过滤器,暗示服务可以用于外部,音质默认值是true。

该属性不是限制其他应用程序使用服务的唯一方式。还可以使用permission属性限制外部实体与服务交互。

③android:icon

表示服务的图标

④android:label

显示给用户的服务名称

⑤android:name

实现服务的Service子类名称,应该是一个完整的类名

⑥android:permission

实体必须包含的权限名称,以便启动或者绑定到服务。

如果startService()、bindService()或者stopService()方法调用者没有被授权,方法调用无效,并且Intent对象也不会发送给服务。

如果没有设置该属性,使用<application>标签的permission属性设置给服务。如果<application>和<service>标签的permission属性都未设置,服务不受权限保护。

⑦android:process

服务运行的进程名称。

通常,应用程序的全部组件运行于为应用程序创建的默认进程。进程名称与应用程序包名相同。

<application>标签的precess属性能为全部组件设置一个相同的默认值。但是组件能用自己的process属性重写默认值,从而允许应用程序跨越多个进程。

如果分配给该属性的名称以冒号(:)开头,仅属于应用程序的新进程会在需要时创建,服务能在该进程中运行;

如果进程名称以小写字母开头,服务会运行在以此为名的全局进程,但需要提供相应的权限。这允许不同应用程序组件共享进程,减少资源使用。


二、创建started Service 

Started Service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。

Android提供了两个类供开发人员继承以创建启动服务。

①service:

这是所有服务的基类。当继承该类时,创建新线程来执行服务的全部工作是非常重要的。因为服务默认使用应用程序的主线程,这可能降低应用程序activity的运行性能。

②IntentService:

这是service类的子类,它每次使用一个工作线程来处理全部启动请求。在不必同时处理多个请求时,这是最佳选择。开发人员仅需要实现onHandleIntent()方法,该方法接收每次启动请求的Intent以便完成后台任务。

IntentService可完成如下任务:

①创建区别于应用程序主线程的默认工作线程来执行发送到onStartCommand()方法的全部Intent。

②创建工作队列,每次传递一个Intent到onHandleIntent()方法实现,这样就不必担心多线程。

③所有启动请求处理完毕后停止服务,这样就不必调用stopself()方法。

④提供onBind()方法默认实现,其返回值是null。

⑤提供onStartCommand()方法默认实现,它先发送Intent到工作队列,然后到onHandleIntent()方法实现。

以上说明开发人员仅需实现onHandleIntent()方法来完成客户端提供的任务。由于IntentService类没有提供空参数的构造方法,一次需要提供一个构造方法。


如果需要让服务处理多线程,则可以继承Service类来处理各个Intent。由于开发人员自己处理onStartCommand()方法调用,可以同时处理多个请求。

onStartCommand()方法必须返回一个整数。该值用来描述系统停止服务后如何继续服务。onStartCommand()方法返回值必须是下列常量之一。

①START_NOT_STICKY

如果系统在onStartCommand()方法返回后停止服务,不重新创建服务,除非有PendingIntent要发送。为避免不在不需要的时候运行服务,这是最佳选择。

②START_STICKY

如果系统在onStartCommand()方法返回后停止服务,重新创建服务并调用onStartCommand()方法,但是不重新发送最后的Intent;相反,系统使用空Intent调用onStartCommand()方法,除非有PendingIntent来启动服务,此时,这些Intent会被发送。这是和多媒体播放器(或者类似服务),它们不执行命令但是无限期运行并等待工作。

③START_REDELIVER_INTENT

如果系统在onStartCommand()方法返回后停止服务,重新创建服务并使用发送给服务的最后Intent调用onStartCommand()方法,全部PendingIntent依次发送。这适合积极执行应该立即回复工作的服务,如下载文件。


启动服务startService()

停止服务stopSelf()或stopService()        stopSelf(int)


三、创建Bound Service

绑定服务是允许其他应用程序绑定并且与之交互的Service类实现类。为了提供绑定,开发人员必须实现onBind()回调方法。该方法返回IBinder对象,它定义了客户端用来与服务交互的程序接口。

客户端能通过bindService()方法绑定到服务。此时,客户端必须提供ServiceConnection接口的实现类,它监视客户端与服务之间的连接。bindService()方法立即返回,但是当Android系统创建客户端与服务之间的连接时,它调用ServiceConnection接口的onServiceConnected()方法,来发送客户端用来与服务通信的IBinder对象。

多个客户端能同时连接到服务。然而,仅当第一个客户端绑定时,系统调用服务的onBinder()方法来获取IBinder对象。系统接着发送同一个IBinder对象到其他绑定的客户端,但是不在调用onBind()方法。

当最后的客户端与服务解绑定时,系统销毁服务(除非服务也使用startService()方法启动)。

在实现绑定服务时,最重要的是定义onBind()回调方法返回的接口,有以下3种方式。

1.继承Binder类

如果服务对应用程序私有并且与客户端运行于相同的进程(这非常常见),则应该继承Binder类来创建接口,并且从onBind()方法返回其一个实例。客户端接收 Binder对象并用其来直接访问Binder实现类或者Service类中可用公共方法。

当服务仅用于私有应用程序时,推荐使用该技术。但当服务可以用于其他应用程序或者访问独立进程时,则不能使用该技术。

2.使用Messenger

如果需要接口跨进程工作,则可以使用Messenger来为服务创建接口。此时,服务定义Handler对象来相应不同类型的Message对象。Handler是Messenger的基础,能与客户端分享IBinder,允许客户端使用Message对象向服务发送命令。此外,客户端能定义自己的Messenger对象,这样服务能发送回消息。

使用Messenger是执行进程间通信(IPC)的最简单方式,因为Messenger类将所有请求队列化到单独的线程,这样开发人员就不必设计服务为线程安全。

3.使用AIDL

AIDL(Android接口定义语言)执行分解对象到原语的全部工作,以便操作系统能理解并且跨进程执行IPC。使用Messenger创建接口,实际上将AIDL作为底层架构。如上所述,Messenger在单个线程中将所有客户端请求队列化,这样服务每次收到一个请求。如果希望服务能同时处理多个请求,则可以直接使用AIDL。此时,服务必须能处理多线程并且要保证线程安全。

为了直接使用IDL,开发人员必须创建定义编程接口的.aidl文件。Android SDK工具使用该文件来生成抽象类,它实现接口并处理IPC,然后就可以在服务中使用了。


继承Binder类

其实现步骤如下:

1.在服务中,创建Binder类实例来完成下列操作之一:

①包含客户端能调用的公共方法

②返回当前Service实例,其中包含客户端能调用的公共方法。

③返回服务管理的其他类的实例,其中包含客户端能调用的公共方法。

2.从onBind()回调方法中返回Binder类实例。

3.在客户端,从onServiceConnected()回调方法接收Binder类实例,并且使用提供的方法调用绑定服务。

public class LocalService extends Service{

   private final IBinder binder = new LocalBinder();

   private final Random generator = new Random();

   public class LocalBinder extends Binder{

       LocalService getService(){

          return LocalService.this;

       } 

   }


   public IBinder onBind(Intent intent){

      return binder;

    }

    public int getRandomNumber(){

       return generator.nextInt(100);

    }

}


public class BindingActivity extends Activity{

   LocalService localService;

   boolean bound=false;

   

   protected void onCreate(Bundle savedInstanceState){

       super.onCreate(savedInstanceState);

       setContentView(R.layout.main);

   }

   protected void onStart(){

      super.onStart();

      Intent intent = new  Intent(this,LocalService.class);

      bindService(intent,connection,Context.BIND_AUTO_CREATE);

   }

   protected void onStop(){

      super.onStop();

      if(bound){

         unbindService(connection);

        bound=false;

      }

   }

   public void onButtonClick(View v){

      if(bound){

         int num=localService.getRandomNumber();

         Toast.makeText(this,"获得随机数:"+num,Toast.LENGTH_SHORT).show();

      }

   }

   private ServiceConnection connection=new ServiceConnection(){

       public void onServiceConnected(ComponentName className,IBinder service){

          LocalBinder binder = (LocalBinder)service;

          localService=binder.getService();

          bound=true;

       }

       public void onServiceDisconnected(ComponentName arg0){

          bound=false;

       }

   };

}

使用Messenger类

如果开发人员需要服务与远程进程通信,则可以使用Messenger来为服务提供接口。该技术允许不适用AIDL执行进程间通信(IPC).

使用Messenger时需注意:

①实现Handler的服务因为每次从客户端调用而收到回调。

②Handler用于创建Messenger对象(它是Handler的引用)

③Messenger创建IBinder,服务从onBind()方法将其返回到客户端。

④客户端使用IBinder来实例化Messenger,然后使用它来发送Message对象到服务。

⑤服务在其Handler的handleMessage()方法接收Message。

此时,没有供客户端在服务上调用的方法。相反,客户端发送消息到服务的Handler方法。


绑定到服务

应用程序组件(客户端)能调用bindService()方法绑定到服务,接下来Android系统调用服务的onBind()方法,返回IBinder来与服务通信。

绑定是异步的。bindService()方法立即返回并且不反悔IBinder到客户端。为了接收IBinder,客户端必须创建ServiceConnection实例,然后将其传递给bindService()方法。ServiceConnection包含系统调用发送IBinder的回调方法。

注意:只有Activity、Service和ContentProvider能绑定到服务,BroadcastReceiver不能绑定到服务。

如果需要从客户端绑定服务,需要完成以下操作:

①实现ServiceConnection,这需要重写onServiceConnected()和onServiceDisconnected()两个回调方法。

②调用bindService()方法,传递ServiceConnection实现。

③当系统调用onServiceConnected()回调方法时,就可以使用接口定义的方法调用服务。

④调用unbindService()方法解绑定。

当客户端销毁时,会将其从服务上解绑定。但是当与服务完成交互或者activity暂停时,最好解绑定,以便系统鞥你即使停止不用的服务。


四、管理Service的生命周期

1.started Service

2.bound Service


服务的更多技巧

服务几乎都是在后台运行的,一直以来它都是默默地做着辛苦地工作,但是服务地系统优先级还是比较低地,当系统出现内存不足地情况时,就有可能会回收掉正在后台运行地服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足原因导致被回收,就可以考虑使用前台服务。前台服务和普通服务地最大区别就在于,它会一直有一个正在运行地图标在系统地状态栏显示,下拉状态栏后可以看到更详细地信息,非常类似于通知地效果。当然有时候你也可能不仅仅时为了防止服务被回收才使用前台服务地,有些项目由于特殊地需求会要求必须使用前台服务,比如彩云天气这款天气预报应用,它地服务在后台更新天气数据地同时,还会在系统状态栏一直显示当前地天气信息。

那么我们就来看以下如何才能创建一个前台服务吧!

public class MyService extends Service{

    ...

    public void onCreate(){

        super.onCreate();;

        Log.d("MyService","onCreate executed");

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

    }

    ...

}


可以看到,这里只是修改了onCreate()方法中地代码,这次在构建出Notification对象后并没有使用NotificationManager来将通知显示出来,而是调用了startfForeground()方法,这个方法接收两个参数,第一个参数时通知的id,类似于notify()方法的第一个参数,第二个参数则是构建出Notification对象,调用startForeground()方法后就会让MyService变成一个前台服务,并在系统状态栏显示出来。


服务的最佳实践——完整版的下载示例

添加依赖库:

dependencies{

    compile fileTree(dir:'libs‘,include:['*.jar'])

    compile 'com.android.support:appcompat-v7:24.2.1'

    testCompile 'junit:junit:4.12'

    compile 'com.squareup.okhttp3:okhttp;3.4.1‘

}


接下来定义一个回调接口,用于对下载过程中的各种状态进行监听和回调,新建一个DownloadListener接口,代码如下:

public interface DownloadListener{

    void onProgress(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();

}

可以看到,这里我们一共定义了5个回调方法,onProgress()方法用于通知当前的下载进度,onSuccess()方法用于通知下载成功事件,onFailed()方法用于通知下载失败事件,onPaused()方法用于通知下载暂停事件,onCanceled()方法用于通知下载取消事件。

开始编写下载功能了,用AsyncTask来进行实现,新建一个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 DownloadListener listener;

    private boolean isCanceled=false;

    private boolean isPaused=false;

    private int lastProgress;

 

    public DownloadTask(DownloadListener listener){

       this.listener = listener;

    }

  

    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(resonse!=null){

                is=resonse.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{

                       tottal+=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;

        }

    

    protected void onProgressUpdate(Integer.. values){

        int progress=values[0];

        if(progress>lastProgress){

           listener.onProgress(progress);

           lastProgress=progress;

         }

     }


    protected void onPostExecute(Integer status){

        case TYPE_SUCCESS:

            listener.onSuccess();

            break;

        case TYPE_FAILED:

            listener.onFailed();

            break;

        case TYPE_PAUSED:

            listener.onPaused();

            break;

         case TYPE_CANCELED:

             listener.onCanceled();

         default:break;

    }


    public void pauseDownload(){

       isPaused=true;

    }


    public void cancelDownload(){

       isCanceld=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;

    }

}


创建下载服务

public class DownloadService extends Service{

    private DownloadTask downloadTask;

    private String downlaodUrl;


    private DownloadListener listener = new DownloadListener(){

        public void onProgress(int progress){

            getNotificationManager().notify(1,getNotification("Downloading..",progress));

        }


        public void onSuccess(){

            dwonloadTask=null;

            stopForeground(true);

            getNotificationManager.notify(1,getNotification("Download Success",-1));

            Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show();

        }


        public void onFailed(){

            downloadTsk=null;

            stopForeground(true);

            getNotificationManager().notify(1,getNotification("Download Failed",-1));

            Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();

        }


        public void onPaused(){

            downloadTask=null;Toast.makeText(DownloadService.this,"Paused",Toast.LENGTH_SHORT).show();

        }


        public void onCanceled(){

            downlaodTask=null;

             stopForeground(true);

            Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();

         }

    };


    private DownloadBinder mBinder = new DownloadBinder();

    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,"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=downlaodUrl..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();

              }

           }

        }

    }


    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 buidler = new NotificationCompat.Builder(this);

       builder.setSmallIcon(R.mipmap.ic_launcher);

       builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));

       builder.setContentIntent(pi);

       builder.setContentTile(title);

       if(progress>0){

           builder.setContentText(progress+"%");

           builder.setProgress(100,progress,false);//第一个参数传入通知的最大进度,第二个参数传入通知的当前进度,第三个参数表示是否使用模糊进度条,这里false显示进度

       }

       return builder.build();

    }


}


MainActivity代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{


    private DownloadService.DownloadBinder downloadBinder;

 

   private  ServiceConnection connection = new ServiceConnection(){

       public void onServiceDisconnected(ComponentName name){

       

       }

       public void onServiceConnected(ComponentName name,IBinder service){

           downloadBinder = (DownloadService.DownloadBinder)service;

       }

   };


    protected void onCreate(Bundle savedInstanceState){

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Button startDownload = (Button) findViewById(R.id.start_download);

        Button pauseDownlaod = (Button) findViewById(R.id.pause_download);

        Button cancelDownload = (Button)findViewById(R.id.cancel_download);

        startDownload.setOnClickListener(this);

        cancelDownload.setOnClickListener(this);

        pauseDownload.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);

        }

    }


    public void onClick(View v){

        if(downloadBinder==null{return;}

        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;

        }

    }


    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){

        switch(requestCode){

            case 1:

                      if(grantResults.length>0&&grantResults[0]!=PackageManager.PERMISSION_GRANTED){

                          Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENTH_SHORT).show();

                           finish();

                      }

                      break;

             default:

        }

    }


    protected void onDestroy(){

        super.onDestroy();

        unbindService(connection);

    }

}


在程序中我们分别调用了startService()和bindService()方法来启动和绑定服务。这一点至关重要,因为启动服务可以保证DownloadService一直在后台运行,绑定服务则可以让MainActivity和DownloadService进行通信。