Android Service一些知识与理解

  1. Service的使用场景
  2. Service的后台工作对比Thread的后台工作
  3. Start型与Bound型启动模式的理解以及使用场景
  4. onBinder()方法返回的IBinder的含义,如何简单的获得IBinder对象
  5. Android UniversalMusicPlayer中的Service实例

概念

Service是四大组件之一,他有两个作用:在无需UI的情况下执行后台工作,提供功能给其他应用使用。

本文主要讨论第一个作用,也就是后台工作。这里的后台工作是与Activity执行的前台工作相对应的,他的后台指的是不需要界面交互,而工作指的是一系列服务于用户的功能。

Service与Activity一样继承自Context,因此它可以使用一系列Android运行环境相关的功能,比如读取资源文件。我们可以视Service跟Activity为同一级别,当我们的应用需要提供一些功能,且无需UI交互时,可以选择使用Service。

我们常说Work Thread执行的也是后台工作,对比Service的后台工作与Thread的后台工作,前者可以理解为“界面之外”,后者则是“UI线程之外”,前者关注的是无需界面交互的功能,后者负责不阻塞UI线程的执行操作。Service在默认的情况下是运行在进程的主线程中的,因此在Service中执行耗时操作也会阻塞UI线程,此时我们需要使用工作线程以避免ANR。Android系统为我们提供了IntentService类,他是一个自带了工作线程的Service。

启动模式

Service的启动模式分为Start型与Bound型。

Start型

Start型由startService()方法启动,首次创建时Service的onCreate()会被调用,随后onStartCommand()被调用。当onStartCommand()被调用后Service会无限期的执行下去,我们需要主动负责Service的结束,通过调用stopSelf()stopService

Start型的Service响应我们的指令并执行任务(onStartCommand()),当任务完成时Service可以自行结束(stopSelf()),当任务不再需要执行也可以由外界结束(stopService())。

Bound型

Bound型由bindService()方法启动,首次创建Service时会调用onCreate(),随后调用onBind()并返回一个IBinder,IBinder能够让我们与Service交互,我们可以向Service发出请求并获得结果,就好像Client-Server架构一样。因此我们把调用bindService()的组件称为Client,Bound型Service的结束不需要我们负责,当所有Client都通过unBindService()解绑后Service就会销毁。


两种类型非常好区分,他们的调用方式不同,生命周期的回调也不同。而一个Service可以即是Start型又是Bound型,只要他通过两种方法都启动过就行了,这个时候Service的结束条件是调用了stop方法且所有Client的连接都断开了,否则就能持续存活并运转。这也好理解,用两种方法启动过就需要用两种方法结束。

两种模式的使用场景

作为开发者,我们更关注的是如何选择启动类型。那我认为我们首先要能理解设计者对这两种模式的定位与设计:

  • Start型的Service关注的是任务,我们启动Service并让他按我们的指令去执行任务,当Service的任务完成或者我们不再需要它执行时,他的使命就结束了。
  • Bind型的Service关注的是客户,客户与他连接并持续获得服务,当所有的客户都断开连接,他的使命结束。

所以我们根据自己的意图选择启动类型就可以了。如果我们的目的是启动一段与界面无关的工作,无论当前界面切换还是消失这段工作都能持续进行下去,那就选择Start型。如果我们希望当前组件能够与Service做些交互,把Service看成当前组件的一个功能模块,那就选择Bound型。如果两种意图兼而有之,那就通过两种启动类型启动。

后面会以Android UniversalMusicPlayer为例看下启动模式的选择与使用。

onBind()与IBinder

我曾见过身边同事对IBinder产生误解,看到IBinder就联想到IPC(跨进程通信),继而认为onBind()方法也是为跨进程场景服务的,对其敬而远之。实际上IBinder只是允许远程操作的对象的基本接口,如同Serializable是序列化操作的基本接口。我们实现IBinder以确保Service提供的操作对象能顺利传输给其他组件,开发中我们的重点应该放在其他组件如何通过IBinder与Service交互。

官方提供给了我们三种方案去获得IBinder对象:

  1. 继承Binder类
  2. 使用Messenger类
  3. 使用AIDL

继承自Binder类

当我们确保Service仅为本应用提供服务并且与Client总是运行在同一个进程中时,最佳的方法是扩展Binder对象,并为Binder对象设置public方法以便组件调用。

使用Messenger类

当我们需要跨进程通信时,最简单的方法是创建一个Messenger类,Messenger能与一个Handler建立关联并向他发送消息,Messenger类自带了一个方法getBinder()。我们需要在Service中定义一个Handler,决定Service如何响应消息,然后将Messenger跟Handler关联,这样其他组件就能通过Messenger向Service发送消息了。

这是最简单的实现IPC的方案,所有对Messenger发送的消息都集中到了一个线程中,因此也不会有线程安全之类的问题需要考虑。

使用AIDL

