二、IPC机制续(IPC方式)

来源:互联网 发布:唐诗逸和张傲月 知乎 编辑:程序博客网 时间:2024/05/02 00:06

IPC机制

具体方式有很多,比如可以在Intent中附加Extra来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,Content Provider天生就是支持跨进程访问的,因此,我们也可以使用它来进行IPC,另外通过网络通信也是可以实现数据传递的,所以Socket也可以实现IPC。

1.使用Bundle

由于Bundle实现了Parcelable接口,所以它可以方便地在不同进程间传输。

除了直接传递数据这种典型的使用场景,他还有一种特殊的使用场景,如A进程正在进行计算,计算完成之后需要把结果传递给B进程,但是这个结果不支持放入Bundle中,那么可以这样考虑,A中,通过Intent启动B进程的一个Service组件(如IntentService),让Service进行后台计算,计算完毕之后,再启动B进程中真正想要启动的组件由于Service也在B进程中,所以目标组件就可以直接获取结果。

        findViewById(R.id. button).setOnClickListener( new OnClickListener() {            @Override            public void onClick(View v) {                Intent intent = new Intent();                intent.setClass(MainActivity. this, SecondActivity.class);                User user = new User(0, "jake", true);                user. book = new Book();                intent.putExtra( "extra_user", (Serializable) user);                startActivity( intent);            }        });

2.使用文件共享

两个进程通过读写同一个文化夹来交换数据,比如A进程把数据写进文件,B进程通过读取这个文件来获取数据。Linux使得并发读写文件可以没有限制,甚至两个线程同时对一个文件进行读写都是运行的。

希望在ManActivity中的onResume中序列化一个User对象到SDk卡上面的一个文件里面,在SecondActivity的onResume中去反序列化。

MainActivity:onResume执行下面的方法      private void persistToFile() {             new Thread( new Runnable() {                   @Override                   public void run() {                        User user = new User(1, "hello world", false);                        File dir = new File(MyConstants. CHAPTER_2_PATH);                         if (! dir.exists()) {                               dir.mkdirs();                        }                        File cachedFile = new File(MyConstants. CACHE_FILE_PATH );                        ObjectOutputStream objectOutputStream = null;                         try {                               objectOutputStream = new ObjectOutputStream(                                           new FileOutputStream(cachedFile));                               objectOutputStream.writeObject( user);                              Log. d(TAG, "persist user:" + user);                        } catch (IOException e) {                               e.printStackTrace();                        } finally {                              MyUtils. close(objectOutputStream);                        }                  }            }).start();      }
SecondActivity中取:    private void recoverFromFile() {        new Thread(new Runnable() {            @Override            public void run() {                User user = null;                File cachedFile = new File(MyConstants. CACHE_FILE_PATH);                if ( cachedFile.exists()) {                    ObjectInputStream objectInputStream = null;                    try {                        objectInputStream = new ObjectInputStream(                                new FileInputStream( cachedFile));                        user = (User) objectInputStream.readObject();                        Log. d(TAG, "recover user:" + user);                    } catch (IOException e) {                        e.printStackTrace();                    } catch (ClassNotFoundException e) {                        e.printStackTrace();                    } finally {                        MyUtils. close(objectInputStream);                    }                }            }        }).start();    }

当然这种不支持并发,如果想要并发,需要使用线程同步机制来解决。SharePreferences是个特例,通过键值对来存储数据,底层采用xml来存储键值对,位置在/data/data/packagename/shared_prefs目录下面,从本质来说SharePreferences也属于文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存里面有一份SharePreferences文件的缓存,因此在多进程模式下,系统对他的读写变得不可靠,当面对高并发的读写访问就有很大几率丢失数据,因此不建议进程间通信使用SP。

3.使用Messenger

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。从构造方法可以很明显的看出AIDL的痕迹。

public Messenger(Handler target) {     mTarget = target.getIMessenger();}public Messenger(IBinder target) {     mTarget = IMessenger.Stub.asInterface(target);}

Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简单地进行线程间通信,同时由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这个是因为服务端不存在并发执行的情况。

步骤:
1.服务端进程,首先我们要创建一个Service来处理客户端的请求,同时创建一个Handler并通过它来创建一个Messenger对象,在Service的onBind里面返回这个Messenger对象底层的Binder即可。
2.客户端进程,首先要绑定服务端的Service,绑定成功之后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了发送消息类型为Message对象。

如果要服务端能够回应客户端,就和服务端一样,需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。

