非实时,通知性的方式
第一种方式就是Intent,Intent可以非常方便的通讯,但是它是非实时的,无法进行实时的像函数调用那样的实时的通讯。
实时的函数调用
但是IPC的根本目的还是为了实现函数的调用,即使是传递数据也是要通过函数调用的方式,为什么呢?因为程序运行总是要知道状态,要有逻辑上的行为,因此必须通讯函数才能体现出行为。
IPC的机制除了进程,或者说不同的应用程序之间进行通讯,同时也能够让不同的组件之间进行像普通对象那样进行实时的调用。因为Android的组件都是由系统框架统一的进行构建和销毁,所以你就无法创建对象,因此,就没有办法像普通的对象那样进行组合或者聚合,从而也就没有办法进行直接调用。但是IPC的方式就可以让Activity/Service获取另外一个Service对象的引用,从而直接调用其上的方法。
还有,就是这些IPC方法总是产生客户/服务端模式的调用,也即是客户端组件(Activity/Service)持有服务端Service的组件,只能是客户端主动调用服务端的方法,服务端无法反过来调用客户端的方法,因为IPC的另一端Service无法获取客户端的对象。
文档对此的描述。
有三种方式可以进行IPC通讯:
1. 直接使用Binder对象
缺点是这种方式不能进行跨进程,跨应用程序的函数调用。只能实现在同一个进程之中,同一个应用程序之中的不同的组件之间通讯。
优点就是这种方式使用起来特别简单,对于公开出来的方法没有任何的限制,可以传递任何的对象,甚至是回调等等。
总体上来讲如果不需要跨进程,这是最好的实现方式,可以完全实现像普通对象之间的组合和聚合。但是这里,最好不要像Android文档中的示例那样,直接返回Service对象,因为Service对象会有很多Public的方法,如果直接返回Service对象会导致公开很多不必须的方法,一旦Client端调用这些方法,将导致奇怪的现象和Bug,一个方法就是用Proxy对Service对象进行封装,只公开需要的接口。
示例:
service:
- public class BinderPrinterService extends Service {
- private static final String TAG = "PlayerService";
- private IBinder mBinder;
-
- @Override
- public void onCreate() {
- mBinder = new ProxyService(this);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- public void print(String msg, TextView tv) {
- try {
- Log.e(TAG, "Preparing printer...");
- tv.setText("Preparing printer...");
- Thread.sleep(1000);
- Log.e(TAG, "Connecting printer...");
- tv.setText("Connecting printer...");
- Thread.sleep(1000);
- Log.e(TAG, "Printing.... " + msg);
- tv.setText("Printing.... ");
- Thread.sleep(1000);
- Log.e(TAG, "Done");
- } catch (InterruptedException e) {
- }
- tv.setText(msg);
- Toast.makeText(this, "Printing is done.", Toast.LENGTH_SHORT).show();
- }
- }
-
- class ProxyService extends Binder {
- private BinderPrinterService mService;
-
- public ProxyService(BinderPrinterService svc) {
- mService = svc;
- }
-
- public void print(String msg, TextView tv) {
- mService.print(msg, tv);
- }
- }
client:
- public class BinderClientActivity extends Activity {
- ProxyService mService;
- private TextView mStatusPanel;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.printer_activity);
- setTitle("Binder client Activity");
- mStatusPanel = (TextView) findViewById(R.id.status);
- ((Button) findViewById(R.id.play)).setText("Print via extending Binder");
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- doBindService();
- }
-
- private void doBindService() {
- Intent intent = new Intent(this, BinderPrinterService.class);
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- doUnbindService();
- }
-
- private void doUnbindService() {
- if (mService != null) {
- unbindService(mConnection);
- }
- }
-
- public void onButtonClick(View v) {
- if (mService == null) {
- return;
- }
- mService.print("Tree of liberty must be refreshed from time to time with blood of patroits and tyrants",
- mStatusPanel);
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = (ProxyService) service;
- }
-
- @Override
- public void onServiceDisconnected(ComponentName arg0) {
- mService = null;
- }
- };
- }
2. 使用Messenger对象
这是利用了消息循环队列来进行通讯的,也就是说服务端封装成了一个Handler,客户端向这个Handler发送Message,服务端再处理这个Message从而实现通讯。
优点,最突出的优点就是它可以保证线程上的安全,或者说时序上的安全。因为客户端是向Handler发送消息,而不是直接的函数调用,所以呢?Message都是按先后的顺序放到服务端的消息队列当中,然后再由服务端Service决定如何处理这些Message。因为直接的函数调用会导致被调用的函数也会出现在调用者的线程之中,也就是说被调用到的函数也会继续存在于调用者的调用栈中,因此有可能产生线程的问题。而Messenger方式,并不是直接的函数调用,而是仅向Service的Handler发送一个Message,然后调用者就此返回,其调用栈也就此停止,Service可以选择如何处理这一个Message。Messenger方式即可以在同一个进程之中,也可以跨进程实现真正的IPC。
但是它的缺点也是相当的明显的,就是它是发送一个Message对象,而不是直接的函数调用,所以非常的不直观,另外,Message对象也无法方便的携带过多的参数,如果超过一个对象,只能封装打包成一个对象然后再放到Message.obj中。需要注意的是,如果是在同一个进程之中,Message可以携带任何对象,但如果是跨进程,则Message.obj中存放的必须是实现了Parcelable接口的对象,否则无法实现IPC,会有Exception。还有一个缺点,就是Message对象的标识(Message.what)必须是Client端和Service端都定义一致,否则就无法通讯,这在调试的时候必须注意,因为这不会有编译或者运行时错误,但就是无法正常工作,是比较隐蔽的Bug。
示例:
Service:
- public class MessengerPrinterService extends Service {
- static final int MSG_PRINT = 1;
-
- private static final String TAG = "PrinterService";
-
- private Handler mServiceHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_PRINT:
- print("Freedom is nothing but a chance to be better!", (TextView) msg.obj);
- break;
- default:
- super.handleMessage(msg);
- }
- }
- };
-
- final Messenger mMessenger = new Messenger(mServiceHandler);
-
- @Override
- public IBinder onBind(Intent intent) {
- return mMessenger.getBinder();
- }
-
- public void print(String msg, TextView tv) {
- try {
- Log.e(TAG, "Preparing printer...");
- if (tv != null) {
- tv.setText("Preparing printer...");
- }
- Thread.sleep(1000);
- Log.e(TAG, "Connecting printer...");
- if (tv != null) {
- tv.setText("Connecting printer...");
- }
- Thread.sleep(1000);
- Log.e(TAG, "Printing.... " + msg);
- if (tv != null) {
- tv.setText("Printing.... ");
- }
- Thread.sleep(1000);
- Log.e(TAG, "Done");
- } catch (InterruptedException e) {
- }
- if (tv != null ) {
- tv.setText(msg);
- }
- Toast.makeText(this, "Messenger Printing is done.", Toast.LENGTH_LONG).show();
- }
- }
Local client(in the same application):
- public class MessengerClientActivity extends Activity {
- Messenger mService = null;
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = new Messenger(service);
- }
-
- public void onServiceDisconnected(ComponentName className) {
- mService = null;
- }
- };
-
- public void onButtonClick(View v) {
- if (mService == null) return;
-
- Message msg = Message.obtain(null, MessengerPrinterService.MSG_PRINT, 0, 0);
- msg.obj = findViewById(R.id.status);
- try {
- mService.send(msg);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.printer_activity);
- setTitle("Messenger client Activity");
- ((Button) findViewById(R.id.play)).setText("Print via constructing Messenger");
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- bindService(new Intent(this, MessengerPrinterService.class), mConnection,
- Context.BIND_AUTO_CREATE);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (mService != null) {
- unbindService(mConnection);
- }
- }
- }
远端的Client(在另外一个应用程序进程里面):
- public class AnotherMessengerClientActivity extends Activity {
- private static final int MSG_PRINT = 1;
-
- Messenger mService = null;
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = new Messenger(service);
- }
-
- public void onServiceDisconnected(ComponentName className) {
- mService = null;
- }
- };
-
- public void onButtonClick(View v) {
- if (mService == null) return;
-
- Message msg = Message.obtain(null, MSG_PRINT, 0, 0);
- try {
- mService.send(msg);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.printer_activity);
- setTitle("Another Messenger client Activity");
- ((Button) findViewById(R.id.play)).setText("Print via constructing Messenger");
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- Intent intent = new Intent();
- intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MessengerPrinterService");
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (mService != null) {
- unbindService(mConnection);
- }
- }
- }
3. 使用AIDL进程通讯
这是最正统的IPC方式,实际上Messenger的跨进程通讯的底层也是通过AIDL来实现的。它的优点就是完全是为跨进程而设计的,接口由AIDL文件指定,Client端和Service端都通过AIDL所描述的接口来进行调用和实现,不容易出错。需要注意的是接口中的所有的对象都必须实现了Parcelable接口,即使仅仅是在同一个进程之中。它的缺点就是必须要在每一个想要通讯的地方定义一个完全一样的AIDL文件,否则IPC不会成功,而且它是严格的按照IPC的机制来的,所以即使是在同一进程之内,所有的接口的参数和返回值的对象必须是实现了Parcelable接口的,但Binder对象和Messenger就无此限制。需要一点:定义AIDL会严格按照IPC的方式进程即使是在同一个进程之中。所以,除非是真的要跨进程通讯,否则不要使用AIDL。
AIDL文件:
- package com.example.effectiveandroid;
-
- interface PrinterInterface {
- void print(String msg);
- }
AIDL Service:
- public class AIDLPrinterService extends Service {
- private static final String TAG = "AIDLPrinterService";
- private Handler mHandler = new Handler();
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- private PrinterInterface.Stub mBinder = new PrinterInterface.Stub() {
- @Override
- public void print(String msg) throws RemoteException {
- AIDLPrinterService.this.print(msg);
- }
- };
-
- public void print(String msg) {
- try {
- Log.e(TAG, "Preparing printer...");
- Thread.sleep(1000);
- Log.e(TAG, "Connecting printer...");
- Thread.sleep(1000);
- Log.e(TAG, "Printing.... " + msg);
- Thread.sleep(1000);
- Log.e(TAG, "Done");
- } catch (InterruptedException e) {
- }
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(AIDLPrinterService.this, "via AIDL Printing is done.", Toast.LENGTH_LONG).show();
- }
- });
- }
- }
Local client:
- public class AIDLClientActivity extends Activity {
- private static final String TAG = "PrinterClientActivity";
- PrinterInterface mService;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.printer_activity);
- setTitle("Local AIDL client Activity");
- ((Button) findViewById(R.id.play)).setText("Print via AIDL");
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- doBindService();
- }
-
- private void doBindService() {
- Intent intent = new Intent(this, AIDLPrinterService.class);
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- doUnbindService();
- }
-
- private void doUnbindService() {
- if (mService != null) {
- unbindService(mConnection);
- }
- }
-
- public void onButtonClick(View v) {
- if (mService == null) {
- Log.e(TAG, "what the fucl service is not ready");
- return;
- }
- try {
- mService.print("This message is from local client via AIDL interface");
- } catch (RemoteException e) {
-
- e.printStackTrace();
- }
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = PrinterInterface.Stub.asInterface(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName arg0) {
- mService = null;
- }
- };
- }
client in another application process:
- public class AnotherAIDLClientActivity extends Activity {
- private static final String TAG = "PrinterClientActivity";
- PrinterInterface mService;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.printer_activity);
- setTitle("Another AIDL client Activity");
- ((Button) findViewById(R.id.play)).setText("Print via AIDL");
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- doBindService();
- }
-
- private void doBindService() {
- Intent intent = new Intent();
- intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.AIDLPrinterService");
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- doUnbindService();
- }
-
- private void doUnbindService() {
- if (mService != null) {
- unbindService(mConnection);
- }
- }
-
- public void onButtonClick(View v) {
- if (mService == null) {
- Log.e(TAG, "what the fucl service is not ready");
- return;
- }
- try {
- mService.print("call PrinterService via AIDL from another application");
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = PrinterInterface.Stub.asInterface(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName arg0) {
- mService = null;
- }
- };
- }
结论
对比且总结了一下:如果是在同一个进程(应用程序)之中,且不涉及复杂的线程模式,直接使用Binder对象方式是最方便快捷的;如果是涉及跨进程操作,且不涉及复杂的线程模式就使用AIDL方式;无论是同一进程内还是跨进程,如果涉及比较复杂的线程模式就推荐使用Messenger的方式。