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

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.aidl
package com.thh.ipcdemo2aidl.aidl;

// Declare any non-default types here with import statements
import 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值