服务端:public class MessengerService extends Service {    private static final String TAG = "MessengerService";    private static class MessengerHandler extends Handler {        @Override        public void handleMessage(Message msg) {            switch ( msg. what) {            case MyConstants. MSG_FROM_CLIENT:                Log. i(TAG, "receive msg from Client:" + msg.getData().getString( "msg"));                Messenger client = msg. replyTo;                Message relpyMessage = Message. obtain(null, MyConstants.MSG_FROM_SERVICE );                Bundle bundle = new Bundle();                bundle.putString( "reply", "嗯,你的消息我已经收到,稍后会回复你。" );                relpyMessage.setData( bundle);                try {                    client. send(relpyMessage);                } catch (RemoteException e) {                    e.printStackTrace();                }                break;            default:                super.handleMessage( msg);            }        }    }    private final Messenger mMessenger = new Messenger( new MessengerHandler());    @Override    public IBinder onBind(Intent intent) {        return mMessenger.getBinder();    }    @Override    public void onCreate() {        super.onCreate();    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        return super.onStartCommand( intent, flags, startId);    }}
客户端:public class MessengerActivity extends Activity {    private static final String TAG = "MessengerActivity";    private Messenger mService;    private Messenger mGetReplyMessenger = new Messenger( new MessengerHandler());    private static class MessengerHandler extends Handler {        @Override        public void handleMessage(Message msg) {            switch ( msg. what) {            case MyConstants. MSG_FROM_SERVICE:                Log. i(TAG, "receive msg from Service:" + msg.getData().getString( "reply"));                break;            default:                super.handleMessage( msg);            }        }    }    private ServiceConnection mConnection = new ServiceConnection() {        public void onServiceConnected(ComponentName className, IBinder service) {            mService = new Messenger( service);            Log. d(TAG, "bind service");            Message msg = Message. obtain(null, MyConstants.MSG_FROM_CLIENT );            Bundle data = new Bundle();            data.putString( "msg", "hello, this is client.");            msg.setData( data);            msg. replyTo = mGetReplyMessenger;            try {                mService.send( msg);            } catch (RemoteException e) {                e.printStackTrace();            }        }        public void onServiceDisconnected(ComponentName className) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate( savedInstanceState);        setContentView(R.layout. activity_messenger);        Intent intent = new Intent( "com.ryg.MessengerService.launch");        bindService( intent, mConnection, Context. BIND_AUTO_CREATE);    }    @Override    protected void onDestroy() {        unbindService( mConnection);        super.onDestroy();    }}
Mainfest里面:        <activity            android:name= ".messenger.MessengerActivity"            android:label= "@string/title_activity_messenger" >            <intent-filter >                <action android:name ="android.intent.action.MAIN" />            </intent-filter >        </activity >        <service            android:name= ".messenger.MessengerService"            android:process= ":remote" >            <intent-filter >                <action android:name ="com.ryg.MessengerService.launch" />            </intent-filter >        </service >

注意:
通过Messenger来传递Message,Message中能用的载体只有what,arg1,arg2,Bundle以及replyTo。Message中的另外一个字段object在同一个进程中是很实用的,但是在跨进程间通信的时候,在Android2.2以前object字段不支持跨进程传输,
即使2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的。

Messenger的工作原理图:
Messenger的工作原理

4.使用AIDL

由于Messenger的主要作用还是传递消息,有时候可能需要跨进程调用服务端的方法,那么Messenger就不行了。
使用AIDL进行跨进程通信也分为客户端和服务端两个方面:
(1)服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口咋这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

(2)客户端
首先绑定服务端的Service,绑定成功之后,将服务端返回的Binder对象转换成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

(3)AIDL接口的创建

package com.ryg.chapter_2.aidl;import com.ryg.chapter_2.aidl.Book;interface IBookManager {     List<Book> getBookList();     void addBook(in Book book);}

AIDL文件支持的数据类型:
1)基本数据类型(int,long,char,boolean,double等)
2)String和CharSquence
3)List,只支持ArrayList,里面的每个元素都要必须能被AIDL支持
4)Map,只支持HashMap,里面的每个元素都必须被AIDL支持,
包括Key和Value
5)Parcelable,所有实现了Parcelable接口的对象
6)AIDL,所有的AIDL接口本身也可以在AIDL文件中使用
以上的6种数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管是否和当前的AIDL文件位于同一个包里面。
另外,如果AIDL文件里面用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在里面声明它为Parcelable类型。
在IBookManager.aidl文件中使用到了自定义的Book对象,所以必须创建Book.aidl在里面添加:

package com.ryg.chapter_2.aidl;parcelable Book;

