IPC 之 AIDL 的使用

1、AIDL 知識儲備

 1. AIDL 文件支持的數據類型:html

  •   基本數據類型 (int , long , char , boolean ,double 等);
  •   String 和 CharSequence;
  •   List :只支持 ArrayList,裏面每一個元素都必須可以被 AIDL 支持;
  •   Map :只支持 HashMap,裏面每一個元素都必須可以被 AIDL 支持,包括 key 和 value;
  •   Parcelable : 全部實現了 Parcelable 接口的對象;
  •   AIDL : 全部的 AIDL 接口自己也能夠在 AIDL 文件中使用。

 2. 自定義的 Parcelable 對象 和 AIDL  對象必需要顯示 import 進來,無論他們是否和當前的 AIDL 文件位於同一個包內。java

 3. 若是 AIDL 文件中用到了自定義的 Parcelable 對象,那麼必須新建一個和它同名的 AIDL 文件,並在其中聲明它爲 Parcelable 類型。android

 4. AIDL 中除了基本數據類型,其餘類型的參數必須標上方向:in 、out 或者 inout,in 表示輸入型參數 , out 表示輸出型參數,inout 表示輸入輸出型參數。安全

2、AIDL 的使用:

  AIDL 是用於多進程通訊場景的,咱們建立兩個應用,一個 Client 和一個 Server 來講明 AIDL 的使用。在示例項目中實現了基本的交互操做,還有 權限驗證註冊 listener 到服務端監聽服務端事件 重連機制併發

  1. 客戶端

    客戶端工程目錄:app

                   

  看圖咱們能夠知道,工程中建立了和 AIDL 相關的四個文件,Book類、Book.aidl 、IBookManager.aidl、IOnNewBookArrivedListener.aidl 四個文件。ide

  (1) Book類是示例中用於傳遞的數據類,實現了 Parcelable接口,代碼以下:ui

package com.sl.analyzebinder;

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;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}

  (2)Book.aidl 的做用是要符合上面第一章節第 3 點的規定,代碼以下:this

package com.sl.analyzebinder;
parcelable Book;

  (3)IBookManager.aidl 定義交互操做的接口,代碼以下:atom

// IBookManager.aidl
package com.sl.analyzebinder;
import com.sl.analyzebinder.Book;
import com.sl.analyzebinder.IOnNewBookArrivedListener;

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

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);
}

  (4)IOnNewBookArrivedListener.aidl 定義接聽服務事件的接口,代碼以下:

// IOnNewBookArrivedListener.aidl
package com.sl.analyzebinder;
import com.sl.analyzebinder.Book;
// Declare any non-default types here with import statements

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

  全部關於AIDL的文件寫好以後,咱們先 Make Project 一下,而後來看一下客戶端如何鏈接服務端,MainActivity 中進行鏈接:

package com.sl.analyzebinder;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

