編輯:關於Android編程
這篇文章講的是在不同的工程文件中實現IPC。這次我決定用一個工程完成
首先,我先介紹一下流程
1服務端
先創建Service來監聽客戶端的連接請求,然後創建AIDL文件,將暴露給客戶端的接口在這個aidl文件中聲明,最後在service中實現這個接口
2客戶端
綁定客戶端的service。綁定成功後將服務端返回的binder對象轉成aidl接口所屬的類型,接著就可以調用aidl中的方法
具體步驟
(1)創建AIDL文件,聲明接口
文件名稱IBookManager.aidl。注意無論Book類在哪個包下都要import,package也是必需的。所有參數必須標上in,out,inout
package com.example.aidl.service;
import com.example.aidl.service.Book;
interface IBookManager{
List getBookList();
void addBook(in Book book);
}
另外,如果要用到實體類,必須繼承Parcelable,而且要創建和它同名的aidl文件
Book.java
package com.example.aidl.service;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source.readInt(), source.readString());
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public String toString() {
return "Book [bookId=" + bookId + ", bookName=" + bookName + "]";
}
}
Book.aidl
必須這樣申明。package + parcelable
package com.example.aidl.service; parcelable Book;
(2)創建service實現這個接口(BookManagerService.java)
package com.example.aidl;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import com.example.aidl.service.Book;
public class BookManagerService extends Service {
private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private Binder mBinder = new IBookManager.Stub() {
@Override
public List 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"));
}
}
然後注冊service並且設置為remote
(3)客戶端的實現
綁定service。綁定成功後將服務端返回的binder對象轉成aidl接口所屬的類型,接著就可以調用aidl中的方法
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List list = bookManager.getBookList();
for (int i = 0; i < list.size(); i++) {
Log.e("booklist", list.get(i).toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
效果截圖

同時,我們可以試著調用addBook接口
try {
List list = bookManager.getBookList();
for (int i = 0; i < list.size(); i++) {
Log.e("booklist", list.get(i).toString());
}
bookManager.addBook(new Book(3, "develop"));
list = bookManager.getBookList();
for (int i = 0; i < list.size(); i++) {
Log.e("booklist", list.get(i).toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
效果截圖

現在我們在考慮一種情況,假設當有一本新書的時候直接通知用戶(觀察者模式)
首先要提供一個aidl接口,普通接口無法使用(IOnNewBookArrivedListener.aidl)
package com.example.aidl.service;
import com.example.aidl.service.Book;
interface IOnNewBookArrivedListener{
void onNewBookArrived(in Book book);
}
同時需要在原有接口中添加兩個新方法
package com.example.aidl.service;
import com.example.aidl.service.Book;
import com.example.aidl.service.IOnNewBookArrivedListener;
interface IBookManager{
List getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
這樣一來BookManagerService.java會自動生成兩個新的方法。同時開啟一個線程,每隔5s就向書庫中添加一個本書並通知所有感興趣單位客戶
public class BookManagerService extends Service {
private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList();
private CopyOnWriteArrayList mListenerList = new CopyOnWriteArrayList();
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private Binder mBinder = new IBookManager.Stub() {
@Override
public List getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
if (!mListenerList.contains(listener)) {
mListenerList.add(listener);
}
Log.e("BookManagerService", "registerListener size:"
+ mListenerList.size());
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
if (mListenerList.contains(listener)) {
mListenerList.remove(listener);
}
Log.e("BookManagerService", "unregisterListener size:"
+ mListenerList.size());
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "iOS"));
// 每隔5s通知一次
new Thread(new Runnable() {
@Override
public void run() {
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(5000);
onNewBookArrived(new Book(mBookList.size(), "test"));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}).start();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
for (int i = 0; i < mListenerList.size(); i++) {
IOnNewBookArrivedListener listener = mListenerList.get(i);
listener.onNewBookArrived(book);
}
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestroyed.set(true);
}
}
此外還要修改一下客戶端代碼。注冊aidl接口,activity退出時要解注冊
public class MainActivity extends Activity {
private IBookManager mRemoteBookManager;
private static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x001) {
Log.e("MainActivity", "receive new book :" + msg.obj);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;//這一句不能忘
List list = bookManager.getBookList();
for (int i = 0; i < list.size(); i++) {
Log.e("booklist", list.get(i).toString());
}
bookManager.addBook(new Book(3, "develop"));
list = bookManager.getBookList();
for (int i = 0; i < list.size(); i++) {
Log.e("booklist", list.get(i).toString());
}
bookManager.registerListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
handler.obtainMessage(0x001, book).sendToTarget();
}
};
@Override
protected void onDestroy() {
if (mRemoteBookManager != null
&& mRemoteBookManager.asBinder().isBinderAlive()) {
try {
mRemoteBookManager
.unregisterListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(connection);
super.onDestroy();
}
}

按back鍵,發現unregister size = 1

也就是說並沒有解注冊。
為什麼呢?因為這是多進程,對象是不能跨進程傳輸的,binder會把客戶端傳遞過來的對象重新轉化並生成一個新的對象。
我們可以用RemoteCallbackList(後續會講解)
修改代碼<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
private RemoteCallbackList
注冊和解注冊代碼也要改
@Override
public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList.register(listener);
Log.e("registerListener",
mListenerList.getRegisteredCallbackCount() + "");
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList.unregister(listener);
Log.e("unregisterListener",
mListenerList.getRegisteredCallbackCount() + "");
}
同時修改onNewBookArrived函數
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = mListenerList
.getBroadcastItem(i);
if (listener != null) {
listener.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}

最後介紹一下RemoteCallbackList。我把源碼貼出來,去掉注解其實很容易看懂
public class RemoteCallbackList{ ArrayMap mCallbacks = new ArrayMap ();//用來保存aidl接口的容器 private Object[] mActiveBroadcast; private int mBroadcastCount = -1; private boolean mKilled = false; //Service進程被異常的退出時,比如被kill掉,這時系統會調用這個IBinder之前通過linkToDeath注冊的DeathRecipient類對象的binderDied函數來釋放資源 private final class Callback implements IBinder.DeathRecipient { final E mCallback; final Object mCookie; Callback(E callback, Object cookie) { mCallback = callback; mCookie = cookie; } public void binderDied() { synchronized (mCallbacks) { mCallbacks.remove(mCallback.asBinder()); } onCallbackDied(mCallback, mCookie); } } public boolean register(E callback) { return register(callback, null); } //將callback添加到ArrayMap中 public boolean register(E callback, Object cookie) { synchronized (mCallbacks) { if (mKilled) { return false; } IBinder binder = callback.asBinder(); try { Callback cb = new Callback(callback, cookie); binder.linkToDeath(cb, 0); mCallbacks.put(binder, cb); return true; } catch (RemoteException e) { return false; } } } // remove函數 public boolean unregister(E callback) { synchronized (mCallbacks) { Callback cb = mCallbacks.remove(callback.asBinder()); if (cb != null) { cb.mCallback.asBinder().unlinkToDeath(cb, 0); return true; } return false; } } //清空容器 public void kill() { synchronized (mCallbacks) { for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) { Callback cb = mCallbacks.valueAt(cbi); cb.mCallback.asBinder().unlinkToDeath(cb, 0); } mCallbacks.clear(); mKilled = true; } } public void onCallbackDied(E callback) { } public void onCallbackDied(E callback, Object cookie) { onCallbackDied(callback); } /** * Prepare to start making calls to the currently registered callbacks. * This creates a copy of the callback list, which you can retrieve items * from using {@link #getBroadcastItem}. Note that only one broadcast can * be active at a time, so you must be sure to always call this from the * same thread (usually by scheduling with {@link Handler}) or * do your own synchronization. You must call {@link #finishBroadcast} * when done. * * A typical loop delivering a broadcast looks like this: * *
* int i = callbacks.beginBroadcast(); * while (i >= 0) { * i--; * try { * callbacks.getBroadcastItem(i).somethingHappened(); * } catch (RemoteException e) { * // The RemoteCallbackList will take care of removing * // the dead object for us. * } * } * callbacks.finishBroadcast();* * @return Returns the number of callbacks in the broadcast, to be used * with {@link #getBroadcastItem} to determine the range of indices you * can supply. * * @see #getBroadcastItem * @see #finishBroadcast */ //beginBroadcast和finishBroadcast必須配對使用 public int beginBroadcast() { synchronized (mCallbacks) { if (mBroadcastCount > 0) { throw new IllegalStateException( "beginBroadcast() called while already in a broadcast"); } final int N = mBroadcastCount = mCallbacks.size(); if (N <= 0) { return 0; } Object[] active = mActiveBroadcast; if (active == null || active.length < N) { mActiveBroadcast = active = new Object[N]; } for (int i=0; i
Android學習小Demo(19)利用Loader來實時接收短信
之前寫過一篇文章《Android學習小Demo(13)Android中關於ContentObserver的使用》,在裡面利用ContentOberver去監測短信URI內
微信授權登錄、分享、支付等核心內容和支付寶支付
一、微信的授權登錄、分享、支付:(項目上線的時候記得把keystore換成記得打包的哦)(一)、微信授權登錄:1.先登錄微信的開發者平台,注冊自己的相關項目內容(詳情請查
解決GridView內容顯示不全問題
我用GridView來顯示一些字符串,而字符串的長度是不固定的,然後就遇到問題了:有時字符重疊,有時顯示不全,有時兩種問題同時出現。見下圖: 圖一 GridView顯示重
全軍盡墨的Android應用:社會化授權登錄及分享安全漏洞
隨著微信微博等社會化媒體的火熱,第三方登錄迅速成為一種快捷注冊的方式,社會化分享也成為一種知識快速傳播的渠道。在移動端,幾乎大多數應用都接入了第三方登錄或者分享組件,尤其