1. 程式人生 > >Android IPC機制(三)使用AIDL實現

Android IPC機制(三)使用AIDL實現

1、AIDL全稱為:Android Interface definition language 譯為:android介面定義語言

 AIDL使用的資料是有限制的,可以使用的有:
基本資料型別(int、long、char、boolean、double等)
String 和CharSequence
List:只支援ArrayList,並且裡面的元素也要被AIDL支援
Map:只支援HashMap,裡面的Key,value都必須支援AIDL
Parcelable:所有實現了Parcelable介面的物件
AIDL:所有的AIDL介面本身也可以在AIDL檔案中使用

而且自定義的Parcelable物件和AIDL物件必須要顯示import進來,無論是否在同一個包裡

2、在AIDL中除了基礎資料型別,其他型別的引數必須標上方向:in 、out或者inout,in代表輸入型引數,out代表
輸出型引數,inout代表輸入輸出型引數

AIDL只支援方法,不支援宣告靜態常量

3、使用的步驟

我們需要建立aidl為字尾的檔案,如下

package com.example.multiprocess.aidl;
import com.example.multiprocess.aidl.Book;
import com.example.multiprocess.aidl.IOnNewBookArrivedListener;
interface IBookManager {

    List<Book> getBookList();
    void addBook(in Book book);

}

build之後會在生成相應的Java檔案

生成的Java類本身是一個繼承了android.os.IInterface(所有在Binder中傳輸的介面必須繼承IInterface的介面)介面的類

裡面有兩個內部類是實現AIDL的關鍵,一個Stub抽象類,具體的介面方法實現在服務端;一個Proxy靜態內部類,在客戶端用。

/**
 * Local-side IPC implementation stub class.繼承自Binder,但是是一個抽象類,
 */
public static abstract class Stub extends android.os.Binder implements com.example.multiprocess.aidl.IBookManager {
    private static final java.lang.String DESCRIPTOR = "com.example.multiprocess.aidl.IBookManager";

    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an com.example.multiprocess.aidl.IBookManager interface,
     * generating a proxy if needed.
     */
    public static com.example.multiprocess.aidl.IBookManager asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
    
    
    
//同進程時直接返回IBookManager物件,多程序則返回Proxy;服務端Stub,客戶端Proxy
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.example.multiprocess.aidl.IBookManager))) {
            return ((com.example.multiprocess.aidl.IBookManager) iin);
        }
        return new com.example.multiprocess.aidl.IBookManager.Stub.Proxy(obj);
    }

    @Override
    public android.os.IBinder asBinder() {
        return this;
    }

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.example.multiprocess.aidl.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                com.example.multiprocess.aidl.Book _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.example.multiprocess.aidl.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }

        }
        return super.onTransact(code, data, reply, flags);
    }

/**
 * 一個對Binder的代理類
 */
private static class Proxy implements com.example.multiprocess.aidl.IBookManager {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    @Override
    public android.os.IBinder asBinder() {
        return mRemote;
    }

    public java.lang.String getInterfaceDescriptor() {
        return DESCRIPTOR;
    }

    @Override
    public java.util.List<com.example.multiprocess.aidl.Book> getBookList() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.example.multiprocess.aidl.Book> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(com.example.multiprocess.aidl.Book.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

    @Override
    public void addBook(com.example.multiprocess.aidl.Book book) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if ((book != null)) {
                _data.writeInt(1);
                book.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }


}

服務端的使用

public class BookManagerService extends Service {

    public final String TAG = "BookManagerService";


    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);//判斷服務是否被銷燬
    //CopyOnWriteArrayList多個執行緒可以同時操作同一資料,執行緒安全的變體
    private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();
    //系統提供的專門用於刪除跨程序listener的介面的,本身實現了執行緒同步
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
    /**
     * 服務端實現介面方法
     */
    IBinder mIBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            //模擬耗時操作,此方法會導致客戶端ANR
            SystemClock.sleep(5000);
            return mBooks;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            if (mBooks != null)
                mBooks.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            //準備開始呼叫當前註冊的回撥。這將建立一個回撥列表的副本,你可以從使用getBroadcastItem檢索條目,
            // 注意:一次只能啟用一個廣播,所以你必須確保總是從同一個執行緒呼叫這個或者是自己做同步。
            // 完成後必須呼叫finishBroadcast。兩個方法必須成對使用
            final int N = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.d(TAG, "registerListener, current size:" + N);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            boolean success = mListenerList.unregister(listener);

