Android总结 - Service

Service是一个长时间操作的后台服务,也可以做IPC操作。
Service有两种启动模式:Started和Bound。所谓”started”就是通过调用startService()而Bound就是通过调用bindService()。

Service的生命周期

Android总结 - Service_第1张图片

通过Service的生命周期可以得到Server的几个重要的回调函数:

  • onStartCommand()
    当其他组件,如 activity 请求服务启动时,系统会调用这个方法。一旦这个方法执行,服务就开始并且无限期的执行。如果实现这个方法,当这个服务完成任务后,需要你来调用 stopSelf() 或者 stopService() 停掉服务。如果只想提供绑定,不需要自己实现这个方法。
  • onBind()
    当有其他组件想通过 bindService() 方法绑定这个服务时系统就会调用此方法。在实现的方法里面,必须添加一个供客户端使用的接口通过返回一个IBinder来与服务通信,这个方法必须实现。当然不想允许绑定的话,返回null即可。
  • onCreate()
    服务第一次建立的时候会调用这个方法,执行一次性设置程序,在上面2个方法执行前调用。如果服务已存在,则不执行该方法。
  • onDestroy()
    服务不再使用则使用该方法。服务应该实现这个方法来清理诸如线程,注册的监听器等资源。这是最后调用的方法。

管理Service的生命周期

  • A started service
     Service从startService()开始启动,然后独立运行,必须调用stopSelf()或者stopService()来停止Service。当Service停止后,系统会销毁它。

  • A bound service
     Service通过其它足交调用bindService()启动。client通过IBinder 接口来调用service的方法。client可以通过unbindService()来关闭连接。多个clients可以同时bind到同一个Service,当所有的client都unbind后,系统就会销毁这个Service。

继承Service的回调函数

public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

在manifest申明Service

        <service android:enabled=["true" | "false"]
             android:exported=["true" | "false"]
             android:icon="drawable resource"
             android:isolatedProcess=["true" | "false"]
             android:label="string resource"
             android:name="string"
             android:permission="string"
             android:process="string" >
        . . .
    </service>

Service中的标签说明:

  • android:name
    你所编写的服务类的类名,可填写完整名称,包名+类名,如com.example.test.ServiceA,也可以忽略包名,用.开头,如.ServiceA,因为在manifest文件开头会定义包名,它会自己引用。
    一旦你发布应用,你就不能改这个名字(除非设置android:exported=”false”),另外name没有默认值,必须定义。

  • android:enabled
    是否可以被系统实例化,默认为true
    因为父标签也有enable属性,所以必须两个都为默认值true的情况下服务才会被激活,否则不会激活。

  • android:exported
    其他应用能否访问该服务,如果不能,则只有本应用或有相同用户ID的应用能访问。当然除了该属性也可以在下面permission中限制其他应用访问本服务。
    这个默认值与服务是否包含意图过滤器intent filters有关。如果一个也没有则为false

  • android:isolatedProcess
    设置true意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(binding and starting)。

  • android:label
    可以显示给用户的服务名称。如果没设置,就用的lable。不管怎样,这个值是所有服务的意图过滤器的默认lable。定义尽量用对字符串资源的引用。

  • android:icon
    类似label,是图标,尽量用drawable资源的引用定义。

  • android:permission
    是一个实体必须要运行或绑定一个服务的权限。如果没有权限,startService(),bindService()或stopService()方法将不执行,Intent也不会传递到服务。
    如果属性未设置,会由权限设置情况应用到服务。如果两者都未设置,服务就不受权限保护。

  • android:process
    服务运行所在的进程名。通常为默认为应用程序所在的进程,与包名同名。元素的属性process可以设置不同的进程名,当然组件也可设置自己的进程覆盖应用的设置。
    如果名称设置为冒号:开头,一个对应用程序私有的新进程会在需要时和运行到这个进程时建立。如果名称为小写字母开头,服务会在一个相同名字的全局进程运行,如果有权限这样的话。这允许不同应用程序的组件可以分享一个进程,减少了资源的使用。

