IPC進程間通信的使用(三)——AIDL

這一篇記錄 AIDL(Android Interface Definition Language,安卓接口定義語言)。
AIDL 默認支持的數據類型包括:

  • Java中的八種基本數據類型,包括 byte,short,int,long,float,double,boolean,char。
  • String 類型。
  • CharSequence類型。
  • List類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable。List可以使用泛型。
  • Map類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable。Map是不支持泛型的。

AIDL接口的創建

兩個aidl文件
// Book.aidl
package com.kevin.testaidl;

parcelable Book;
// IBookManager.aidl
package com.kevin.testaidl;
import com.kevin.testaidl.Book;//這裏一定要手動導入

interface IBookManager {

void addBook(in Book b);
List<Book> getBookList();
}

代碼中使用了自定義的對象Book,一定要在aidl文件中手動導入進來。同時一定要新建一個同名的aild文件,Book.aidl.

AIDL已經定義好了,找到Android Studio的make project進行編譯,會在generatedJava裏面
生成IBookManager。

服務端Service

/**
 * Created by Kevin on 2019/3/21<br/>
 * Blog:https://blog.csdn.net/student9128<br/>
 * Describe:<br/>
 * 服務端
 */
public class AIDLService extends Service {
    private final String TAG = this.getClass().getSimpleName();
    //    private List<Book> mBooks = new ArrayList<>();//aidl只支持arrayList
    private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();//保證線程同步

    private IBookManager.Stub mBookManager = new IBookManager.Stub() {

        @Override
        public void addBook(Book b) throws RemoteException {
            synchronized (this) {
                if (mBooks == null) {
//                    mBooks = new ArrayList<Book>();
                    mBooks = new CopyOnWriteArrayList<>();
                }
                if (b == null) {
                    Log.e(TAG, "Book is null in In");
                    b = new Book();
                }
                b.setPrice(123);//在服務端修改了book的價格
                if (!mBooks.contains(b)) {
                    mBooks.add(b);
                }
                //打印mBooks列表,觀察客戶端傳過來的值
                StringBuilder sb = new StringBuilder();
                for (Book boo : mBooks) {
                    sb.append("book: name is " + boo.getName() + ",price is" + boo.getPrice());
                }
                Log.e(TAG, "invoking addBooks() method , now the list is : " + sb.toString());
            }
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
          //  SystemClock.sleep(10000);
            synchronized (this) {
                if (mBooks != null) {
                    return mBooks;
                }
            }
            return new CopyOnWriteArrayList<>();
        }
    @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("Android ADIL 測試");
        book.setPrice(33);
        mBooks.add(book);
        Log.w(TAG, "onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, String.format("on bind,intent=%s", intent.toString()));
        return mBookManager;
    }
}

在onCrate中初始化添加了一本圖書信息,然後創建了一個Binder對象mBookManager並在onBind中返回它,這個對象實現了IBookManager.Stub內部的ADIL方法——addBook和getBookList方法。 可以看到addBook方法就是將添加的Book修改了price,添加到mBooks集合中,mBooks是CopyOnWriteArrayList對象,CopyOnWriteArrayList支持併發讀寫,保證多個客戶端連接是,線程同步。
清單文件配置:

        <service
            android:name=".AIDLService"
            android:exported="true"
            android:permission="com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE"
            android:process=":remote">
            <intent-filter>
                <action android:name="com.kevin.aidl" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

        </service>

客戶端

客戶端綁定遠程服務,綁定成功後將服務端返回的Binder對象轉換成AIDL接口,然後通過這個接口去調用服務端的遠程方法。

public class MainActivity extends AppCompatActivity {
    private IBookManager mBookManager = null;
    private boolean mBound = false;
    private List<Book> mBooks;
    private final String TAG = getClass().getSimpleName();
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, AIDLService.class);
        bindService(intent, mSC, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(mSC);
        super.onDestroy();
    }