AIDL中每个实现了Parcelable接口的类型的类,都需要像上面那样去声明,创建对应的AIDL文件,并声明那个类为parcelable。除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向,in,out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。AIDL接口中只支持方法,不支持静态常量。

in,out,inout区别:
http://hold-on.iteye.com/blog/2026138
inout区别

inout区别

为了方便开发,建议把所有的和AIDL相关的类和文件全部放入同一个包中,这样的好处是,当客户端是另外的应用时,我们可以直接把整个包复制放入到客户端工程中。
AIDL包结构,在客户端和服务端要一致,否则会运行出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类。

(4)远程服务端的Service实现

public class BookManagerService extends Service {    private static final String TAG = "BMS";    private CopyOnWriteArrayList<Book> mBookList =     new CopyOnWriteArrayList<Book>();    private Binder mBinder = new IBookManager.Stub() {        @Override        public List<Book> getBookList() throws RemoteException {            return mBookList;        }        @Override        public void addBook(Book book) throws RemoteException {            mBookList.add( book);        }    };    @Override    public void onCreate() {        super.onCreate();        mBookList.add(new Book(1, "Android"));        mBookList.add(new Book(2, "Ios"));    }    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }}
        <service            android:name= ".aidl.BookManagerService"            android:process= ":remote" >        </service >

采用CopyOnWriteArrayList,它支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的问题,所以要在AIDL方法中处理线程同步,这里使用它来进行自动的线程同步。

服务端可以使用CopyOnWriteArrayList和ConcurrentHashMap来进行自动线程同步,客户端拿到的依然是ArrayList和HashMap。

(5)客户端的实现
客户端首先绑定远程服务,绑定成功之后将服务端返回的Binder对象转换成为AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。

public class BookManagerActivity extends Activity {    private static final String TAG = "BookManagerActivity";    private ServiceConnection mConnection = new ServiceConnection() {        public void onServiceConnected(ComponentName className, IBinder service) {            IBookManager bookManager = IBookManager.Stub.asInterface(service);            try {                List<Book> list = bookManager.getBookList();                Log. i(TAG, "query book list, list type:"                        + list.getClass().getCanonicalName());                Log. i(TAG, "query book list:" + list.toString());            } catch (RemoteException e) {                e.printStackTrace();            }        }        public void onServiceDisconnected(ComponentName className) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate( savedInstanceState);        setContentView(R.layout. activity_book_manager);              Intent intent = new Intent( this, BookManagerService. class);        bindService( intent, mConnection, Context. BIND_AUTO_CREATE);    }    @Override    protected void onDestroy() {        unbindService( mConnection);        super.onDestroy();    }}

(6)监听
比如现在想服务端有新书的时候通知客户端,那么必须要监听了需要使用RemoteCallbackList,存储我们自定义的监听器,它是一个泛型,支持管理任意的AIDL接口。它的内部是Map结构,key是IBinder类型,value是Callback类型。

注意:
服务端和客户端之间做监听器,服务端需要使用RemoteCallbackList,否则客户端的监听器无法收到通知(因为服务端实质还是一份新的序列化后的监听器实例,并不是客户端那份)

RemoteCallbackList的beginBroadcast和finishBroadcast必须配对使用,哪怕我们仅仅需要获取RemoteCallbackList中的元素个数。

(7)不要在客户端的ui线程里面调用服务端的耗时方法
客户端调用远程服务方法时,因为远程方法运行在服务端的binder线程池中(服务端方法可以执行大量耗时操作,不需要开线程执行异步任务的);
同时客户端线程会被挂起,所以如果该方法过于耗时,而客户端又是UI线程,会导致ANR,所以当确认该远程方法是耗时操作时,应避免客户端在UI线程中调用该方法。
同理,当服务器调用客户端的listener方法时,该方法也运行在客户端的binder线程池中,所以如果该方法也是耗时操作,请确认运行在服务端的非UI线程中。
另外,因为客户端的回调listener运行在binder线程池中,所以更新UI需要用到handler。

(8)服务端进程意外终止
客户端通过IBinder.DeathRecipient来监听Binder死亡,也可以在onServiceDisconnected中监听并重连服务端。区别在于前者是在binder线程池中,访问UI需要用Handler,后者则是UI线程。

(9)权限验证
可通过自定义权限在onBind或者onTransact中进行权限验证。

onBind中验证,验证不通过返回null,验证方式可以使用permission验证,首先在manifest里面注册。

<permission        android:name= "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"        android:protectionLevel= "normal" />

