IPC机制之四:IPC方式(AIDL)

来源:互联网 发布:网络电视怎么搜索节目 编辑:程序博客网 时间:2024/05/12 05:42

Messenger与AIDL

上一节讲解的Messenger来进行进程间通信的方法,可以发现,Messenger是以串行的方式来处理客户端的请求的,如果大量的消息同时发送给服务端,仍然也只能一个一个的处理。所以如果有大量的并发请求,那么用Messenger是不太合适了。同时,Messenger的作用是用来传递消息的,很多时候我们需要跨进程调用服务端的方法。这种情形Messenger是无法做到的。只能使用AIDL来进行。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL。只不过系统为我们做了封装方便调用而已。

AIDL进程间通信的流程,分为服务端和客户端:
1.服务端
首先需要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

2.客户端
首先通过绑定服务端的Service,绑定成功后,将服务端返回的IBinder对象转成AIDL接口所属的类型。接着就可以调用AIDL中的方法了。

AIDL接口的创建

// IBookManager.aidlpackage com.thh.ipcdemo2aidl.aidl;// Declare any non-default types here with import statementsimport com.thh.ipcdemo2aidl.aidl.Book;import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;interface IBookManager {    List<Book> getBookList();    void addBook(in Book book);    void registerListener(IOnNewBookArrivedListener listener);    void unRegisterListener(IOnNewBookArrivedListener listener);}

AIDL支持的数据类型:
基本数据类型、String和CharSequence、List(只支持ArrayList,并且里面的每个元素都必须能被AIDL支持)、Map(只支持HashMap,里面的每个元素都必须被AIDL支持,包括KEY和Value)、Parcelable、AIDL(所有AIDL接口本身也可以在AIDL文件中使用)

注意:
自定义的Parcelable对象不是与AIDL文件是否在同一个包下,都需要显式的import进来。
如果AIDL文件中用到了自定义的Parcelable对象,必须创建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。如下:

package com.thh.ipcdemo2aidl.aidl;parcelable Book;

.
AIDL中除了基本数据类型以外,其他类型的参数必须标上方向:in、out或者inout。
如果client不需要传输数据给server,client只需要处理经过server处理过后的数据,
那么 client 和 server 都为 out
如果client只需要传输数据给server,而不需要处理返回的数据,
那么client和server都为 in
如果client需要传输数据给server,而且需要处理返回的数据,
则client和server都为 inout

AIDL中只支持方法,不支持静态常量。这点区别于传统接口。

示例代码:

package com.thh.ipcdemo2aidl;import android.annotation.TargetApi;import android.app.Service;import android.content.Intent;import android.content.pm.PackageManager;import android.os.Binder;import android.os.Build;import android.os.IBinder;import android.os.Parcel;import android.os.RemoteCallbackList;import android.os.RemoteException;import android.support.annotation.Nullable;import android.util.Log;import com.thh.ipcdemo2aidl.aidl.Book;import com.thh.ipcdemo2aidl.aidl.IBookManager;import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;import java.util.concurrent.atomic.AtomicBoolean;/** * Created by TangHui on 2015/10/21. */public class IService extends Service {    private AtomicBoolean mIsServiceDestory = new AtomicBoolean(false);    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();    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);        }        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)        @Override        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {            mListenerList.register(listener);            Log.i("thhi", "[IService registerListener] registerListener size:"+mListenerList.getRegisteredCallbackCount());        }        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)        @Override        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {            mListenerList.unregister(listener);            Log.i("thhi", "[IService unRegisterListener] current size:" + mListenerList.getRegisteredCallbackCount());        }        @Override        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {            // 第二种远程调用的验证方式            int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");            if (check == PackageManager.PERMISSION_DENIED){                Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED");                return false;            }            String packageName = null;            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());            if (packages!=null && packages.length > 0){                packageName = packages[0];            }            if (!packageName.startsWith("com.thh")){                return false;            }            return super.onTransact(code, data, reply, flags);        }    };    @Override    public void onCreate() {        super.onCreate();        mBookList.add(new Book(1, "Android"));        mBookList.add(new Book(2, "IOS"));        new Thread(new ServiceWorker()).start();    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        // 第一种远程调用的验证方式//        int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");//        if (check == PackageManager.PERMISSION_DENIED){//            Log.i("thhi","[IService onBind] 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, "new Book # " + bookId);                try {                    onNewBookArrived(newBook);                } catch (RemoteException e) {                    e.printStackTrace();                }            }        }    }    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);            Log.i("thhi", "[IService onNewBookArrived] notify listener:"+listener);            listener.onNewBookArrived(book);        }        mListenerList.finishBroadcast();    }    @Override    public void onDestroy() {        super.onDestroy();        mIsServiceDestory.set(true);    }}

CopyOnWriteArrayList支持并发读/写,其原理是这样的,开始大家共享同一个内容,但如果一个人要去修改,它就会Copy一份新的内容,当修改并添加完毕后,自动将引用指向这个新的内容。在源代码中可以发现,它的add方法是添加的有锁的,get方法没有锁。这种类型对象适合多读少写的操作,因为可能导致数据的不一致性。因为AIDL方法在服务端的Bindler线程池中执行,因此当多个客户端访问时,我们需要做线程同步的处理,所以这里就用CopyOnWriteArrayList。

另外还有一个ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

package com.thh.ipcdemo2aidl;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.os.RemoteException;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.Menu;import android.view.MenuItem;import com.thh.ipcdemo2aidl.aidl.Book;import com.thh.ipcdemo2aidl.aidl.IBookManager;import com.thh.ipcdemo2aidl.aidl.IOnNewBookArrivedListener;import java.util.List;public class MainActivity extends AppCompatActivity {    private static final int MESSAGE_NEW_BOOK_ARRIVED = 0;    private Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case MESSAGE_NEW_BOOK_ARRIVED:                    Log.i("thhi", "[MainActivity mHandler handlerMessage]: MESSAGE_NEW_BOOK_ARRIVED, msg:"+msg.obj);                    break;            }            super.handleMessage(msg);        }    };    private IBookManager iBookManager;    private ServiceConnection mServiceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            iBookManager = IBookManager.Stub.asInterface(service);            try {                List<Book> bookList = iBookManager.getBookList();                Log.i("thhi", "bookList:" + bookList);                iBookManager.addBook(new Book(3, "Window Phone"));                Log.i("thhi", "addBook after:" + iBookManager.getBookList());                iBookManager.registerListener(mOnNewBookArrivedListener);            } catch (RemoteException e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName name) {            iBookManager = null;        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Intent intent = new Intent(this, IService.class);        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);    }    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {        @Override        public void onNewBookArrived(Book book) throws RemoteException {            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();        }    };    @Override    protected void onDestroy() {        if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {            try {                iBookManager.unRegisterListener(mOnNewBookArrivedListener);            } catch (RemoteException e) {                e.printStackTrace();            }        }        unbindService(mServiceConnection);        super.onDestroy();    }}

