美文网首页面试题
Android IPC通信之AIDL

Android IPC通信之AIDL

作者: Kris_Ni | 来源:发表于2019-07-23 13:21 被阅读0次

AIDL( Android Interface Definition Language),安卓接口定义语言,用于定义服务器和客户端通信接口的一种描述语言,主要是解决了android进程间通信的问题。
在AIDL中,不是所有的数据类型都是可以使用的。可用类型如下:

  • 基本数据类型(byte char int long boolean float double),short不支持
  • String和CharSequence
  • List:只支持ArrayList,里面的每个元素都必须被AIDL支持
  • Map:只支持HashMap,里面的每个key,value都必须被AIDL支持
  • Parcelable:所有实现了Parcelable接口的对象
  • AIDL 所有的AIDL接口本身也可以在AIDL文件中使用
    需要注意的是:
  • AIDL中自定义的Parcelable对象和AIDL对象必须要实现import进来,即使是在同一个包下面。
  • 如果AIDL中用到了Parcelable对象,那么必须要新建一个和它同名的AIDL文件,并在其中声明它为Parcelable对象。
  • AIDL中除了基本数据,其他类型的参数必须标上方向:in、out、inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。
    接下来通过Demo来讲解:
//自定义数据类型Book,实现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();
    }

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

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

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

接着New->AIDL->AIDL file创建AIDL文件,注意要明确导包

// Book.aidl ,可能会出现同名问题,可以先建一个别的名字再改回来
package com.wtwd.aidl;
parcelable Book;

// IBookManager.aidl
package com.wtwd.aidl;
import com.wtwd.aidl.Book;
import com.wtwd.aidl.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

// IOnNewBookArrivedListener.aidl
package com.wtwd.aidl;
import com.wtwd.aidl.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newbook);
}

接着build一下项目会在app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out目录下找到对应的AIDL生成文件(具体分析见Android中的IPC机制一文),接下来创建BookManagerService作为服务端核心代码。

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";
    //CopyOnWriteArrayList是为了处理线程同步,但是这里就和之前说的AIDL只能够使用ArrayList,为什么这儿可以
    //使用CopyOnWriteArrayList呢?首先CopyOnWriteArrayList不是继承自ArrayList的,且AIDL支持的是抽象的
    //List,而List是一个接口,虽然服务端返回的是CopyOnWriteArrayList,但是Binder中会按照List的规范去访
    //问数据并最终形成一个新的ArrayList给客户端,同理ConcurrentHashMap也是如此。
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    //private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
    //Binder从客户端传过来的对象经过反序列化后和原先是不同的,所以需要采用RemoteCallbackList来实现
    //RemoteCallbackList是系统专门用来删除跨进程的listener接口,它是一个泛型,支持管理任意的AIDL接口,这是因为所以AIDL接口继承自IInterface
    //public class RemoteCallbackList<E extends IInterface> {}
    //内部采用Map结构保存回调,key是IBinder类型,value是CallBack类型
    //ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
    //在CallBack中封装了真正的远程listener,客户端注册listener的时候会把这个listener信息存到mCallbacks,key和value通过如下获取
    //IBinder binder = callback.asBinder();
    //Callback cb = new Callback(callback, cookie);
    //RemoteCallbackList内部已实现线程同步
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
    //用来记录Service是否销毁的标志位,默认false
    private AtomicBoolean mIsServiceDestroyed = 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 {
//            if (!mListenerList.contains(listener)) {
//                mListenerList.add(listener);
//            } else {
//                Log.e(TAG,"already exists.");
//            }
//            Log.e(TAG,"registerListener,size:"+mListenerList.size());
            mListenerList.register(listener);
        }
        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
//            if (!mListenerList.contains(listener)) {
//                mListenerList.remove(listener);
//                Log.e(TAG,"unregister listener success");
//            } else {
//                Log.e(TAG,"not found,can not unregister.");
//            }
//            Log.e(TAG,"unregisterListener,size:"+mListenerList.size());
            mListenerList.unregister(listener);
        }
    };
    
    //服务端返回的IBinder对象
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1,"Android"));
        mBookList.add(new Book(2,"Ios"));
        //开启子线程,执行任务
        new Thread(new ServiceWorker()).start();
    }

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

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

    private class ServiceWorker implements Runnable {
        @Override
        public void run() {
            while (!mIsServiceDestroyed.get()) {
                try {
                    //每隔5s执行任务,回调给客户端
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size()+1;
                Book newBook = new Book(bookId,"new book#"+bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
AndroidManafist.png

以上就是AIDL服务端的代码了,虽然看起来比较复杂,但是细细评味下来还是获益匪浅的。接下来就开始客户端的代码编写了,通常我们接触最多的也是这方面,服务端会把相关的AIDL代码提供过来,我们直接把它复制粘贴到项目中就可以了,需要注意一点的是,如果存在自定义数据类型,需要将JavaBean放到java目录和AIDL相同的包下面,如下所示:


AIDL客户端复制.png

接下来Build一下Project就可以生成对应的AIDL接口文件了

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "kris_ni";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 0x1;

    private IBookManager iBookManager;

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

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            iBookManager = IBookManager.Stub.asInterface(iBinder);
            try {
                List<Book> bookList = iBookManager.getBookList();
                Log.e(TAG,"query book list,list type: "+ bookList.getClass().getCanonicalName());
                Log.e(TAG,"query book list: "+bookList.toString());
                Book newBook = new Book(3,"Kotlin");
                iBookManager.addBook(newBook);
                Log.e(TAG,"add new book: "+newBook);
                List<Book> newList = iBookManager.getBookList();
                Log.e(TAG,"query book list: "+newList.toString());
                iBookManager.registerListener(listener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            iBookManager = null;
            Log.e(TAG,"binder died");
        }
    };

    private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,book).sendToTarget();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent("com.example.aidldemo.BookManagerService");
        intent.setPackage("com.example.aidldemo");
        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
            try {
                iBookManager.unregisterListener(listener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(serviceConnection);
        super.onDestroy();
    }
}

总体看下来AIDL也就这样了么,当然不止这些。有没有考虑过AIDL被滥用的情况呢?可以通过权限来限制客户端,简单的写法就是在服务端声明权限

    <permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal"/>
    <uses-permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"/>

接着就可以在Service里面判断是否将IBinder返回了

    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.example.aidldemo.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            Log.e(TAG,"has no permission");
            return null;
        }
        return mBinder;
    }

客户端所做的是需要申明权限才可以进行ServiceConnection

    <uses-permission android:name="com.example.aidldemo.permission.ACCESS_BOOK_SERVICE"/>

另外一种写法如下:

     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            int check = checkCallingOrSelfPermission("com.example.aidldemo.permission.ACCESS_BOOK_SERVICE");
            Log.d(TAG, "check=" + 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);
        }

AIDL的介绍到此就差不多结束了,有没有思考过Binder可能会意外被销毁呢?在接下来的Binder连接池里面会重点为大家介绍,感谢您的阅读,记得随手点个赞喔!

相关文章

网友评论

    本文标题:Android IPC通信之AIDL

    本文链接:https://www.haomeiwen.com/subject/oozilctx.html