最近忙于一个项目,涉及到Android平台下的进程间通信(IPC)问题,由于项目的
架构问题,遇到一系列数据的传送,
同步问题,让人蛋疼`~~~~~~~~~~,浅谈一下心得····
整个系统的架构如下:
所要做的项目即图中的中间件,它是手机上的第三方应用和服务器间的桥梁,为第三方应用提供相关服务
接口。
整个流程即:第三方应用调用接口中的方法,通知中间件做某些操作,然后中间件把数据发送到服务器或从服务器接受到数据返回给应用(比如,应用需要注册到服务器,首先调用接口中的方法通知中间件,中间件做一些逻辑操作(如:判断是否已注册),中间件最后把应用注册到服务器,并把注册结果返回给应用)
接口的设计:做成一个jar包,放到第三方应用中,供其调用其中的方法。
由于第三方应用和中间件都是安装在手机上的应用程序,在android平台下为两个独立的进程(数据都是私有的),
他们之间进行数据的交互就要用到android平台下进程间的通信(IPC)。
在Windows Mobile平台下使用共享
内存实现进程间数据的共享很方便,在Android平台下却很麻烦(也没尝试去做)
3种方法实现Android平台下的进程间通信(即上面所说的项目中接口与中间件间的数据交互(接口放在第三方应用中的))
1.说到android平台下的进程间通信,首先会想到AIDL(Android Interface Definition Language)
AIDL是基于
Service组件的,实现起来也很简单,大概分为以下几步:
a.在中间件中创建一个aidl文件,把接口中的方法写到xxx.aidl文件中,此时ADT插件会自动生成一个与aidl
文件名相同的类,并这个类中有一个内部类Stub
b.在中间件中创建一个Remote Service(即一个service组件),在其onBind方法中返回一个Stub对象,而这个Stub对象中实现了aidl文件中的方法
c.把接口发布给应用,在第三方应用中也创建这样一个相同的aidl文件,在客户端通过调用bindService方法,在调用的时候传入一个ServiceConnect对象,当bind到Remote Service的时候,系统会回调ServiceConnect中的onServiceConnected方法,在此方法中获得RemoteService返回的Stub对象(IBinder对象),把此对象转换成aidl文件所对应的类,即可调用中间件中的方法,AIDL实现的步骤和流程基本上是这样,但还要注意许多细节,请看Android SDK
官方文档,写的很详细。Dev Guid-->Developing-->Tools-->aidl
但是,此时有个同步的问题,即第三方应用调用接口中的注册方法appRegist(int appId)(假如是在onClick方法中调用的),接口中的操作就是bindService,然后系统会回调onServiceConnected方法,在此方法被回调完成后才能获得中间件返回的操作结果(注册是否成功),但是在UI
线程中调用appRegist(int appId)方法和系统回调onServiceConnected方法不是同步执行的,即onServiceConnected方法还未回调完成,appRegist方法已经返回,所以应用获得的数据时
错误的,但是由于是UI线程又不能阻塞去等待onServiceConnected方法的完成,所以这是个很麻烦的问题。。。
2.第二种方法使用Messenge+Message,即接口和中间件各自维护一个消息
队列,并互发消息来实现的,也是基于Service和bindService方法,具体实现看Android SDK官方文档,写的很详细,如下:
Remote Messenger Service SampleIf you need to be able to write a Service that
can perform complicated communication with clients in remote processes (beyond simply the use of Context.startServiceto send
commands to it), then you can use the Messenger class instead of writing full AIDL
files.An example of a Service that uses Messenger as its client interface is shown here. First is the Service itself, publishing a Messenger to an internal Handler when bound:public class MessengerService extends Service { /** For showing and hiding our notification. */ NotificationManager mNM; /** Keeps track of all current registered clients. */ ArrayList<Messenger> mClients = new ArrayList<Messenger>(); /** Holds last value set by a client. */ int mValue = 0; /** * Command to the service to register a client, receiving callbacks * from the service. The Message's replyTo field must be a Messenger of * the client where callbacks should be sent. */ static final int MSG_REGISTER_CLIENT = 1; /** * Command to the service to unregister a client, ot stop receiving callbacks * from the service. The Message's replyTo field must be a Messenger of * the client as previously given with MSG_REGISTER_CLIENT. */ static final int MSG_UNREGISTER_CLIENT = 2; /** * Command to service to set a new value. This can be sent to the * service to supply a new value, and will be sent by the service to * any registered clients with the new value. */ static final int MSG_SET_VALUE = 3; /** * Handler of incoming messages from clients. */ class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_REGISTER_CLIENT: mClients.add(msg.replyTo);
break; case MSG_UNREGISTER_CLIENT: mClients.remove(msg.replyTo); break; case MSG_SET_VALUE: mValue = msg.arg1; for (int i=mClients.size()-1; i>=0; i--) { try { mClients.get(i).send(Message.obtain(null, MSG_SET_VALUE, mValue, 0)); } catch (RemoteException e) { // The client is dead. Remove it from the list; // we are going through the list from back to front // so this is safe to do inside the loop. mClients.remove(i); } } break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ final Messenger mMessenger = new Messenger(new IncomingHandler()); @Override public void onCreate() { mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // Display a notification about us starting. showNotification(); } @Override public void onDestroy() { // Cancel the persistent notification. mNM.cancel(R.string.remote_service_started); // Tell the user we stopped. Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show(); } /** * When binding to the service, we return an interface to our
messenger * for sending messages to the service. */ @Override public IBinder onBind(
Intent intent) { return mMessenger.getBinder(); } /** * Show a notification while this service is running. */ private void showNotification() { // In this sample, we'll use the same text for the ticker and the expanded notification CharSequence text = getText(R.string.remote_service_started); // Set the icon, scrolling text and timestamp Notification notification = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis()); // The PendingIntent to launch our activity if the user selects this notification PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Controller.class), 0); // Set the info for the views that show in the notification panel. notification.setLatestEventInfo(this, getText(R.string.remote_service_label), text, contentIntent); // Send the notification. // We use a string id because it is a unique number. We use it later to cancel. mNM.notify(R.string.remote_service_started, notification); }}If we want to make this service run in a remote process (instead of the standard one for its .apk), we can use android:process in its manifest tag to specify one:<service android:name=".app.MessengerService" android:process=":remote" />Note that the name "remote" chosen here is arbitrary, and you can use other names if you want additional processes. The ':' prefix appends the name to your package's standard process name.With that done, clients can now bind to the service and send messages to it. Note that this allows clients to register with it to receive messages back as well:/** Messenger for communicating with service. */Messenger mService = null;/** Flag indicating whether we have called bind on the service. */boolean mIsBound;/** Some text view we are using to show state information. */TextView mCallbackText;/** * Handler of incoming messages from service. */class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MessengerService.MSG_SET_VALUE: mCallbackText.setText("Received from service: " + msg.arg1); break; default: super.handleMessage(msg); } }}/** * Target we publish for clients to send messages to IncomingHandler. */final Messenger mMessenger = new Messenger(new IncomingHandler());/** * 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 service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = new Messenger(service); mCallbackText.setText("Attached."); // We want to monitor the service for as long as we are // connected to it. try { Message msg = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); // Give it some value as an example. msg = Message.obtain(null, MessengerService.MSG_SET_VALUE, this.hashCode(), 0); mService.send(msg); } catch (RemoteException e) { // In this case the service has crashed before we could even // do anything with it; we can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } 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; mCallbackText.setText("Disconnected."); // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show(); }};void doBindService() { // Establish a connection with the service. We use an explicit // class name because there is no reason to be able to let other // applications replace our component. bindService(new Intent(Binding.this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; mCallbackText.setText("Binding.");}void doUnbindService() { if (mIsBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { Message msg = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // There is nothing special we need to do if the service // has crashed. } } // Detach our existing connection. unbindService(mConnection); mIsBound = false; mCallbackText.setText("Unbinding."); }}这种实现方式同样存在一个同步的问题,和第一种实现方法中的问题是一样的3.第三种实现方式有点笨重,通过ContentProvider+sqlite数据库实现我们知道在Android平台下可以使用ContentProvider暴露一个应用程序中的数据给其他应用,其他应用通过ContentResolver和URI可以操作该数据。在上面项目中,先在中间件中创建一个数据库,接口可以通过ContentResolver+URI向该数据库中写入数据,然后通过startService或发送广播通知中间件,去做相应的操作,中间件收到命令后再从数据库中取出数据做相应操作。但问题是接口中的方法不会等待中间件操作完成就首先返回了。所以中间件做完操作后还要通知接口(可以使用广播或Message实现),在接口中还要注册一个广播接收器或消息接收器,问题又来了,接口还要返回数据给应用,此时应用的方法调用早已返回。。。总结以上几种实现方式,除非在应用中作一个消息接收器或广播接收器,让接口把最终的操作结果放到消息或广播中发送给应用,但这样就增加了接口跟第三方应用的耦合度,也许是架构师当初没考虑到Android平台下进程间的通信吧,在应用和中间件间增加了一个接口,由于操作的
异步性,最终还是要把操作结果通知到应用,而应用无法主动或不知道什么时候去从接口获得数据,通过多线程和阻塞线程也许会有更好的解决办法,
研究中。。。