知识点:
AIDL文件中无法使用的普通接口,只能是AIDL接口。
另外通过RemoteCallbackList来删除跨进程listener的接口。用于解决无法删除对应listener对象。因为是客户端传递给服务端的对象在服务端会生成一个不同的对象,但它们底层的Binder对象是同一个,利用这个特点,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删除掉。另外,当客户端进程终止后,它能够自动移除客户端所注册的listener。
客户端调用服务端方法时,尽量使用子线程。服务端AIDL方法调用客户端方法时,因为当前线程是Binder线程池的子线程,所以不得进行UI操作。在上面例子中,服务端需要调用客户端listener的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。
.
.

Binderl意外中断

往往是由于服务端进程意外停止了,这时我们需要重新连接服务。有两种方法:
给Binder设置DeathRecipient监听,当Binder中断后,我们会收到binderDeath方法的回调。
另一种方法在onServiceDisconnected中重连。
以上两种方法区别在于:onServiceDisconnected在UI线程中,而DeathRecipient binderDeath方法是在Binder线程池中被回调。
.
.

AIDL权限验证

在服务端Service的onBind方法进行验证,或在onTransact方法进行权限验证。
验证的方法有多种,比如设置Service的permission,或者通过getCallingUid或getCallingPid做验证。

服务端

<uses-permission android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" /><service            android:name=".IService"            android:enabled="true"            android:exported="true"            android:permission="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE"            android:process=":remote">            <intent-filter>                <action android:name="com.thh.ipcdemo2aidl.action.BOOK_SERVICE" />            </intent-filter>        </service><permission        android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE"        android:protectionLevel="normal" />
 @Override    public IBinder onBind(Intent intent) {        // 第一种远程调用的验证方式        int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");        if (check == PackageManager.PERMISSION_DENIED){            Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED");            return null;        }        return mBinder;    }
@Override        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {            // 第二种远程调用的验证方式,权限验证            int check = checkCallingOrSelfPermission("com.thh.ipcdemo2aidl.permission.BOOK_SERVICE");            if (check == PackageManager.PERMISSION_DENIED){                Log.i("thhi","[IService onBind] check == PackageManager.PERMISSION_DENIED");                return false;            }            String packageName = null;            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());            if (packages!=null && packages.length > 0){                packageName = packages[0];            }            if (!packageName.startsWith("com.thh")){                return false;            }            return super.onTransact(code, data, reply, flags);        }

客户端

<uses-permission android:name="com.thh.ipcdemo2aidl.permission.BOOK_SERVICE" />
Intent intent = new Intent();        intent.setAction("com.thh.ipcdemo2aidl.action.BOOK_SERVICE");        Intent explicitIntent = getExplicitIntent(this, intent);        bindService(explicitIntent, new ServiceConnection() {            @Override            public void onServiceConnected(ComponentName name, IBinder service) {                mBookManager = IBookManager.Stub.asInterface(service);                try {                    Log.i("thhi", "[MainActivity onServiceConnected] bookList:" + mBookManager.getBookList());                } catch (RemoteException e) {                    e.printStackTrace();                }            }            @Override            public void onServiceDisconnected(ComponentName name) {            }        }, BIND_AUTO_CREATE);
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {        // Retrieve all services that can match the given intent        PackageManager pm = context.getPackageManager();        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);        // Make sure only one match was found        if (resolveInfo == null || resolveInfo.size() != 1) {            return null;        }        // Get component info and create ComponentName        ResolveInfo serviceInfo = resolveInfo.get(0);        String packageName = serviceInfo.serviceInfo.packageName;        String className = serviceInfo.serviceInfo.name;        ComponentName component = new ComponentName(packageName, className);        // Create a new intent. Use the old one for extras and such reuse        Intent explicitIntent = new Intent(implicitIntent);        // Set the component to be explicit        explicitIntent.setComponent(component);        return explicitIntent;    }

示例代码:https://github.com/huivs12/IPCDemo2AIDL

0 0
原创粉丝点击