安卓基础知识(一) 服务(Service)

一.基本概念:

1.服务就是长期于后台运行的程序,可以理解为是一个,用于执行长期任务,并且与用户没有交互的组件。每一个服务需要在配置文件AndroidManifest.xml文件里面进行生命。使用的标签之后,和活动以及广播接收者的生命类似。

2.我们为什么要使用服务?
做一些需要长期执行的后台工作。

3.进程的优先级:
前台进程 > 可见进程 > 服务进程 > 后台进程 > 空进程

二.在活动中调用服务的方法

在主活动中调用服务内部的方法

错误实例

思路是,先实例化一个服务的对象。再通过这个实例化的对象去调用服务中的方法。但是这样操作会出现空指针的异常。
MainActivity: 
public void callServiceMethod(View view){
        Log.d(TAG, "callServiceMethod: ");
        FirstService firstService=new FirstService();
        firstService.sayhello();
    }
FirstService:
 public void sayhello(){
     Toast.makeText(this,"hello",Toast.LENGTH_SHORT).show();
    }
/*service继承自ContextWrapper,ContextWrapper继承自Context,就是说service就是继承了context。
报错会说context为空,因为context是由框架给我们的,由系统去调用起来,然后给它一个上下文(就是全局的一个环境的一个对象)。我们这样写相当于自己去创建和调用,应该让系统去创建和调用。

直接在主方法中实例化Service再调用服务中的方法,会在造成空指针的异常报错
*/

在最开始实现服务时会强制我们实现onBind()方法也就是绑定方法,就是借助这个方法实现服务内部方法的调用。

在这里要先明确一下服务的两种启动方法:

1.bind服务

2.start服务

用第二种方法时startService开始服务,stopService终止服务。下面示例是用bind服务的方式启动服务。

@Override
    public IBinder onBind(Intent intent){
        Log.d(TAG, "onBind: 服务");
        return new InnerBinder();
    }

在onBind()中让我们返回一个IBinder。本来是返回一个null的,为了实现和Activity的交互,这里我们改写一下,让它返会一个InnerBinder()对象。

我们先创建一个叫做InnerBinder的内部类,我们让这个内部类去暴漏我们想让MainActivity实现的方法callServiceInnerMethod(),在callServiceInnerMethod()里面直接实现sayhello()。

下面是InnerBinder的一个继承关系层级。Binder里面会实现IBinder的接口,而我们继承了Binder,就不用再去实现这个接口了。

android 应用后台置前台 安卓 运行前台服务_android


InnerBinder中的代码示例:

public class InnerBinder extends Binder{
        public void callServiceInnerMethod(){
            sayhello();
        }
    }

这里我们把sayhello()改成private不让服务外部调用。

private void sayhello(){
     Toast.makeText(this,"hello",Toast.LENGTH_SHORT).show();
    }

紧接着,我们修改onBind()方法,让它直接返回一个InnerBinder对象就可以了。

@Override
public IBinder onBind(Intent intent){
        Log.d(TAG, "onBind: 服务");
        return new InnerBinder();
    }

之后我们在主活动中去实现绑定服务和解绑服务的方法就可以实现由这个活动去调用服务中的方法啦。需要注意的是bindService()这个方法里面需要写进去三个参数(intent,mConnection,创建的模式)。BIND_AUTO_CREATE的意思是,如果绑定的时候没有创建就新创建一个,如果已经创建,就只执行绑定。mConnection是我们需要重写的一个回调。这个bindService会有返回值,为了提高代码的可靠性,我们要用返回值来判断它是不是正确。ctrl+alt+f 可以把一个变量改为全局变量。

第二个参数的官方解释:
Receives information as the service is started and stopped.This must be a valid ServiceConnection object;it must not be null.

在服务启动和停止时接收信息。这必须是一个有效的 ServiceConnection 对象;它不能为空。就是说我们要将ServiceConnection()这个抽象方法实例化,然后传入bindService()中去。下面是在活动中调用服务方法的代码示例:

/**
     * 绑定服务
     * @param view
     */
    public void bandServiceClick(View view){
        Intent intent=new Intent();
        intent.setClass(this, FirstService.class);
        isService = bindService(intent,mConnection,BIND_AUTO_CREATE);
    }

    private ServiceConnection mConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
           //连接上之后的回调
            Log.d(TAG, "onServiceConnected: 连接上之后的回调");
            mRemoteBinder = (FirstService.InnerBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceConnected: 没有连接上之后的回调");
            mRemoteBinder=null;
            //解开就null,释放资源
        }
    };

    /**
     * 解绑服务
     * @param view
     */
    public void unbandServiceClick(View view){
        if (mConnection!=null&&isService){
            unbindService(mConnection);
        }

    }

ctrl+h看实现的接口,下面加深一下对两种启动模式不同点的理解。

三.服务的生命周期

android 应用后台置前台 安卓 运行前台服务_java_02

通过这个图可以看到,两种启动Service的方式以及他们的生命周期,bindService的不同之处在于当绑定的组件销毁后,对应的Service也就被kill了。我们在使用onBind()启动模式时,需要在destory()中解绑,要不然退出程序会有内存泄漏。Service的声明周期相比与Activity的简单了许多,只要好好理解两种启动service方式的异同就行。

总结:

  1. bindService启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端,service就一个,但是连接绑定到service上面的客户端client可以是一个或多个。
  2. startService启动的服务默认无限期执行(可以通过Context的stopService或Service的stopSelf方法停止运行),bindService启动的服务的生命周期与其绑定的client息息相关。当client销毁的时候,client会自动与Service解除绑定,client也可以通过明确调用Context的unbindService方法与Service解除绑定。当没有任何client与Service绑定的时候,Service会自行销毁(通过startService启动的除外)。
  3. startService和bindService二者执行的回调方法不同:startService启动的服务会涉及Service的的onStartCommand回调方法,而通过bindService启动的服务会涉及Service的onBind、onUnbind等回调方法。

四.onBind()的大致流程

调用了bindService之后,由于Service此时还不存在,那么Android就会首先创建一个Service的实例,并执行其onCreate回调方法,onCreate方法在其生命周期中只会被调用一次。

然后会调用Service的onBind方法,该方法只有在第一次bindService调用后才会执行,onBind执行后会返回一个IBinder类型的实例,此时Android会将该IBinder实例存起来,这个IBinder实例是对所有client共享的。当下次其他的client执行bindService的时候,不会再执行onBind方法,因为我们之前已经得到了一个IBinder实例,Android会直接使用这个IBinder实例。

在得到了IBinder实例之后,Android会执行client端ServiceConnection中的onServiceConnected方法,在该方法中我们会得到IBinder实例,并通过该IBinder实例得到了TestService实例,这样我们的客户端ActivityA就通过IBinder与TestService建立了连接,我们就可以调用Service的一些方法。

五.接口的作用

用一个public的接口,在InnerBinder中实现这个接口。就是在服务中实现了接口的方法。而这个InnerBinder就相当于它父类的子类,当返回到产生联系的活动时,就拿到了InnerBinder实例对象。

private class InnerBinder extends Binder implements ICommunication {
        @Override
        public void callServiceInnerMethod() {
            sayhello();
            //这个类此时是私有的,外面看不到
        }
    }

    @Override
    public IBinder onBind(Intent intent){
        Log.d(TAG, "onBind: 服务");
        return new InnerBinder();
    }

此时,在调用时,我们不再把它强转为InnerBinder,而是强转为ICommunication接口类,因为ICommunication是公开的,而我们在服务端已经重写了callServiceInnerMethod() 方法,相当于通过接口的方式,间接的调用了服务中私有的一些方法。

下面是接口的实现

public interface ICommunication {
    void callServiceInnerMethod();
}