创建Started Service

写自己的Service有两种方法,继承Service或者IntentService,IntentService是Service的子类,由于Service运行在主线程中,所以如果继承Service就必须要新建一个线程去做耗时的工作。而IntentService就是这个作用,会使用一个工作线程来处理多个请求:

  • 继承IntentService
    大多数服务不需要同时处理多个请求,继承IntentService是最好的选择。
    IntentService做了以下的工作:

    • 创建默认的一个worker线程处理传递给onStartCommand()的所有intent,从而不占据应用的主线程
    • 创建一个工作队列一次传递一个intent到你实现的onHandleIntent()方法,避免了多线程。
    • 在所以启动请求被处理后自动关闭服务,不需要调用stopSelf()
    • 默认提供onBind()的实现,并返回null
    • 默认提供onStartCommand()的实现,实现发送intent到工作队列再到你的onHandleIntent()方法实现。

    这些都加入到IntentService中了,你需要做的就是实现构造方法和onHandleIntent(),如下:

public class HelloIntentService extends IntentService {

  /** * A constructor is required, and must call the super IntentService(String) * constructor with a name for the worker thread. */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /** * The IntentService calls this method from the default worker thread with * the intent that started the service. When this method returns, IntentService * stops the service, as appropriate. */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}

如果需要重写其他回调方法,如onCreate(),onStartCommand()等,一定要调用super()方法,保证IntentService正确处理worker线程,只有onHandleIntent()和onBind()不需要这样。如:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}
  • 继承Service
    继承Service就可以实现对请求多线程的处理(而不是通过一个work queue做线性处理)。Android官方实现了一个与IntentService类似功能的例子,也是通过一个work thread来分发每一次请求:
public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service. Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block. We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

onStartCommand()返回值

返回一个整型值,用来描述系统在杀掉服务后是否要继续启动服务,返回值有三种:

  • START_NOT_STICKY
    如果系统在onStartCommand()返回之后杀掉了这个Service,那么不重新创建这个Service,除非有新的intent会传递过来。这是最安全的选项,可以避免在不必要的情况下也启动Service。
  • START_STICKY
    如果系统在onStartCommand()返回之后杀掉了这个Service,系统重新创建服务并且调用onStartCommand()方法,但并不会传递最后一次传递的intent,只是传递一个空的intent。除非存在将要传递来的intent,那么就会传递这些intent。这个适合播放器一类的服务,不需要执行命令,只需要独自运行,等待任务。
  • START_REDELIVER_INTENT
    如果系统在onStartCommand()返回之后杀掉了这个Service,系统重新创建服务并且调用onStartCommand()方法,传递最后一次传递的intent。其余存在的需要传递的intent会按顺序传递进来。这适合像下载一样的服务,立即恢复,积极执行。

启动前台Service

 前台服务是被认为是用户已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该服务。前台Service必须在状态栏上提供一个notification,这个Notification不会消失除服Service销毁了或者被从前台移除。
 比如,音乐播放器通过一个Service在播放音乐就需要被设为在前台运行,因为用户需要明确的知道它的操作。在状态栏的notification 需要显示当前的歌曲并且允许用户启动音乐播放器。
 想要请求Service在前台运行,需要调用startForeground()。比如:

Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
        System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

The integer ID you give to startForeground() must not be 0.

想要把Service从前台移除,需要调用stopForeground()。这个方法需要一个boolean参数,指明是否需要从状态把Notification移除。这个方法不会stop service。然而,如果前台service被stop了,那么对应的Notification也会被移除。

Andorid总结 - Bound Services

Andorid总结 - AIDL

创建Bound Service

Bound Service允许其他应用组件通过bindService()来绑定它,从而建立一个长链接,可以提供交互和返回值。

