浅谈Android Service

2015/11/22 22:00
阅读数 167

Service的基本概念

Service 是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

Note:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 这意味着,如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应在服务内创建新线程来完成这项工作。通过使用单独的线程,可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。

Service服务的两种形式:

启动

  • 当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,直到服务使用 stopSelf() 自行停止运行,或由其他组件通过调用 stopService() 停止它为止。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。

绑定

  • 当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁

Note: 两种形式的服务是可以同时运行的,也就是说,它既可以是无限期启动的启动服务,也允许被绑定。

无论应用是处于启动状态还是绑定状态,抑或处于启动并且绑定状态,任何应用组件均可像使用活动那样通过调用 Intent 来使用服务(即使此服务来自另一应用)。 不过,您可以通过清单文件将服务声明为私有服务,并阻止其他应用访问。

声明服务

在清单文件中声明服务:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

为了确保应用的安全性,请始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。 最好的启动方式为:

Intent intent = new Intent(mContext, SimService.class)

如果你非要为service添加过滤器,随后必须使用 setPackage() 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性。

Intent intent = new Intent("org.thanatos.action.name").setPackage("org.thanatos.example");

Note:这样做会导致其它应用程序也能访问到你的service,如果你想阻止其他应用启动你的服务,可以通过添加 android:exported 属性并将其设置为 "false",确保服务仅适用于您的应用。

Service的一般回调方法

package org.example.thanatos.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * Created by thanatos on 15-11-21.
 */
public class SimService extends Service{


    /**
     * 当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。
     * 在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。
     * 请务必实现此方法,但如果您并不希望允许绑定,则应返回 null
     * @param intent Context.bindService(Intent intent)
     * @return IBinder的实现
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。
     * 如果服务已在运行,则不会调用此方法。
     */
    @Override
    public void onCreate() {
        Log.d("app", ">>>SimService onCreate<<<");
        super.onCreate();
    }

    /**
     * 当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,
     * 系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。
     * 如果您实现此方法,则在服务工作完成后,需要由您通过调用 stopSelf() 或
     * stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)
     *
     * @param intent Context.startService(Intent intent) 所提供的intent对象,如果service被回收之后重启,
     *               并且之前返回值非START_STICKY_COMPATIBILITY,那么它将会是空值
     *
     * @param flags 启动时额外的数据,为0, START_FLAG_REDELIVERY, or START_FLAG_RETRY 三者中的其中之一
     *
     * @param startId 启动标识
     *
     * @return 声明当前service的启动姿势,声明如果service被杀死那么它将如果重新开始
     *                     START_STICKY
     *                     START_NOT_STICKY
     *                     START_REDELIVER_INTENT
     *                     START_STICKY_COMPATIBILITY
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("app", ">>>SimService onStartCommand<<<");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,
     * 如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。
     */
    @Override
    public void onDestroy() {
        Log.d("app", ">>>SimService onDestroy<<<");
        super.onDestroy();
    }

    /**
     * 当所有客户端都没有连接到这个service所提供的特定接口时会调用
     * @param intent
     * @return
     */
    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    /**
     * 如果onUnbind返回true是会调用
     * @param intent
     */
    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
    }
}

onStartCommand的返回值有一下四种:

  • START_STICKY:如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)

  • START_NOT_STICKY:如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent要传递给该服务,否则系统不会重建服务,onStartCommand()也将不会受理Intentnull的消息。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

  • START_REDELIVER_INTENT:如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

  • START_STICKY_COMPATIBILITY:能力和START_STICKY一样,但是不保证服务被杀死之后能调用onStartCommand()

创建启动服务

从传统上讲,你可以扩展两个类来创建启动服务:

  • Service 这是适用于所有服务的基类。扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。

  • IntentService 这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果你不要求服务同时处理多个请求,这是最好的选择。 你只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使你能够执行后台工作。

扩展IntentService类

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。

IntentService 执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
  • 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()
  • 提供 onBind() 的默认实现(返回 null)。
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。

综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。)

启动服务

你可以通过将 Intent(指定要启动的服务)传递给 startService(),从 Activity 或其他应用组件启动服务。Android 系统调用服务的 onStartCommand() 方法,并向其传递 Intent。(切勿直接调用 onStartCommand()。)

例如,Activity 可以结合使用显式 Intent 与 startService()

