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的工作原理圖:
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
爲了方便開發,建議把所有的和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連接池,通過BinderPool的方式將Binder的控制與Service本身解耦,同時只需要維護一份Service即可。這裏用到了CountDownLatch,大概解釋下用意:線程在await後等待,直到CountDownLatch的計數爲0,BinderPool裏使用它的目的是爲了保證Activity獲取BinderPool的時候Service已確定bind完成~
例子:
兩個AIDL:
ISecurityCenter.aidl
package com.ryg.chapter_2.binderpool;
interface ISecurityCenter {
String encrypt(String content);
String decrypt(String password);
}
ICompute.aidl
package 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();
}
}
}