import java.lang.ref.WeakReference;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private static final int MESSAGE_NEW_BOOK_ARRIVED = 110;
    private BookHandler mHandler = new BookHandler(this);
    private IBookManager bookManager;

    private static class BookHandler extends Handler {
        private WeakReference<MainActivity> activity;

        BookHandler(MainActivity activity) {
            this.activity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    if (activity != null) {
                        activity.get().changeContent((Book) msg.obj);
                    }
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (bookManager == null)
                return;
            bookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            bookManager = null;
            bindService();

        }
    };

    private void changeContent(Book book) {
        String text = content.getText().toString();
        content.setText(text.concat("-----------").concat(book.bookName));
    }

    private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();
        }
    };
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) { 
            // onServiceConnected 和 onServiceDisconnected 都在ui線程中執行,不要執行服務端耗時操做,不然 ANR
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> bookList = bookManager.getBookList();
                StringBuilder result = new StringBuilder();
                if (bookList != null && bookList.size() > 0) {
                    for (int i = 0; i < bookList.size(); i++) {
                        result.append("----").append(bookList.get(i).bookName);
                    }
                }
                content.setText(bookList != null ? result : "失敗了");
                bookManager.addBook(new Book(3, "擺渡人"));
                bookManager.registerListener(mListener);
                service.linkToDeath(mDeathRecipient,0); //重連機制
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) { //重連機制
            bookManager = null;
        }
    };
    private TextView content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService();
        content = findViewById(R.id.book_list);
    }

    private void bindService() {
        Intent service = new Intent();
        service.setAction("com.sl.aidl");
        service.setPackage("com.sl.binderservice");
        bindService(service, mConnection, Context.BIND_AUTO_CREATE);
    }

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

    private void unRegisterListener() {
        if (bookManager != null && bookManager.asBinder().isBinderAlive()) { // 經過Binder 的 isBinderAlive 方法也能夠判斷 Binder 是否死亡
            try {
                bookManager.unRegisterListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void stopInform(View view) {
        unRegisterListener();
    }
}

  爲何要先 Make Project 一下呢,目的是爲了讓系統爲咱們自動生成 AIDL 中的 Binder 實現,你能夠到下面這個目錄中去查看一下具體生成的代碼:

     

    對了,要想 aidl 目錄下的 java 文件被 編譯,還須要再 app 下的build.xml 文件中添加配置:

android {
    sourceSets{
main{
java.srcDirs = ['src/main/java', 'src/main/aidl']
aidl.srcDirs = ['src/main/aidl']
}
}

  服務端要求權限驗證,因此客戶端得添加權限,這個權限在服務端中定義:

<uses-permission android:name="com.sl.permission.aidl" />

 

  2. 服務端

      將 客戶端 aidl 目錄所有拷貝到 服務端,拷貝後來看服務端目錄:

        

    拷貝過來以後,仍然須要 Make Project ,道理你懂得。BookManagerService 就是咱們的服務了,來看具體實現:

package com.sl.binderservice;

import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.sl.analyzebinder.Book;
import com.sl.analyzebinder.IBookManager;
import com.sl.analyzebinder.IOnNewBookArrivedListener;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

public class BookManagerService extends Service {
    private static final String TAG = "BMS";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); //CopyOnWriteArrayList 支持併發讀寫
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>(); //CopyOnWriteArrayList 支持併發讀寫
    private AtomicBoolean mIsServiceDestory = new AtomicBoolean();

    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 registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

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

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { //onTransact處進行權限驗證
            int check = checkCallingOrSelfPermission("com.sl.permission.aidl");
            if (check == PackageManager.PERMISSION_DENIED) //權限驗證
                return false;
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); // uid 驗證
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (packageName == null || !packageName.startsWith("com.sl"))
                return false;
            return super.onTransact(code, data, reply, flags);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mIsServiceDestory.set(false);
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "IOS"));

        new Thread(new ServiceWorker()).start();
    }

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

    @Override
    public IBinder onBind(Intent intent) { //onBind 處進行權限驗證,驗證權限並無經過。不知道是爲何?
/**int check = checkCallingOrSelfPermission("com.sl.permission.aidl");
      * if (check == PackageManager.PERMISSION_DENIED)
      *           return null;
*/
return mBinder; } private class ServiceWorker implements Runnable { @Override public void run() { while (!mIsServiceDestory.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "NewBook#" + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); int count = mListenerList.beginBroadcast(); for (int i = 0; i < count; i++) { IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i); if (listener != null) listener.onNewBookArrived(book); } mListenerList.finishBroadcast(); } }

   記得在 AndroidManifest.xml 中註冊服務:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sl.binderservice">

    <permission
        android:name="com.sl.permission.aidl"
        android:description="@string/service_permission"
        android:permissionGroup="com.sl.permissions"
        android:protectionLevel="normal" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".BookManagerService"
            android:permission="com.sl.permission.aidl"
            android:exported="true">
            <intent-filter>
                <action android:name="com.sl.aidl" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>
    </application>

</manifest>

  BookManagerService 中進行了權限驗證,涉及到 自定義權限 查看這篇。除了自定義的Permission 驗證外,還有經過 UID 驗證,這是 Binder 機制提供的,這就是體現 Binder 通訊機制相對其餘機制安全的點。

  注意服務端中用到了幾個類,這裏提一下:

  AtomicBoolean :  支持併發讀寫

  RemoteCallbackList : 支持併發讀寫 ,是系統專門提供的用於刪除跨進程 listener 的接口,它是一個泛型,支持管理任意的 AIDL 接口 。它還能當客戶端進程終止後,它可以自動移除客戶端所註冊的 listener 。

  CopyOnWriteArrayList : 支持併發讀寫 。

  這三個這裏就不詳細介紹了,到這裏AIDL 的具體使用也基本完事了,若是有漏的話,後續補上或者告訴我。

  示例工程代碼,下載地址  密碼: smf7

相關文章
相關標籤/搜索