让其他应用程序复用本程序的服务。这样的服务叫远程(remote)服务,实际上是进程间通信(RPC)。
如果是调用不需要数据交互的远程服务(startservice调用),跟无需数据交互的我本地服务调用是类似的
如果调用远程服务进行数据交互,这时需要使用android接口描述语言(AIDL)来定义远程服务的接口,而不是上述那样简单的java接口。扩展名为aidl而不是java。可用上面的ICountService改动而成ICountSerivde.aidl,eclipse会自动生成相关的java文件。
package com.easymorse; interface ICountService { int getCount(); }
编写服务(Service)类,稍有差别,主要在binder是通过远程获得的,需要通过桩(Stub)来获取。桩对象是远程对象的本地代理。
ICountService.Stub继承自IBinder接口和ICountService接口
package com.easymorse; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class CountService extends Service { private boolean threadDisable; private int count; private ICountService.Stub serviceBinder = new ICountService.Stub() { @Override public int getCount() throws RemoteException { return count; } }; @Override public IBinder onBind(Intent intent) { return serviceBinder; } @Override public void onCreate() { super .onCreate(); new Thread( new Runnable() { @Override public void run() { while ( ! threadDisable) { try { Thread.sleep( 1000 ); } catch (InterruptedException e) { } count ++ ; Log.v( " CountService " , " Count is " + count); } } }).start(); } @Override public void onDestroy() { super .onDestroy(); this .threadDisable = true ; Log.v( " CountService " , " on destroy " ); } }
配置文件AndroidManifest.xml和上面的类似, 只是ServiceConnection中返回的IBinder是代理对象,不能使用强转,改用Stub.asInterface()
在Activity中使用服务的差别不大,只需要对ServiceConnection中的调用远程服务的方法时,要捕获异常。
private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { countService = ICountService.Stub.asInterface(service); ; try { Log.v( " CountService " , " on serivce connected, count is " + countService.getCount()); } catch (RemoteException e) { throw new RuntimeException(e); } } @Override public void onServiceDisconnected(ComponentName name) { countService = null ; } };
这样就可以在同一个应用程序中使用远程服务的方式和自己定义的服务交互了。
如果是另外的应用程序使用远程服务,需要做的是复制上面的aidl文件和相应的包构到应用程序中,其他调用等都一样。
下面是一个传参数的例子(string是可以作为基本数据类型传递的):
在AIDL中定义服务接口
为了展示远程服务实例,我们将编写一个股票报价服务。此服务将提供一种方法来获取股票代号并返回价格。要在Android 中编写远程服务,第一步是在 AID文件中定义服务的接口。
股票报价服务的 AIDL 定义
- //This file is IStockQuoteService.aidl
- package com.androidbook.stockquoteservice;
- interface IStockQuoteService
- {
- double getQuote(String ticker);
- }
上边我们为股票报价服务定义了 AIDL 文件并生成了绑定文件。现在我们将提供该服务的实现。要实现服务的接口,需要编写一个类来扩展 android.app.Service 并实现 IStockQuoteService接口。我们将编写的类 命名为 StockQuoteService。为了将服务向客户端公开,StockQuoteService 需要提供onBind()方法 的实现,我们还需要将一些配置信息添加到 AndroidManifest.xml文件中。 下面给出 我们服务类得实现。
IStockQuoteService 服务实现。
- package com.androidbook.stockquoteservice;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.util.Log;
- public class StockQuoteService extends Service {
- private static final String TAG = "StockQuoteService";
- public class StockQuoteServiceImpl extends IStockQuoteService.Stub {
- @Override
- public double getQuote(String ticker) throws RemoteException {
- Log.v(TAG, "getQuote() called for " + ticker);
- return 20.0;
- }
- }
- @Override
- public void onCreate() {
- super.onCreate();
- Log.v(TAG, "onCreate called");
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.v(TAG, "onDestory() called");
- }
- @Override
- public void onStart(Intent intent, int startId) {
- super.onStart(intent, startId);
- Log.v(TAG, "onStart() called");
- }
- @Override
- public IBinder onBind(Intent intent) {
- Log.v(TAG, "onBind() called");
- return new StockQuoteServiceImpl();
- }
- }
在这个服务类中大家可以看到我们实现了 onBind() 方法。从AIDL文件生成的 Stub类是抽象类并且它实现了 IStockQuoteService接口。在我们的服务实现中,有一个扩展了 Stub类得内部类,名为 StockQuoteServiceImpl。此类充当着远程服务实现,而且 onBind()方法会返回此类的实例。到此,我们有了一个有效的 ADIL服务,但是外部的客户端还无法连接到它。
要将服务向客户端公开,需要在AndroidManifest.xml文件中添加服务声明,而这一次我们需要一个Intent 过滤器来公开服务,如下。
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/473828/b86f52c6-be7c-3370-9984-11f0b5165e3d.jpg)
从客户端应用程序调用服务
当客户端与服务通信时,它们之间必须有一个协议或契约。在Android中,这个契约就是AIDL。所以,使用服务的第一步是,获取服务的 AIDL文件并将其复制到客户端项目中。当将AIDL文件复制到客户端项目时,AIDL 编译器将创建一个接口定义文件,这个文件与我们在服务端定义的文件相同。这会向客户端公开所有的方法、参数并返回服务的类型。我们创建一个新项目并复制AIDL文件。
(1)创建一个新的Android项目, 将其命名为 StockQuoteClient。使用不同的包名称比如 com.androidbook.stockquoteclient。在Create Activity字段中使用 MainActivity注意不要把IStockQuoteService.aidl文件放到这包中,这个包只有一个MainActivity 类。
(2)在此项目中新建一个包 com.androidbook.stockquoteservice,放在src目录下。
(3)将IStockQuoteService.aidl文件从 StockQuoteService 项目也就是我们服务端得项目复制到新建的包中。复制过来之后,AIDL编译器会自动生成关联的java文件。
重新生成的服务接口充当着客户端与服务之间的契约。下一步是获取服务的引用,以便调用getQuote()方法。对于远程服务,必须调用 bindService()方法,而不是 startService()方法。
客户端布局文件
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <Button
- android:id="@+id/bindBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Bind"/>
- <Button
- android:id="@+id/callBtn"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:text="Call Again"/>
- <Button
- android:id="@+id/unbindBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="UnBind"/>
- </LinearLayout>
MainActivity类
- package com.androidbook.stockquoteclient;
- import com.androidbook.stockquoteservice.IStockQuoteService;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.Toast;
- public class MainActivity extends Activity {
- protected static final String TAG = "StockQuoteClient";
- private IStockQuoteService stockService = null;
- private Button bindBtn;
- private Button callBtn;
- private Button unbindBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- bindBtn = (Button) findViewById(R.id.bindBtn);
- bindBtn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- bindService(new Intent(IStockQuoteService.class.getName()),
- serConn, Context.BIND_AUTO_CREATE);
- bindBtn.setEnabled(false);
- callBtn.setEnabled(true);
- unbindBtn.setEnabled(true);
- }
- });
- callBtn = (Button) findViewById(R.id.callBtn);
- callBtn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- callService();
- }
- });
- callBtn.setEnabled(false);
- unbindBtn = (Button) findViewById(R.id.unbindBtn);
- unbindBtn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- unbindService(serConn);
- bindBtn.setEnabled(true);
- callBtn.setEnabled(false);
- unbindBtn.setEnabled(false);
- }
- });
- }
- private void callService() {
- try {
- double val = stockService.getQuote("SYH");
- Toast.makeText(this, "Value from service is " + val,
- Toast.LENGTH_LONG).show();
- } catch (RemoteException e) {
- Log.e("MainActivity", e.getMessage(), e);
- }
- }
- private ServiceConnection serConn = new ServiceConnection() {
- // 此方法在系统建立服务连接时调用
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.v(TAG, "onServiceConnected() called");
- stockService = IStockQuoteService.Stub.asInterface(service);
- callService();
- }
- // 此方法在销毁服务连接时调用
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Log.v(TAG, "onServiceDisconnected()");
- stockService = null;
- }
- };
- }
MainActivity里边我们定义了三个按钮 分别是 Bind、Call Again 和 UnBind。当用户单击 Bind 按钮时,活动调用bindService()方法。类似地,当用户点击 UnBind 时,活动调用unbindService()方法。请注意传递给 bindService()方法 的3个参数:AIDL 服务的名称、ServiceConnection 实例和自动创建服务的标志。
我们在程序了打印了日志,我们可以看到当我们点击 Bind按钮的时候 服务端 方法的一个执行顺序。
![点击查看原始大小图片](http://dl.iteye.com/upload/attachment/473842/be2a3b07-4fdf-3f22-88c0-5232434f5178.jpg)