編輯:關於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{ ListgetBookList(); 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.CreatorCREATOR = 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 CopyOnWriteArrayListmBookList = 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 { Listlist = 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 { Listlist = 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{ ListgetBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); }
這樣一來BookManagerService.java會自動生成兩個新的方法。同時開啟一個線程,每隔5s就向書庫中添加一個本書並通知所有感興趣單位客戶
public class BookManagerService extends Service { private CopyOnWriteArrayListmBookList = 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;//這一句不能忘 Listlist = 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(13)Android中關於ContentObserver的使用》,在裡面利用ContentOberver去監測短信URI內
一、微信的授權登錄、分享、支付:(項目上線的時候記得把keystore換成記得打包的哦)(一)、微信授權登錄:1.先登錄微信的開發者平台,注冊自己的相關項目內容(詳情請查
我用GridView來顯示一些字符串,而字符串的長度是不固定的,然後就遇到問題了:有時字符重疊,有時顯示不全,有時兩種問題同時出現。見下圖: 圖一 GridView顯示重
隨著微信微博等社會化媒體的火熱,第三方登錄迅速成為一種快捷注冊的方式,社會化分享也成為一種知識快速傳播的渠道。在移動端,幾乎大多數應用都接入了第三方登錄或者分享組件,尤其