    private ServiceConnection mSC = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(getLocalClassName(), "service connected");
            mBookManager = IBookManager.Stub.asInterface(service);
            if (mBookManager != null) {
                try {
                    Book book = new Book();
                    book.setPrice(99);
                    book.setName("Test");
                    mBookManager.addBook(book);
                   mBooks = mBookManager.getBookList();
                    for (Book b : mBooks) {

                        Log.d(TAG, "已存在的書本:名字:" + b.getName() + "價格:" + b.getPrice());
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.w(getLocalClassName(), "service disconnected");
        
        }
    };
}

在這裏插入圖片描述
可以看到,總共添加了兩本書,第一本是在服務端添加的,第二本是在客戶端添加的。
現在增加一種場景,用戶不希望每次都手動去獲取服務端增加的新書,而是希望,每次一有新書到來,用戶可以收到相關提醒。
添加一個aidl接口,aidl無法使用普通接口

// IOnNewBookArrivedListener.aidl
package com.kevin.testaidl;
import com.kevin.testaidl.Book;

// Declare any non-default types here with import statements

interface IOnNewBookArrivedListener {
   void onNewBookArrived(in Book newBook);
}

// IBookManager.aidl
package com.kevin.testaidl;
import com.kevin.testaidl.Book;
import com.kevin.testaidl.IOnNewBookArrivedListener;
// Declare any non-default types here with import statements

interface IBookManager {

void addBook(in Book b);
List<Book> getBookList();
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);

}

服務端代碼修改:

public class AIDLService extends Service {
    private final String TAG = this.getClass().getSimpleName();
    //    private List<Book> mBooks = new ArrayList<>();//aidl只支持arrayList
    private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();//保證線程同步
    //使用RemoteCallbackList存儲 listener,這樣可以解註冊
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);

    private IBookManager.Stub mBookManager = new IBookManager.Stub() {

        @Override
        public void addBook(Book b) throws RemoteException {
            synchronized (this) {
                if (mBooks == null) {
//                    mBooks = new ArrayList<Book>();
                    mBooks = new CopyOnWriteArrayList<>();
                }
                if (b == null) {
                    Log.e(TAG, "Book is null in In");
                    b = new Book();
                }
                b.setPrice(123);
                if (!mBooks.contains(b)) {
                    mBooks.add(b);
                }
                //打印mBooks列表,觀察客戶端傳過來的值
                StringBuilder sb = new StringBuilder();
                for (Book boo : mBooks) {
                    sb.append("book: name is " + boo.getName() + ",price is" + boo.getPrice());
                }
                Log.e(TAG, "invoking addBooks() method , now the list is : " + sb.toString());
            }
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
//            SystemClock.sleep(10000);
            synchronized (this) {
                if (mBooks != null) {
                    return mBooks;
                }
            }
            return new CopyOnWriteArrayList<>();
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {

            mListenerList.register(listener);
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("Android ADIL 測試");
        book.setPrice(33);
        mBooks.add(book);
        Log.w(TAG, "onCreate");
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, String.format("on bind,intent=%s", intent.toString()));
        return mBookManager;
    }

    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBooks.add(book);
        int N = mListenerList.beginBroadcast();
        Log.d(TAG, "onNewBookArrived,notify listeners:" + N);
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if (listener != null) {
                listener.onNewBookArrived(book);
            }
            Log.d(TAG, "onNewBookArrived,notify listener:" + listener);
            mListenerList.finishBroadcast();
        }
    }

    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestroyed.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();
                }

            }
        }
    }
}

從代碼中可以看到,創建了一個 RemoteCallbackList<>對象用來存儲IOnNewBookArrivedListener。(RemoteCallbackList是系統專門提供的專門用於刪跨進程listener的接口。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口。

public class RemoteCallbackList<E extends IInterface>

從聲明中可以看出,所以的ADIL接口都繼承自IInterface。)

實現了IBookManager.Stub內部的registerListener,unRegisterListener在這兩個方法中去實現listener的註冊和解註冊。
同時添加了一個onNewBookArrived方法,當有新書的時候,添加到mBooks集合,並調用IOnNewBookArrivedListener的onNewBookArrived進行通知用戶,新書到了。
注意這裏需要使用beginBroadcastfinishBroadcast
客戶端代碼修改:

public class MainActivity extends AppCompatActivity {
    private IBookManager mBookManager = null;
    private boolean mBound = false;
    private List<Book> mBooks;
    private final String TAG = getClass().getSimpleName();
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "receive new book: " + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    };
    private DeathRecipient deathRecipient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //開子線程防止服務器端進行耗時操作,app出現ANR
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            if (mBookManager != null) {
                                List<Book> bookList = mBookManager.getBookList();
                                for (Book b : bookList) {
                                    Log.d(TAG, "已存在的書本:名字:" + b.getName() + "價格:" + b.getPrice());
                                }
                            }
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
        Intent intent = new Intent(this, AIDLService.class);
        deathRecipient = new DeathRecipient() {

            @Override
            public void binderDied() {
                if (mBookManager == null) {
                    return;
                }
                mBookManager.asBinder().unlinkToDeath(deathRecipient, 0);
                mBookManager = null;
                attemptToBindService();//Binder die後,重新綁定服務
            }
        };

        bindService(intent, mSC, Context.BIND_AUTO_CREATE);
    }

    public void addBook() {
        if (!mBound) {
            attemptToBindService();
            Toast.makeText(this, "當前與服務的處於未連接狀態,正在嘗試連", Toast.LENGTH_SHORT).show();
            return;
        }
        if (mBookManager == null)
            return;
        Book book = new Book();
        book.setName("App研發錄");
        book.setPrice(30);
        try {
            mBookManager.addBook(book);
            Log.d(getLocalClassName(), "添加的課本:name:" + book.getName() + "\tprice:" + book.getPrice());
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("com.kevin.aidl");
        intent.setPackage("com.kevin.testaidl");
        bindService(intent, mSC, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if (mBookManager != null && mBookManager.asBinder().isBinderAlive()) {
            try {
                Log.i(TAG, "unregister listner:" + mOnNewBookArrivedListener);
                mBookManager.unRegisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mSC);
        super.onDestroy();
    }

    private ServiceConnection mSC = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(getLocalClassName(), "service connected");
            try {
                service.linkToDeath(deathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mBookManager = IBookManager.Stub.asInterface(service);
            mBound = true;
            if (mBookManager != null) {
                try {
                    Book book = new Book();
                    book.setPrice(99);
                    book.setName("Test");
                    mBookManager.addBook(book);
                    mBooks = mBookManager.getBookList();
                    for (Book b : mBooks) {

                        Log.d(TAG, "已存在的書本:名字:" + b.getName() + "價格:" + b.getPrice());
                    }
                    mBookManager.registerListener(mOnNewBookArrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.w(getLocalClassName(), "service disconnected");
            mBound = false;
        }
    };
    //運行中Binder線程池中,不能在它裏面訪問UI相關的內容,如果要訪問,使用Handler切換到UI線程
    // Binder可能會意外死亡,使用DeathRecipient監聽,當Binder死亡是binderDied方法中重新連接服務
    //或者在onServiceDisconnected重新連接服務
    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            //
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };
}

可以看到代碼中爲了防止Binder以外死亡,使用了DeathRecipient,當Binder死亡了,可以在binderDied方法中監聽到,重新連接服務。
在使用進程間通信的過程中,還有一點是要去驗證應用的權限,這裏可以爲服務自己定義一個權限,在使用的過程中在清單文件去聲明下這個權限,服務端可以進行權限驗證。

兩種方法,

  1. 在onBind方法中驗證權限,如果驗證失敗,返回null

        @Override
        public IBinder onBind(Intent intent) {
            //在這裏校驗權限,如果客戶端沒有聲明權限會一直連接不上,但是不知道爲什麼,不會有提示
           int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE");
            if (i == PackageManager.PERMISSION_DENIED) {
                return null;
            }
            Log.i(TAG, String.format("on bind,intent=%s", intent.toString()));
            return mBookManager;
        }
    
    
  2. 在服務端的onTransact方法中進行權限驗證,如果失敗就返回false,這樣服務端就不會終止執行AIDL中的方法從而達到保護服務端的效果。

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            //驗證權限
            int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE");
            if (i == PackageManager.PERMISSION_DENIED) {
                Log.i(TAG, "權限不被允許");
                return false;
            }
            //驗證包名
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (!packageName.startsWith("com.kevin.testaidl")) {
                Log.i(TAG, "包名不對");
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
    };

清單文件的配置
服務端:

   <permission
        android:name="com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="signature" />

客戶端:

    <uses-permission android:name="com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE" />

在這裏插入圖片描述

最後貼一下服務端的完整代碼:

/**
 * Created by Kevin on 2019/3/21<br/>
 * Blog:https://blog.csdn.net/student9128<br/>
 * Describe:<br/>
 * 服務端
 */
public class AIDLService extends Service {
    private final String TAG = this.getClass().getSimpleName();
    //    private List<Book> mBooks = new ArrayList<>();//aidl只支持arrayList
    private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();//保證線程同步
    //使用RemoteCallbackList存儲 listener,這樣可以解註冊
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);

    private IBookManager.Stub mBookManager = new IBookManager.Stub() {

        @Override
        public void addBook(Book b) throws RemoteException {
            synchronized (this) {
                if (mBooks == null) {
//                    mBooks = new ArrayList<Book>();
                    mBooks = new CopyOnWriteArrayList<>();
                }
                if (b == null) {
                    Log.e(TAG, "Book is null in In");
                    b = new Book();
                }
                b.setPrice(123);
                if (!mBooks.contains(b)) {
                    mBooks.add(b);
                }
                //打印mBooks列表,觀察客戶端傳過來的值
                StringBuilder sb = new StringBuilder();
                for (Book boo : mBooks) {
                    sb.append("book: name is " + boo.getName() + ",price is" + boo.getPrice());
                }
                Log.e(TAG, "invoking addBooks() method , now the list is : " + sb.toString());
            }
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
//            SystemClock.sleep(10000);
            synchronized (this) {
                if (mBooks != null) {
                    return mBooks;
                }
            }
            return new CopyOnWriteArrayList<>();
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
//            if (!mListenerList.contains(listener)) {
//                mListenerList.add(listener);
//            }else {
//                Log.d(TAG, "listener already exists...");
//            }
//            Log.d(TAG, "registerListener,size:" + mListenerList.size());
            mListenerList.register(listener);
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
//            if (mListenerList.contains(listener)) {
//                mListenerList.remove(listener);
//            } else {
//                Log.d(TAG, "not found the listener,cannot unregister");
//            }
//            Log.d(TAG, "unRegisterListener,current size :" + mListenerList.size());
            mListenerList.unregister(listener);
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            //驗證權限
            int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE");
            if (i == PackageManager.PERMISSION_DENIED) {
                Log.i(TAG, "權限不被允許");
                return false;
            }
            //驗證包名
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (!packageName.startsWith("com.kevin")) {
                Log.i(TAG, "包名不對");
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("Android ADIL 測試");
        book.setPrice(33);
        mBooks.add(book);
        Log.w(TAG, "onCreate");
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        //在這裏校驗權限,如果客戶端沒有聲明權限會一直連接不上,但是不知道爲什麼,不會有提示
//        int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE");
//        if (i == PackageManager.PERMISSION_DENIED) {
//            return null;
//        }
        Log.i(TAG, String.format("on bind,intent=%s", intent.toString()));
        return mBookManager;
    }

    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBooks.add(book);
        int N = mListenerList.beginBroadcast();
        Log.d(TAG, "onNewBookArrived,notify listeners:" + N);
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if (listener != null) {
                listener.onNewBookArrived(book);
            }
            Log.d(TAG, "onNewBookArrived,notify listener:" + listener);
            mListenerList.finishBroadcast();
        }
    }

    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestroyed.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();
                }

            }
        }
    }
}

注:本文章知識點來自學習《Android開發藝術探索》一書

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章