            if (success) {
                Log.d(TAG, "unregister success.");
            } else {
                Log.d(TAG, "not found, can not unregister.");
            }
            final int N = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.d(TAG, "unregisterListener, current size:" + N);
        }

/**
 *驗證自定義許可權和包名,此種方法服務端不會終止AIDL中的方法從而達到保護服務端的效果
 */
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.example.multiprocess.ACCESS_BOOK_SERVICE");
            Log.e(TAG, "permission: " + 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.example")) {
                return false;
            }

            return super.onTransact(code, data, reply, flags);
        }

    };


    @Override
    public void onCreate() {
        super.onCreate();
        mBooks.add(new Book(1, "Android"));
        mBooks.add(new Book(2, "Ios"));
        new Thread(new ServiceWorker()).start();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
/**
 * 驗證自定義許可權,無法服務端的正常連線
 */
        int check = checkCallingOrSelfPermission("com.example.multiprocess.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mIBinder;

    }

    /**
     * 新增新書後,遍歷通知觀察者
     * @param book
     * @throws RemoteException
     */
    private void onNewBookArrived(Book book) throws RemoteException {
        mBooks.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if (l != null) {
                try {
                    l.onNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListenerList.finishBroadcast();
    }

    /**
     * 每間隔5s新增一本書
     */
    private class ServiceWorker implements Runnable {
        @Override
        public void run() {
            // do background processing here.....
            while (!mIsServiceDestoryed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBooks.size() + 1;
                Book newBook = new Book(bookId, "new book#" + bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);//表示停止服務,停止新增書
        super.onDestroy();
    }
}

客戶端的呼叫

public class BookManagerActivity extends AppCompatActivity {

    private final static String TAG="BookManagerActivity";
    private IBookManager mRemoteIBookManager;

    private static Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.e(TAG,((Book)msg.obj).toString());
                    break;
                    default:
                        super.handleMessage(msg);
            }

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent=new Intent(this,BookManagerService.class);
        bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }

    private IBinder.DeathRecipient mDeathRecipient=new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
            if (mRemoteIBookManager == null)
                return;
            //解除死亡通知,如果Binder死亡了,不會在觸發binderDied方法
            mRemoteIBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mRemoteIBookManager = null;
            // TODO:這裡重新繫結遠端Service
        }
    };

    private ServiceConnection mServiceConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager=IBookManager.Stub.asInterface(service);
            mRemoteIBookManager=iBookManager;

            try {
                mRemoteIBookManager.asBinder().linkToDeath(mDeathRecipient, 0);//設定死亡代理
                //服務端用到的是CopyOnWriteArrayList,但是AIDL會將其按照List介面規則將其轉換為ArrayList
                //iBookManager.getBookList()如果為耗時操作需要開啟子執行緒,否則會造成執行緒阻塞,從而ANR
                List<Book> books=iBookManager.getBookList();
                Log.e(TAG,"list type:"+books.getClass().getCanonicalName());
                Log.e(TAG,"query book list:"+books.toString());
                iBookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
//            mRemoteIBookManager = null;
            Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
        }
    };

    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {

        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
                //此處是在Binder執行緒中執行的,需要用Handler跳轉至主執行緒
                handler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();

        }

    };

    @Override
    protected void onDestroy() {
        if (mRemoteIBookManager != null && mRemoteIBookManager.asBinder().isBinderAlive()) {
            try {
                //解除繫結
                mRemoteIBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mServiceConnection);//接觸服務繫結
        super.onDestroy();

    }
}
我們通過registerListener和unregisterListener兩個方法來實現伺服器對客戶端的通知,相當於一個觀察者模式,
當有新書的時候統一向客戶端傳送訊息。1、還有要注意客戶端和服務端方法呼叫時所在的執行緒是不同的,要根據實際
情況來進行處理。2、在解除註冊監聽時要注意使用RemoteCallbackList來管理監聽,否則無法正確取消監聽。
3、在服務端意外終止時要利用IBinder.DeathRecipient(死亡代理)或onServiceDisconnected(ComponentName name)
方法重新連線伺服器。4、在介面銷燬時要解除註冊和解除服務繫結具體的注意在程式碼註釋中有。