4、Service详解(二):本地Service

时间:2021-11-23 04:41:08
    本地Service同Activity一样,会存在于当主前线程中,所以我们不能把耗时的操作放在Service中执行,比如把网络请求处理的任务放到这里面的话,同Activity一样,可会会报ANR问题,这点我们在开发的时候需要注意,官方的API也给出了这样的提示。Service和Activity的最大区别就在于Service没有前台界面,尽管没有界面,他还是有自己的生命周期的。

     Service生命周期:

4、Service详解(二):本地Service


    如果应用程序通过startService()方法来启动Service,Service的生命周期如上图左边所示。
    如果应用程序通过bindService()方法来启动Service,Service的生命周期如上图右边所示。


(一)通过startService()方法启动Service
    首先我们先看一下上面生命周期中的几个方法:
    • void onCreate();当该Service第一次被创建后将立即回掉该方法。
    •      void onStartCommand(Intent intent,int flags,int startId);该方法的早期方法是onStart(Intent intent,int startId);每次通过startService(Intent intent)方法启动Service时都会回掉该方法。
    • void onDestroy();当该Service被关闭之前会回掉该方法。

    下面的类定义了一个Service组件,我们只是输出了一条语句:

public class ServiceWithStart extends Service {

@Override
public IBinder onBind(Intent intent) {
// 使用startService方式不会回调这个方法
return null;
}

@Override
public void onCreate() {
System.out.println("Service OnCreate");
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("Service OnStartCommand");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
System.out.println("Service OnDestroy");
super.onDestroy();
}

}


    所有的Service和Activity同样,需要在AndroidManifest.xml中配置,Activity和Service是平级的,同样配置在application标签内。
 <service android:name= ".ServiceWithStart" ></service>

    为了能够演示启动和停止Service,我们使用两个按钮,直接在activity_main.xml中插入2个按钮,分别用于启动Service和停止Service:

<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:background= "#fff"
android:gravity= "center"
android:orientation= "vertical" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:onClick="onBtnStartService"
android:text="使用StartService启动" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:onClick="onBtnStopService"
android:text="使用stop停止" />
</LinearLayout>

   界面如图所示

4、Service详解(二):本地Service


    在MainActivity中分别实现启动和停止

public class MainActivity extends Activity {

private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_main);
intent = new Intent(this , ServiceWithStart.class );
}
//第一个按钮的绑定事件
public void onBtnStartService(View v) {
startService( intent);
}
//第二个按钮的绑定事件
public void onBtnStopService(View v) {
stopService( intent);
}
}


所有的准备都完成了,下面我们开始测试:
1.进入应用,我们点击启动按钮,这时LogCat输出了两行信息,正如生命周期所示
         
Service OnCreate
          Service OnStartCommand
2.再次点击启动按钮,LogCat输出了一行信息,说明Service已经有一个实例启动,不会再执行OnCreate
          
 Service OnStartCommand
3.点击停止按钮,LogCat输出一行信息,Service已经停止。
          Service OnDestroy
4.再次点击启动按钮,Service再次启动
          
Service OnCreate
          Service OnStartCommand
5.直接点击返回键,退出应用,Logcat没有任何输出,Service仍在后台运行,这个问题一定要注意

6.再次进入应用,点击返回键,LogCat输出一行信息,退出应用没有停止的Service已经停止。
          Service OnDestroy




(二)通过bindService()方法启动Service
    首先我们先看一下生命周期中的几个方法:
        void onCreate()和 void onDestroy()就不说了,上面讲过了看下其他的
IBinder onBind(Intent intent):该方法是Service子类必须实现的方法,该方法返回一个IBinder对象,应用程序可通过该对象与Service组件通信。
boolean onUnbind(Intent intent):当该Service上绑定的所有客户端都断开连接时将会回调该方法。
     使用startService方式启动的Service与访问者之间基本上不存在关联,因此Service与访问者之间也无法进行通信和数据交换。如果需要访问者和Service进行方法调用和数据交换,则应该使用bindService方法启动和关闭。
     使用bindService方式,需要调用Context的bindService(Intent service,ServiceConnection conn,int flags)该方法有三个参数,三个参数都比较重要,下面依次介绍这三个参数:
service:通过Intent指定要启动的Service
conn:ServiceConnection对象用于访问者与Service连接情况,当连接成功则回调该对象的onServiceConneccted(ComponentName name,IBinder service)方法,当访问者与Service由于异常情况终止(unBindService断开连接时不回调)连接时会回调该对象的onServiceDisconnected(ComponentName name)方法。
flags:指定绑定时是否自动创建Service,0为不自动创建,BIND_AUTO_CREATE为自动创建。
     我们可以看到Service中的onBind方法会返回一个IBinder对象,于此同时,我们看到bindService中conn参数对应的对象中有个方法onServiceConneccted(ComponentName name,IBinder service),这里面有个参数IBinder service,其实这两个IBinder对戏那个是同一个,而组件与Service的通信也是通过IBinder方式来完成的。通常我们采用继承Binder的方式来实现自己的IBinder对象。
     下面我们还是通过例子来看一下bindService方式怎么使用:
     首先定义了一个Service组件,我们想要看看组件与Service之间的通信情况,所以我们先定义一个接口,方便数据访问:
public interface IBook {
//获取书的名字
String getNameById( int Id);
}

    来看一下我们的Service,这里要重点看我们自己定义的Binder对象,通信就靠它了:
public class ServiceWithBind extends Service {

private static String[] bookArr = { "Java", "设计模式" , "IOS开发" , "网络编程" };

// Service被创建时回调此方法
@Override
public void onCreate() {
super.onCreate();
System. out.println("Service onCreate" );
}

// 绑定Service时回调该方法
@Override
public IBinder onBind(Intent intent) {
System. out.println("Service onBind" );
return new BookBinder();
}

// Service被断开连接时回调此方法
@Override
public boolean onUnbind(Intent intent) {
System. out.println("Service onUnbind" );
return super .onUnbind(intent);
}

// Service被关闭之前回调此方法
@Override
public void onDestroy() {
System. out.println("Service onDestroy" );
super.onDestroy();
}

// 自定义的IBinder实现类
private class BookBinder extends Binder implements IBook {

@Override
public String getNameById(int Id) {
// TODO Auto-generated method stub
if (Id < 0 || Id >= bookArr.length) {
return "" ;
} else {
return bookArr [Id];
}
}
}
}

同样需要在AndroidMenifist中注册:

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

我们定义一个布局文件activity_bindservice.xml,放上三个按钮,1个绑定按钮,1个解绑定按钮,1个查看通信的按钮:
<?xml version= "1.0" encoding ="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:background= "#fff"
android:gravity= "center"
android:orientation= "vertical" >

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:onClick="onBtnBindService"
android:text="使用bindService启动" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:onClick="onBtnUnbindService"
android:text="使用unbindService停止" />


<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:onClick="onBtnBookName"
android:text="查看书的名字" />

</LinearLayout>

界面如图所示

4、Service详解(二):本地Service

在Activity中实现各种方法,注意看通信中重要的ServiceConnection 对象,在哪里可以拿到我们的访问接口

public class BindDomeActivity extends Activity {

private Intent intent;
private IBook myBook;

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_bindservice);
intent = new Intent(this , ServiceWithBind.class );
}

private ServiceConnection conn = new ServiceConnection() {

@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
myBook = (IBook) service;
}
};

//绑定Service的按钮点击事件
public void onBtnBindService(View v) {
bindService( intent, conn, Service.BIND_AUTO_CREATE );
}

//取消绑定Service的按钮点击事件
public void onBtnUnbindService(View v) {
unbindService( conn);
}

//查看Service中书籍
public void onBtnBookName(View v) {
String bookName = myBook.getNameById(2);
System. out.println(bookName);
Toast. makeText(this, "书名:" + bookName, Toast.LENGTH_SHORT).show();
}
}


所有的准备都完成了,下面我们开始测试:
1.进入应用,我们点击启动按钮,这时LogCat输出了两行信息,正如生命周期所示
          
Service onCreate
          Service onBind
2.点击停止按钮,LogCat输出两行信息,说明已经解除连接,并且停止了Service
         
 Service onUnbind
          Service onDestroy
3.再重复执行步骤1,这时候Service已经绑定,我们退出应用,会发现有错误提示,这是由于还没有解除绑定就退出组件了,所以会报错,我们开发过程中要注意解除绑定
4.再重复执行步骤1,点击查看书的名字按钮,会提示:书名:IOS开发,这说明我们成功的通信了。





(三)start和bind方式的区别与注意
1、通过startService()和stopService()启动关闭服务。适用于服务和访问者之间没有交互的情况。如果服务和访问者之间需要方法调用或者传递参数,则需要使用bindService()和unbindService()方法启动关闭服务。
2、采用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法,这个时候访问者和服务绑定在一起。
3、如果访问者要与服务进行通信,那么,onBind()方法必须返回Ibinder对象。如果访问者退出了,系统就会先调用服务的onUnbind()方法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法并不会导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果访问者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,调用该方法也会导致系统调用服务的onUnbind()—>onDestroy()方法。
4、Service的onCreate的方法只会被调用一次,就是你无论多少次的startService又 bindService,Service只被创建一次。如果先是bind了,那么start的时候就直接运行Service的onStart方法,如果先是start,那么bind的时候就直接运行onBind方法。如果你先bind上了,就stop不掉了,只能先UnbindService, 再StopService,所以是先start还是先bind行为是有区别的。