建立一个绑定的服务需要实现onBind()方法返回一个定义了与服务通信接口的IBinder对象。其他应用程序组件可以调用bindService()方法获取接口并且调用服务上的方法。

创建一个绑定的服务,第一件事就是定义一个说明客户端与服务通信方式的接口。这个接口必须是IBinder的实现,并且必须要从onBind()方法返回。一旦客户端接收到了IBinder,就可以通过这个接口进行交互。

多个客户端可以绑定到一个服务,可以用unbindService()方法解除绑定,当没有组件绑定在服务上,这个服务就会被销毁。

创建Bound Service

想要创建一个提供binding的service,必须提供一个IBinder给client与service进行交互。有三种方式可以定义接口:

  • 继承Binder类
    如果service只给自己的应用使用并且不会做跨进程的操作,我们可以继承Binder类并且通过onBind()返回一个它的实例。client接收到这个Binder可以直接使用它开调用service提供的方法。
    使用步骤:
    1. 创建一个Binder实例,任意实现一种:
      • 在Binder类中包含client可以访问的public方法。
      • 返回一个当前Service的实例,client可以通过这个实例调用Service的public方法。
      • 返回一个封装了service public方法的类对象,client可以调用。
    2. 通过onBind()回调函数返回Binder实例。
    3. 在client端,通过onServiceConnected()的回调函数接收Binder 对象,通过Binder对象来调用公开的方法。

例子,通过onBinder返回Service对象:

public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();

    /** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}
  • 使用Messenger
    如果service需要与别的进程进行交互,那么可以使用Messenger提供的接口来实现service。这个技术不需要使用AIDL就能实现IPC。
    Messenger的使用方法:
    1. service需要实现一个Handler来接收client调用时传递过来的callback。
    2. 通过Handler来创建一个 Messenger对象。
    3. 这个Messenger 对象创建一个IBinder,提供给onBind()返回给client。
    4. Clients 使用 IBinder来初始化这个Messenger (that references the service’s Handler), client可以使用这个Messenger 来给service发送Message对象。
    5. Service中实现的Handler会接收并处理接收到的Message。
      使用Messenger来实现的Service实例:
public class MessengerService extends Service {
    /** Command to the service to display a message */
    static final int MSG_SAY_HELLO = 1;

    /** * Handler of incoming messages from clients. */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /** * Target we publish for clients to send messages to IncomingHandler. */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}

一个调用Messenger实现的Service的客服端实例:

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */
    Messenger mService = null;

    /** Flag indicating whether we have called bind on the service. */
    boolean mBound;

    /** * Class for interacting with the main interface of the service. */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the object we can use to
            // interact with the service. We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = new Messenger(service);
            mBound = true;
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mBound = false;
        }
    };

    public void sayHello(View v) {
        if (!mBound) return;
        // Create and send a message to the service, using a supported 'what' value
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

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

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to the service
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}
  • Using AIDL

Binding to a Service

想要绑定Service,Client需要实现:


  1. 实现ServiceConnection接口,必须实现两个方法。

    • onServiceConnected(),系统会调用这个函数用于返回IBinder。
    • onServiceDisconnected(),当连接异常断掉之后,系统会调用这个函数。unbind的时候不会调用。

  2. 调用bindSercie,把ServiceConnection 作为参数传递过去。
  3. 当系统调用onServiceConnected() 之后,就可以 通过获取到的IBinder来调用service公开的方法。
  4. 调用unbindService()来断开连接。

实现ServiceConnection的实例:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

调用bindService():

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

bindService的第三个参数指示binding时的选项。通常应该设置为BIND_AUTO_CREATE 。还可以为BIND_DEBUG_UNBIND 和BIND_NOT_FOREGROUND, 或者设为0代表什么都不选.

额外的注意项