Intent intent = new Intent(this, SimService.class);
startService(intent);

startService() 方法将立即返回,且 Android 系统调用服务的 onStartCommand() 方法。如果服务尚未运行,则系统会先调用 onCreate(),然后再调用 onStartCommand()

多个服务启动请求会导致多次对服务的 onStartCommand() 进行相应的调用。但是,要停止服务,只需一个服务停止请求(使用 stopSelf()stopService())即可。

使用PendingIntent接收服务返回结果

如果服务亦未提供绑定,则使用 startService() 传递的 Intent 是应用组件与服务之间唯一的通信模式。但是,如果您希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用 getBroadcast()),并通过启动服务的 Intent传递给服务。然后,服务就可以使用广播传递结果。

1.创建一个BroadcastReceiver

public class DataReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("app", "I catch it + " + intent.getStringExtra("result"));
    }

}

2.创建一个Service

public class CanReturnDataService extends Service{

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        PendingIntent receiver = intent.getParcelableExtra("receiver");
        Intent data = new Intent();
        data.putExtra("result", "hello world");
        try {
            receiver.send(getApplicationContext(), 0, data);
        } catch (PendingIntent.CanceledException e) {
            e.printStackTrace();
        }
        return super.onStartCommand(intent, flags, startId);
    }

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

3.在清单文件里声明

<manifest ... >
  ...
  <application ... >
      <receiver android:name="org.example.thanatos.receiver.DataReceiver"/>
      <service android:name="org.example.thanatos.service.CanReturnDataService" android:exported="false"/>
      ...
  </application>
</manifest>

4.启动服务

@Override
public void onClick(View v) {
    Intent broadcastReceiverIntent = new Intent(MainActivity.this, DataReceiver.class);
    PendingIntent pi = PendingIntent.getBroadcast(MainActivity.this, 0, broadcastReceiverIntent, 0);
    Intent serviceIntent = new Intent(MainActivity.this, CanReturnDataService.class);
    serviceIntent.putExtra("receiver", pi);
    startService(serviceIntent);
}

停止服务

启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 onStartCommand() 返回后会继续运行。因此,服务必须通过调用 stopSelf() 自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。

一旦请求使用 stopSelf()stopService() 停止服务,系统就会尽快销毁服务。

但是,如果服务同时处理多个 onStartCommand() 请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已经收到了新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为了避免这一问题,您可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。也就说,在调用 stopSelf(int) 时,只有传递的id和最后启动该服务所生成的id(传递给 onStartCommand() 的 startId)匹配时才会销毁服务 。

Note:为了避免浪费系统资源和消耗电池电量,应用必须在工作完成之后停止其服务。 如有必要,其他组件可以通过调用 stopService() 来停止服务。即使为服务启用了绑定,一旦服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。

创建绑定服务

创建绑定服务时,必须实现onBind()方法,返回IBinder的实现,用以提供客户端用来与服务进行交互的编程接口。 您可以通过三种方法定义接口:

  • 扩展Binder类: 如果服务只是你自己的应用的后台工作线程,则优先采用这种方法。 不以这种方式创建接口的唯一原因是,您的服务被其他应用或不同的进程占用。

  • Messenger: 这是执行进程间通信 (IPC) 的最简单方法,适用于多个进程间共享服务,因为 Messenger会在单一线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。

  • AIDL: 适用于多个应用程序间共享服务。进程间是不能共享内存的,AIDL执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,从而实现 IPC。

Note: 多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至任何其他绑定的客户端。

扩展Binder类

创建一个绑定服务

public class BinderService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        Log.d("thanatos", "bind binder service");
        return new LocalBinder();
    }

    @Override
    public void onDestroy() {
        Log.d("thanatos", "BinderService is destroied");
        super.onDestroy();
    }

    public class LocalBinder extends Binder{

        public void doSomething(){
            // do something here
            Log.d("thanatos", "BinderService do something here");
        }
    }
}

绑定服务


    private ServiceConnection serviceCnn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Intent intent = new Intent(this, BinderService.class);

        serviceCnn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // --- connected service ----
                BinderService.LocalBinder binder = (BinderService.LocalBinder) service;
                binder.doSomething();
            }

            /**
             * 当连接对象服务丢失时触发这个回调,典型情况的service出现异常强制终止或者被系统回收
             * @param name 丢失连接对象的具体服务名
             */
            @Override
            public void onServiceDisconnected(ComponentName name) {
                // ---- the service connection had lost the service ----
                Log.d("thanatos", "unbind BinderService");
            }
        };

        bindService(intent, serviceCnn, BIND_AUTO_CREATE);

    }

    @Override
    protected void onDestroy() {
        unbindService(serviceCnn);
        super.onDestroy();
    }