就可以在onBind里面验证了。

      @Override      public IBinder onBind(Intent intent) {             int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" );            Log. d(TAG, "onbind check=" + check);             if ( check == PackageManager. PERMISSION_DENIED) {                   return null;            }             return mBinder;      }

一个应用来绑定我们的服务的时候,会验证这个应用的权限,没有权限就返回null。这个方法同样适用于Messenger中。

我们自己内部的应用想要绑定我们的服务,只需要在Manifest采用如下方式使用permission即可。

<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />

服务端的onTransact方法中进行权限验证,验证失败就会返回false,服务端的方法就不会执行,验证方式可以采用permission验证,也可以使用Uid和Pid来验证。通过getCallingUid和getCallingPid可以拿到客户端所属的应用的Uid和Pid,通过这两个参数可以做一些验证工作,比如验证包名。

下面即验证了权限又需要包名以com.rgy开始。

             public boolean onTransact( int code, Parcel data, Parcel reply, int flags)                         throws RemoteException {                   int check = checkCallingOrSelfPermission( "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" );                  Log. d(TAG, "check=" + check);                   if ( check == PackageManager. PERMISSION_DENIED) {                         return false;                  }                  String packageName = null;                  String[] packages = getPackageManager().getPackagesForUid(                               getCallingUid());                   if ( packages != null && packages. length > 0) {                         packageName = packages[0];                  }                  Log. d(TAG, "onTransact: " + packageName);                   if (! packageName.startsWith( "com.ryg")) {                         return false;                  }                   return super.onTransact( code, data, reply, flags);            }

5.使用ContentProvider

底层实现是AIDL,使用比AIDL简单许多。自定义ContentProvider需要继承ContentProvider并实现里面的方法即可,onCreate,query,update,insert,delete,getType,getType用来返回Uri请求所对应的MimeType。如果应用不关系这个,只需要返回null或者”/“,根据Binder,我们知道这6个方法均运行在ContentProvider的进程里面,除了onCreate方法由系统回调并运行在主线程里面,其余的5个均由外界调用并运行在Binder线程池中。

虽然ContentProvider的底层数据看起来很像一个SQLite数据库,但是它对底层的数据的存储方式没有任何要求,我们即可以使用SQLite,也可以使用普通文件,甚至可以采用内存中的一个对象来进行数据的存储。

注册android:authorities是它的唯一标识,建议命名的时候加上包名前缀,如果声明了权限,那么外界应用也需要相应的权限。

ContentProvider(有的手机上会出现不加uses-permission依然可以访问BookProvider的问题)

6.使用Socket
Socket 一般用于网络通信,AIDL用这种方式会过于繁琐,不建议。

7.Binder连接池
比如100个地方需要用到AIDL那么不可能创建100个Service,需要减少Service的数量,将AIDL放在同一个Service里面去管理。

Binder连接池的作用就是将每个业务模块的Binder请求统一转发到远程的Service中去。

Binder连接池

Binder连接池,通过BinderPool的方式将Binder的控制与Service本身解耦,同时只需要维护一份Service即可。这里用到了CountDownLatch,大概解释下用意:线程在await后等待,直到CountDownLatch的计数为0,BinderPool里使用它的目的是为了保证Activity获取BinderPool的时候Service已确定bind完成~

例子:
两个AIDL:

ISecurityCenter.aidlpackage com.ryg.chapter_2.binderpool;interface ISecurityCenter {    String encrypt(String content);    String decrypt(String password);}
ICompute.aidlpackage com.ryg.chapter_2.binderpool;interface ICompute {    int add( int a, int b);}

实现:

public class SecurityCenterImpl extends ISecurityCenter.Stub {    private static final char SECRET_CODE = '^';    @Override    public String encrypt(String content) throws RemoteException {        char[] chars = content.toCharArray();        for (int i = 0; i < chars.length; i++) {            chars[ i] ^= SECRET_CODE;        }        return new String(chars);    }    @Override    public String decrypt (String password ) throws RemoteException {        return encrypt( password);    }}

以及:

public class ComputeImpl extends ICompute.Stub {    @Override    public int add(int a, int b) throws RemoteException {        return a + b;    }}

为Binder连接池创建AIDL接口IBinderPool.aidl

interface IBinderPool {    /**     * @param binderCode, the unique token of specific Binder<br/>     * @return specific Binder who's token is binderCode.     */    IBinder queryBinder( int binderCode);}

为Binder连接池创建远程Service并实现IBinderPool,
下面是queryBinder的实现:

        @Override        public IBinder queryBinder( int binderCode) throws RemoteException {            IBinder binder = null;            switch ( binderCode) {            case BINDER_SECURITY_CENTER: {                binder = new SecurityCenterImpl();                break;            }            case BINDER_COMPUTE: {                binder = new ComputeImpl();                break;            }            default:                break;            }            return binder;        }

远程Service的实现比较简单了:

public class BinderPoolService extends Service {    private static final String TAG = "BinderPoolService";    private Binder mBinderPool = new BinderPool.BinderPoolImpl();    @Override    public void onCreate() {        super.onCreate();    }    @Override    public IBinder onBind(Intent intent ) {        Log.d(TAG, "onBind");        return mBinderPool;    }    @Override    public void onDestroy() {        super.onDestroy();    }}

下面还有Binder连接池的具体实现,首先绑定远程服务,成功之后,客户端就可以通过它的queryBinder方法获取各自对应的Binder,拿到所需要的Binder之后,不同的业务模块之间就可以进行各自的操作了。

public class BinderPool {    private static final String TAG = "BinderPool";    public static final int BINDER_NONE = -1;    public static final int BINDER_COMPUTE = 0;    public static final int BINDER_SECURITY_CENTER = 1;    private Context mContext;    private IBinderPool mBinderPool;    private static volatile BinderPool sInstance;    private CountDownLatch mConnectBinderPoolCountDownLatch;    private BinderPool(Context context) {        mContext = context.getApplicationContext();        connectBinderPoolService();    }    public static BinderPool getInsance(Context context) {        if (sInstance == null) {            synchronized (BinderPool.class) {                if (sInstance == null) {                    sInstance = new BinderPool(context);                }            }        }        return sInstance;    }    private synchronized void connectBinderPoolService() {        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);        Intent service = new Intent(mContext, BinderPoolService.class);        mContext.bindService(service, mBinderPoolConnection,                Context.BIND_AUTO_CREATE);        try {            mConnectBinderPoolCountDownLatch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }    }    /**     * query binder by binderCode from binder pool     *      * @param binderCode     *            the unique token of binder     * @return binder who's token is binderCode<br>     *         return null when not found or BinderPoolService died.     */    public IBinder queryBinder(int binderCode) {        IBinder binder = null;        try {            if (mBinderPool != null) {                binder = mBinderPool.queryBinder(binderCode);            }        } catch (RemoteException e) {            e.printStackTrace();        }        return binder;    }    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {        @Override        public void onServiceDisconnected(ComponentName name) {            // ignored.        }        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            mBinderPool = IBinderPool.Stub.asInterface(service);            try {                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);            } catch (RemoteException e) {                e.printStackTrace();            }            mConnectBinderPoolCountDownLatch.countDown();        }    };    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {        @Override        public void binderDied() {            Log.w(TAG, "binder died.");            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);            mBinderPool = null;            connectBinderPoolService();        }    };    public static class BinderPoolImpl extends IBinderPool.Stub {        public BinderPoolImpl() {            super();        }        @Override        public IBinder queryBinder(int binderCode) throws RemoteException {            IBinder binder = null;            switch (binderCode) {            case BINDER_SECURITY_CENTER: {                binder = new SecurityCenterImpl();                break;            }            case BINDER_COMPUTE: {                binder = new ComputeImpl();                break;            }            default:                break;            }            return binder;        }    }}

使用:

public class BinderPoolActivity extends Activity {    private static final String TAG = "BinderPoolActivity";    private ISecurityCenter mSecurityCenter;    private ICompute mCompute;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate( savedInstanceState);        setContentView(R.layout. activity_binder_pool);        new Thread(new Runnable() {            @Override            public void run() {                doWork();            }        }).start();    }    private void doWork() {        BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity. this);        IBinder securityBinder = binderPool                .queryBinder(BinderPool. BINDER_SECURITY_CENTER);        mSecurityCenter = (ISecurityCenter) SecurityCenterImpl                . asInterface(securityBinder);        Log.d(TAG, "visit ISecurityCenter");        String msg = "helloworld-安卓";        System. out.println( "content:" + msg);        try {            String password = mSecurityCenter.encrypt( msg);            System. out.println( "encrypt:" + password);            System. out.println( "decrypt:" + mSecurityCenter.decrypt(password ));        } catch (RemoteException e) {            e.printStackTrace();        }        Log.d(TAG, "visit ICompute");        IBinder computeBinder = binderPool                .queryBinder(BinderPool. BINDER_COMPUTE);                mCompute = ComputeImpl.asInterface(computeBinder );        try {            System. out.println( "3+5=" + mCompute.add(3, 5));        } catch (RemoteException e) {            e.printStackTrace();        }    }}

8.选择适合的IPC方式

选择方式

0 0
原创粉丝点击