binding to a service的时候有几个注意事项:

  • 应该总是捕捉DeadObjectException 异常, 这种异常会在链接被破坏掉的时候抛出来。 这是远程方法会抛出的唯一的一种异常。
  • 跨进程处理的时候,Object的引用会被计数。
  • 需要在client的生命周期中成对的使用binding和unbinging。例如:
    • 如果只需要在Activity可见的时候使用service,应该在onStart的时候bind servcie,在onStop的时候unbind srvice。
    • 如果想Activity在后台的时候也与service交互,那么在onCreate的时候bind service,在onDestroy的时候unbind service。

管理Bound Service的生命周期

Android总结 - Service_第2张图片

使用AIDL

使用AIDL的必要条件是你允许来自不同应用的client来访问你的service做IPC的操作,并且需要处理多线程的情况。

如果你不要做跨进程的IPC,那么你应该使用“Extending the Binder class”方法,参考Andorid总结 - Bound Services

如果需要跨进程IPC但是不会有多线程的操作,那么你应该使用“Using a Messenger”方法,参考Andorid总结 - Bound Services

在使用AIDL时,client与service在同一进程和不在同一进程的区别:

  • 在相同进程中,service中的AIDL方法会在调用者所在的线程中执行。
  • 在不同进程中,远程进程的调用会从维护在本进程中的一个线程池中随机分配一个线程来执行。AIDL的接口实现必须是线程安全的。
  • oneway 关键字用来修饰远程调用的行为。如果使用的话,呢么远程调用就不会被block了;它之后发送数据并立马返回。 oneway对本地调用不起作用,调用依旧是同步的。

定义AIDL接口

你必须要定义AIDL接口在 “.aidl”文件中,语法跟Java类似。“.aidl”可以通过Android Studio来帮助创建,Android Studio会默认创建一个文件夹来保存.aidl的文件。

在应用程序编译之后,Android SDK tools 会根据”.aidl”生成一个IBinder的接口,这个接口保存在build目录下。service必须实现IBinder,然后把这个IBinder传给client调用。

具体步骤如下:

  1. 创建.aidl文件
    “.aidl”文件中定义一个接口,接口里的方法提供给client调用。
    AIDL使用很简单的Java语法,里面什么的方法可以带有参数和返回值。参数和返回值的类型可以为任意的类型,甚至可以用AIDL生成的接口做参数。只能定义方法,不能有变量。
    AIDL中可以用的默认类型有:

    • String
    • CharSequence
    • List
    • Map

    使用额外的数据类型时,必须使用import来导入,即使在同一个包中。

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

编译完成后的目录看似这样:
Android总结 - Service_第3张图片
Android总结 - Service_第4张图片

2. 实现接口

如上图,当编译完成后,Android SDK tools会给我们在generated/source中生成对应的java接口文件。生成的接口包含了一个叫“Stub”的子类,比如“YourServiceInterface.Stub”,这个子类同事也继承了Binder,而我们的Servcie就是要实现这个Stub子类,作为Binder来传递给client使用。

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

在实现AIDL接口时,有几个小规格要注意:

  • 调用接口不保证在主线程执行,所以需要考虑启动时的多线程和建立时的线程安全。
  • 默认,RPC调用是同步的。所以如果请求比较耗时的话,请使用子线程来调用。
  • client端不用收到任务异常消息。

    1. 把接口暴露给clients
      只需在onBinder()函数中把上一步的实现了Stub的对象mBinder 返回给clients。
public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

而client的onServiceConnected() callback 就会接受到这个mBinder 对象。接收到之后通过调用 YourServiceInterface.Stub.asInterface(service) 把mBinder转换为 YourServiceInterface类型. For example:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};

调用 IPC 方法

跟Bound Sercie类似,唯一需要注意的就是调用方法的时候,需要捕获一个 异常 DeadObjectException。当连接断开的时候会抛出这个异常。

在IPC中传递对象

可以在IPC中传递对象,但是这个对象必须实现Parcelable 接口。快速实现Parcelable 可以通过Android Studio的插件“android-parcelable-intellij-plugin”

你可能感兴趣的:(android)