上一篇复习了Android生命周期和本地Service的使用,这一篇继续总结一下Android远程Service的使用,远程Service就是在新的进程中开启service,这样会遇到一个问题,就是进程间通信的问题。Android系统的进程之间不能共享内存,那怎么传递对象呢,需要把对象弄成操作系统可以识别的形式,在Android中,可以采用AIDL来公开服务的接口,采用远程过程调用(Remote Procedure Call,RPC)和代理模式来实现跨进程通信。AIDL:Android Interface Definition Language,即Android接口描述语言,ADT会根据aidl文件在gen目录下生成对应的java接口文件。我们需要手工创建一个Service的子类并实现生成的java接口,然后在AndroidManifest.xml文件中进行配置。远程服务可以为多个客户端服务,由于涉及到数据通信,一般采用bindService的方式。
下面我们通过一个demo来看看AIDL是如何实现的。
首先创建服务端Android工程。目录结构如图
代码如下
User.java,为了实现跨进程数据传递,需要实现Parcelable 接口,是一种序列化方式。
public class User implements Parcelable { private int id; private String name; public User() { } public User(Parcel parcel) { this.id = parcel.readInt(); this.name = parcel.readString(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel dest, int flags) { //顺序需与构造函数中read保持一致 dest.writeInt(id); dest.writeString(name); } public static final Parcelable.Creator<User> CREATOR = new Creator<User>() { @Override public User createFromParcel(Parcel source) { return new User(source); } @Override public User[] newArray(int size) { return new User[size]; } }; }
User.adil
parcelable User;
IRemoteService.aidl
/** 远程的服务 IRemoteService.aidl */ interface IRemoteService { //返回基本类型 int getId(); //返回对象 User getUser(); }
RemoteService.java
public class RemoteService extends Service { @Override public void onCreate() { Log.i(this.getClass().getName(), "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(this.getClass().getName(), "onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { Log.i(this.getClass().getName(), "onDestroy"); } @Override public IBinder onBind(Intent intent) { return mRemoteServiceBinder; } @Override public boolean onUnbind(Intent intent) { Log.i(this.getClass().getName(), "onUnbind"); return super.onUnbind(intent); } @Override public void onRebind(Intent intent) { Log.i(this.getClass().getName(), "onRebind"); super.onRebind(intent); } IRemoteService.Stub mRemoteServiceBinder = new IRemoteService.Stub() { @Override public User getUser() throws RemoteException { User user = new User(); user.setId(123456); user.setName("alexzhou"); return user; } @Override public int getId() throws RemoteException { return 123456; } }; }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.alexzhou.aidl.server" android:installLocation="internalOnly" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".RemoteServiceActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- android:process="name" name的值是随便取的 ,android:exported:是否允许被其它程序调用--> <service android:name="com.alexzhou.aidl.server.RemoteService" android:exported="true" android:process=":remote" > <intent-filter> <action android:name="com.alexzhou.service.REMOTE_SERVICE" /> </intent-filter> </service> </application> </manifest>
服务端的Activty是自动生成的。没写任何其他代码,这里就不贴出来了。
接着需要创建一个客户端Android工程,目录结构如下图:
代码如下:
先把User.java,User.aidl,IRemoteService.aidl三个文件复制到客户端,注意包名必须跟服务端所在的包名一致。
创建客户端主界面类ClientActivity.java
public class ClientActivity extends Activity implements OnClickListener{ private TextView callbackView; private Button bindButton; private boolean isBind; private final String REMOTE_SERVICE_ACTION = "com.alexzhou.service.REMOTE_SERVICE"; private IRemoteService mRemoteService; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findViews(); setListeners(); callbackView.setText("no callback"); } private void findViews() { callbackView = (TextView)this.findViewById(R.id.callback); bindButton = (Button)this.findViewById(R.id.bind); } private void setListeners() { bindButton.setOnClickListener(this); } @Override public void onClick(View view) { switch(view.getId()) { case R.id.bind: this.bindService(new Intent(REMOTE_SERVICE_ACTION), mConntectin, Context.BIND_AUTO_CREATE); callbackView.setText("binding..."); break; } } private ServiceConnection mConntectin = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName arg0) { callbackView.setText("Disconnected!"); } @Override public void onServiceConnected(ComponentName name, IBinder binder) { mRemoteService = IRemoteService.Stub.asInterface(binder); isBind = true; try { int id = mRemoteService.getId(); User user = mRemoteService.getUser(); StringBuffer buffer = new StringBuffer(); buffer.append("id:"); buffer.append(id); buffer.append("name"); buffer.append(user.getName()); callbackView.setText(buffer.toString()); } catch (RemoteException e) { e.printStackTrace(); } } }; @Override protected void onDestroy() { if(isBind) { this.unbindService(mConntectin); isBind = false; } super.onDestroy(); } }
布局文件main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/callback" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bind" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/bind_remote_service_text"/> </LinearLayout>
现在运行你的客户端程序,点击绑定远程服务按钮,如果一切顺利,将会看到服务端返回的信息。如图:
通过eclipse控制台Devices视图,可以看到远程服务进程已启动,如图:
可能会遇到的问题和解决办法:
(1)AIDL unable to start service not found
客户端和服务端activty包名相同了,改成不同就可以
(2)Not allowed to bind to service Intent
在服务端配置文件中把android:exported = false 改成 true,android:exported表示是否允许被其它程序调用
(3)Binder invocation to an incorrect interface
客户端aidl文件的包名跟service的包名不一样,改成一样的就ok