AIDL是系统级别的实现IPC的方案,上面的Messenger也是基于AIDL实现的,对这一块我没有深入研究,上面两个方案基本能覆盖我们的需求。

onBind实例 - 自定义Binder类

onBind()的实现实际上非常容易,我们来看下官方的demo。

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 {
        // 获得Service对象,继而可以调用Service中的所有公开方法
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // 返回自定义Binder的实例
        return mBinder;
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

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

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

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(mConnection);
        mBound = false;
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            // 连接成功时,操作Service对象
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service;
            // 获得连接的Service对象
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

逻辑非常简单

  1. 在Binder中定义组件可执行的方法,或者直接返回Service对象
  2. 组件成功连接Service时获得IBinder对象
  3. 组件确认Service保持连接,然后通过IBinder对象与Service交互,demo中自定义Binder直接返回了Service实例,那Activity种就可以直接操作这个Service实例。

onBind实例 - 使用Messenger

再看下使用Messenger的demo:

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

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

在设计上也很简单:

  1. 在Service中定义一个Handler,响应操作
  2. 获得一个与Handler相关联的Messenger,通过Messenger可以获得IBinder实例
  3. 组件拿到IBinder后,通过IBinder生成一个Messenger
  4. 组件确认Service还保持连接,使用自己生成的Messenger发送消息即可,Service会接收到他的消息

实例 - Android UniversalMusicPlayer

Android UniversalMusicPlayer是Google音乐播放器的Sample,我们思考下音乐播放器中后台任务的需求:

  1. 音乐播放器肯定是个后台任务,无论是否展示UI都应该保持工作,适合使用Service。
  2. 音乐播放器开始播放后应该保持工作,直到音乐播完或是我们结束播放,适合使用Start型启动
  3. 音乐播放过程中用户可以使用UI控制播放情况,因此Activity可以与Service交互,适合使用Bound型启动
  4. 当用户不播放音乐时,可能不需要Service,可以使用stopService()避免Service长期存活

因此Android UniversalMusicPlayer 适合使用Start型与Bound型两种启动方式配合。我们来看下他的源码的做法。

启动Service

我们在Activity中使用MediaBrowserCompat去控制播放器,首先第一步就是启动播放器的Service,在源码中我们可以看到:

/// MediaBrowserCompat.java

public void connect() {
    ...
    Intent intent = new Intent("android.media.browse.MediaBrowserService");
    intent.setComponent(MediaBrowserImplBase.this.mServiceComponent);
    MediaBrowserImplBase.this.mServiceConnection = MediaBrowserImplBase.this.new MediaServiceConnection();
    ...
    /// 通过BindService()启动Service
    /// 绑定结果在ServiceConnection的回调中
    bound = MediaBrowserImplBase.this.mContext.bindService(intent, MediaBrowserImplBase.this.mServiceConnection, 1);
    ...
}

所以播放器的Service是通过bindService()启动的,由前文中我们对bound型Service的了解,我们接下来应该要做两步:一是去Service的onBind()看下Service会为Client提供哪些功能。二是看下ServiceConnection回调后Client会如何操作IBinder。

Service.onBind()

Android UniversalMusicPlayer中播放器的Service是MusicService.java,他继承自MediaBrowserServiceCompat,这是Android Media框架提供的基类。

/// MediaBrowserServiceImplBase.java

public IBinder onBind(Intent intent) {
    /// action与MediaBrowserCompat中调用的一致
    return "android.media.browse.MediaBrowserService".equals(intent.getAction())?this.mMessenger.getBinder():null;
}

他选择的IBinder生成方案是Messenger,前面提到这种方案的优点是支持IPC,既然是Messenger就要看下他对应的Handler。

/// MediaBrowserServiceImplBase.java

public void onCreate() {
    this.mMessenger = new Messenger(MediaBrowserServiceCompat.this.mHandler);
}

final MediaBrowserServiceCompat.ServiceHandler mHandler = new MediaBrowserServiceCompat.ServiceHandler();

private final class ServiceHandler extends Handler {
    private final MediaBrowserServiceCompat.ServiceBinderImpl mServiceBinderImpl = MediaBrowserServiceCompat.this.new ServiceBinderImpl();

    ServiceHandler() {
    }

    public void handleMessage(Message msg) {
        Bundle data = msg.getData();
        switch(msg.what) {
        case 1:
            this.mServiceBinderImpl.connect(data.getString("data_package_name"), data.getInt("data_calling_uid"), data.getBundle("data_root_hints"), new MediaBrowserServiceCompat.ServiceCallbacksCompat(msg.replyTo));
            break;
        case 2:
            this.mServiceBinderImpl.disconnect(new MediaBrowserServiceCompat.ServiceCallbacksCompat(msg.replyTo));
            break;
        ...
        default:
            Log.w("MBServiceCompat", "Unhandled message: " + msg + "\n  Service version: " + 1 + "\n  Client version: " + msg.arg1);
        }

    }
}    

Handler的细节我们先不细看,我们回去看下Activity中的ServiceConnection

ServiceConnection.onServiceConnected

/// MedaiBrowserCompat.java

private class MediaServiceConnection implements ServiceConnection {
    MediaServiceConnection() {
    }
    
    public void onServiceConnected(final ComponentName name, final IBinder binder) {
        this.postOrRun(new Runnable() {
            public void run() {
                if(MediaServiceConnection.this.isCurrent("onServiceConnected")) {
                    MediaBrowserImplBase.this.mServiceBinderWrapper = new MediaBrowserCompat.ServiceBinderWrapper(binder, MediaBrowserImplBase.this.mRootHints);
                    MediaBrowserImplBase.this.mServiceBinderWrapper.connect(MediaBrowserImplBase.this.mContext, MediaBrowserImplBase.this.mCallbacksMessenger);
                }
            }
        });
    }
    
    public void onServiceDisconnected(final ComponentName name) {
        this.postOrRun(new Runnable() {
            public void run() {
                if(MediaServiceConnection.this.isCurrent("onServiceDisconnected")) {
                    MediaBrowserImplBase.this.mServiceBinderWrapper = null;
                }
            }
        });
    }
}

具体方案也跟我们前面demo中的一致,收到ServiceConnection的回调后更新状态,并通过一个ServiceBinderWrapper封装IBinder,我们猜想这个Wrapper类封装了对Binder的操作,也就是当前组件与Service的交互行为,我们去验证一下。

向Service发起请求

private static class ServiceBinderWrapper {
    private Messenger mMessenger;

    public ServiceBinderWrapper(IBinder target, Bundle rootHints) {
        this.mMessenger = new Messenger(target);
    }

    void connect(Context context, Messenger callbacksMessenger) throws RemoteException {
        Bundle data = new Bundle();
        data.putString("data_package_name", context.getPackageName());
        this.sendRequest(1, data, callbacksMessenger);
    }
    
    ...
    
    private void sendRequest(int what, Bundle data, Messenger cbMessenger) throws RemoteException {
        Message msg = Message.obtain();
        msg.what = what;
        msg.arg1 = 1;
        msg.setData(data);
        msg.replyTo = cbMessenger;
        this.mMessenger.send(msg);
    }
}    

基本验证了我们的猜想,组件通过ServiceBinderWrapper向Service发起请求,内部实现其实就是用Service传来的IBinder去生成一个Messenger,并发送消息。以connect()为例,生成了一个msg.what为1的Message并通过Messenger发送,这个消息由Service中的ServiceHandler收到并执行。可以看到ServiceHandler中对what为1的消息的处理就是connect。所以,Client调用connect()方法,顺利触发了Service中的connect()方法。

至此,组件启动了Service并与之连接,组件就可以操作播放器了。这里整个流程其实与onBind()方法的实例代码是一致的,只是业务场景复杂了许多。

startService

通过bindService,我们启动了Service并允许组件与之交互。我们前面提到过Bound型Service的结束条件是所有连接的Client都断开,如果我们开始播放音乐了,却由于界面切换而导致音乐断开,这是不能接受的,所以,UniversalMusicPlayer中也使用了Start型启动。

/// MusicService.java

@Override
public void onPlaybackStart() {
    ...
    // The service needs to continue running even after the bound client (usually a
    // MediaController) disconnects, otherwise the music playback will stop.
    // Calling startService(Intent) will keep the service running until it is explicitly killed.
    startService(new Intent(getApplicationContext(), MusicService.class));
}

对于Start型的Service我们需要负责他的结束

@Override
public void onPlaybackStop() {
    mSession.setActive(false);
    // Reset the delayed stop handler, so after STOP_DELAY it will be executed again,
    // potentially stopping the service.
    mDelayedStopHandler.removeCallbacksAndMessages(null);
    mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
    stopForeground(true);
}

private static class DelayedStopHandler extends Handler {
    private final WeakReference mWeakReference;

    private DelayedStopHandler(MusicService service) {
        mWeakReference = new WeakReference<>(service);
    }

    @Override
    public void handleMessage(Message msg) {
        MusicService service = mWeakReference.get();
        if (service != null && service.mPlaybackManager.getPlayback() != null) {
            if (service.mPlaybackManager.getPlayback().isPlaying()) {
                LogHelper.d(TAG, "Ignoring delayed stop since the media player is in use.");
                return;
            }
            LogHelper.d(TAG, "Stopping service with delay handler.");
            service.stopSelf();
        }
    }
}

当音乐结束时,会发送一个延迟消息,若消息到位了还是没有播放音乐,通过stopSelf()结束Service。这样做的作用是如果用户结束音乐后很快需要再次播放,不需要重启Service。且在Start型跟Bound型共同作用时,需要所有连接Service的组件都断开连接播放器才会销毁。

你可能感兴趣的:(Android Service一些知识与理解)