Note: 如果你在一个Activity组件绑定了一个服务,但是在它销毁时没有解除绑定,那么应用会抛出一个android.app.ServiceConnectionLeaked内存泄漏的异常,虽然这个异常只是单纯的打印在控制台,并不会终止你的程序,但是还是建议记得取消绑定以免过多的内存泄漏导致内存溢出。

使用Messenger

创建MessengerService服务

public class MessengerService extends Service {

    private final Messenger messenger = new Messenger(new LocalHandler());

    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }

    class LocalHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0:
                    Log.d("thanatos", "MessengerService said zero");
                    break;
                case 1:
                    Log.d("thanatos", "MessengerService said one");
                    break;
            }
        }
    }
}

绑定服务

    private ServiceConnection serviceCnn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Intent intent = new Intent(this, MessengerService.class);

        serviceCnn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // --- connected service ----
                Messenger messenger = new Messenger(service);
                try {
                    messenger.send(Message.obtain(null, 1, 0, 0));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            /**
             * 当连接对象服务丢失时触发这个回调,典型情况的service出现异常强制终止或者被系统回收
             * @param name 丢失连接对象的具体服务名
             */
            @Override
            public void onServiceDisconnected(ComponentName name) {
                // ---- the service connection had lost the service ----
                Log.d("thanatos", "unbind BinderService");
            }
        };

        bindService(intent, serviceCnn, BIND_AUTO_CREATE);

    }

    @Override
    protected void onDestroy() {
        unbindService(serviceCnn);
        super.onDestroy();
    }

Note: Messenger并没有创建一个额外的HandlerThread线程去循环处理Message而是仍然使用主线程的Looper

管理绑定服务的生命周期

当服务与所有客户端之间的绑定全部取消时,Android 系统便会销毁服务(除非还使用 onStartCommand() 启动了该服务)。因此,如果你的服务是纯粹的绑定服务,则无需对其生命周期进行管理—Android 系统会根据它是否绑定到任何客户端代您管理。

不过,如果您选择实现 onStartCommand() 回调方法,则您必须显式停止服务,因为系统现在已将服务视为已启动。在此情况下,服务将一直运行到其通过 stopSelf() 自行停止,或其他组件调用 stopService() 为止,无论其是否绑定到任何客户端。

在前台运行服务

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。

例如,应该将从服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。

要请求让服务运行于前台,请调用 startForeground()。此方法取两个参数:唯一标识通知的整型数和状态栏的 Notification。例如:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Notification notification = new NotificationCompat.Builder(getApplicationContext())
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("通知栏")
            .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0))
            .build();
    startForeground(2, notification);
    return super.onStartCommand(intent, flags, startId);
}

Note:提供给 startForeground() 的整型 ID 不得为 0。

要从前台删除服务,请调用 stopForeground()。此方法取一个布尔值,指示是否也删除状态栏通知。 此方法绝对不会停止服务。 但是,如果您在服务正在前台运行时将其停止,则通知也会被删除。

Service的生命周期

  • 服务的整个生命周期从调用 onCreate() 开始起,到 onDestroy() 返回时结束。与 Activity 类似,服务也在 onCreate( 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。 无论服务是通过 startService() 还是 bindService() 创建,都会为所有服务调用 onCreate()onDestroy() 方法。

  • 服务的有效生命周期从调用 onStartCommand()onBind() 方法开始。每种方法均有 Intent 对象,该对象分别传递到 startService()bindService()。 对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。

仅当内存过低且必须回收系统资源以供具有用户焦点的 Activity 使用时,Android 系统才会强制停止服务。如果将服务绑定到具有用户焦点的 Activity,则它不太可能会终止;如果将服务声明为在前台运行(稍后讨论),则它几乎永远不会终止。或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止;如果服务是启动服务,则您必须将其设计为能够妥善处理系统对它的重启。 如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(不过这还取决于从 onStartCommand() 返回的值)。

展开阅读全文
打赏
0
2 收藏
分享
加载中
更多评论
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部