Android IPC之Messenger和AIDL(android开发艺术探索随笔)

最近感觉自己越来越菜,感觉自己什么都懂一点点,什么也不会。于是决定静下心来学习学习。所以,从看开发艺术说起。今天重新看了看第二章,感觉有了不一样的收获。那么今天就来记录下。
Messenger,信使。
AIDL(Android Interface definition language),android接口定义语言。实现跨进程通讯的一种方式。

使用Messenger

  1. 服务端进程
    我们需要在服务端创建一个Service来处理客户端的链接请求。同时创建一个Handler来通过他创建Messenger对象,然后在onBind中返回Messenger对象底层的Binder对象。
  2. 客户端进程
    绑定服务端的service,利用服务端返回的IBinder对象创建Messenger,通过这个Messenger对象发送Message类型的消息。如果需要服务端的响应消息,那么只需要利用Hnadler创建一个Messenger对象,并把这个对象通过Message的replyTo参数传递给服务端,服务端就可以通过这个replyTo参数就可以回应客户端。

我们来看个例子:

服务端Service,并将这个service开启到另一个进程

public class MessengerService extends Service {

    public static final String TAG = "MessengerService";

    public static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what){
                case 0: //来自客户端的消息
                    Log.e(TAG,"receive msg from Client"+msg.getData().getString("msg"));
                    Messenger client = msg.replyTo;
                    Message replyMessage = Message.obtain(null,1);
                    Bundle data = new Bundle();
                    data.putString("reply","hello ,i can shoudao you message");
                    replyMessage.setData(data);
                    try {
                        client.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    }

    private final Messenger mMessenger = new Messenger(new MessengerHandler());
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        Log.e("tag","bind success");
        return mMessenger.getBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
}

客户端Activity

public class MessengerActivity extends AppCompatActivity {

    private static final String TAG = "MessengerActivity";
    private Messenger mService;
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e("tag","can ");
            mService = new Messenger(service);
            Message msg = Message.obtain(null,0);
            Bundle data = new Bundle();
            data.putString("msg","hello,this is client");
            msg.setData(data);
            msg.replyTo = mGetReplyMessenger;
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                Log.e("tag","send msg errormessage:-->"+e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {

            Log.e("tag","recive msg from service:"+msg.getData().getString("reply"));
        }
    }
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);

        button = (Button) findViewById(R.id.bind);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MessengerActivity.this,MessengerService.class);
                bindService(intent,conn, Context.BIND_AUTO_CREATE);
            }
        });
    }

    @Override
    protected void onDestroy() {

        unbindService(conn);
        super.onDestroy();

    }
}

如果还有不懂的,请看张鸿洋 或者看书。

  • Messenger以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务器,就不太合适了
  • Messenger主要目的是为了传递消息,很多时候我们可能需要调用服务端的方法,这种情况Messenger无法做到,这个时候,我们就需要使用AIDL了。
  • -

使用AIDL

  1. 服务端
    创建一个Service用来监听客户端的连接请求,然后创建AIDL文件,将暴漏给客户端的接口在这个文件中声明,最后在service中实现这个AIDL接口
  2. 客户端
    绑定服务端service,将服务端返回的Binder对象转化为AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

那么,接下来我就按照书上的内容来做一个demo。

首先将Book序列化

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    public Book(){

    }
    public Book(int bookId,String bookName){
        this.bookId = bookId;
        this.bookName = bookName;
    }
    @Override
    public int describeContents() {
        return 0;
    }

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

    public static final Parcelable.Creator<Book> CREATOR = new ClassLoaderCreator<Book>() {

        @Override
        public Book createFromParcel(Parcel source, ClassLoader loader) {
            return new Book(source);
        }

        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

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

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

    @Override
    public String toString() {

        return String.format("[bookId:%s, bookName:%s]",bookId,bookName);
    }
}

接着,创建IBookManager.aidl和Book.aidl,注意Book.aidl创建的时候创建个文件,起名交Book.aidl,若直接在as下创建AIDL文件,会提示以存在。
Book.aidl

package com.gl.android_yishu;

parcelabl

IBookManager.aidl,注意,要将除了一些基础类型以外的其他类型用import导进来

package com.gl.android_yishu;

// Declare any non-default types here with import statements
import com.gl.android_yishu.Book;
import com.gl.android_yishu.IOnNewBookArrivedListener;
interface IBookManager {
    /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);

}

IOnNewBookArrivedListener.aidl (监听器)

package com.gl.android_yishu;

// Declare any non-default types here with import statements
import com.gl.android_yishu.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book book);
}

将这个aidl文件创建好之后,rebuild,就会在build/source/aidl/debug 下面生成对应的java文件。我在这里不管文件的内容。(我会在后面学习Binder的时候记录)。

接下来看看service的写法。

public class BookManagerService extends Service {

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    private RemoteCallbackList<IOnNewBookArrivedListener> mListener = new
            RemoteCallbackList<IOnNewBookArrivedListener>();

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    private Binder mBinder = new IBookManager.Stub(){

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @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 {
            mListener.register(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListener.unregister(listener);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.

        int check = checkCallingOrSelfPermission("com.gl.android_yishu.permission.ACCESS");
        if (check == PackageManager.PERMISSION_DENIED){
            Log.e("tag","权限认证失败");
        }else{
            Log.e("tag","onbind---->");
            return mBinder;
        }
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mBookList.add(new Book(0, "Android"));
        mBookList.add(new Book(1,"IOS"));
        new Thread(new ServiceWorker()).start();
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        int N = mListener.beginBroadcast();
        Log.e("tag","onNewBookArrived , notify listeners" +N);
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListener.getBroadcastItem(i);
            listener.onNewBookArrived(book);
        }
        mListener.finishBroadcast();
    }

    private class ServiceWorker implements Runnable {
        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()){
                try {
                    Thread.sleep(1000);
                } 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();
                }
            }
        }
    }
}

代码不多,也不复杂。

  • CopyOnWriteArrayList 支持并发读写,这个类不是继承List,但是在Binder中会按照List的规范去访问数据并最后形成一个新的ArrayList传给客户端。所以,即使这个不是List,我们仍然可以使用。
  • RemoteCallbackList 是系统专门提供的用户删除跨进程listener的接口。为什么要使用这个,因为对象不能进行跨进程直接传输,跨进程直接传输实际上是反序列化的过程。因此,若不用这个类,客户端和服务器的listener就不是同一个,也就无法unregist。
  • 权限认证,我们只希望拥有权限的才能绑定,就可以自定义权限,并在OnBind中进行权限认证,当然,我们自己的应用是需要用uses-permission标签来声明权限的。
  • 在使用了RemoteCallbackList之后,我们需要注意,以下面的格式为准
int N = mListener.beginBroadcast();
        Log.e("tag","onNewBookArrived , notify listeners" +N);
        for (int i = 0; i < N; i++) { IOnNewBookArrivedListener listener = mListener.getBroadcastItem(i); listener.onNewBookArrived(book); }
        mListener.finishBroadcast();

以begin开头,以finish结束。
我们来看下客户端的代码

public class MainActivity extends AppCompatActivity {

    private Button messenger,aidl;

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

        }
    };

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

    private ServiceConnection conn;

    {
        conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager bookManager = IBookManager.Stub.asInterface(service);
                try {
                    service.linkToDeath(new IBinder.DeathRecipient() {
                        @Override
                        public void binderDied() {
                            bind();
                        }
                    },0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                try {
                    List<Book> list = bookManager.getBookList();
                    Log.e("tag", "query book list,list type :" + list.getClass().getCanonicalName());
                    Log.e("tag", "query book list :" + list.toString());
                    bookManager.registerListener(mListener);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
// bind();
            }
        };
    }

    private void initView() {
        messenger = (Button) findViewById(R.id.messenger);
        messenger.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MessengerActivity.class);
                startActivity(intent);
            }
        });
        aidl = (Button) findViewById(R.id.AIDL);
        aidl.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bind();
            }


        });

    }

    private void bind() {
        Intent intent = new Intent(MainActivity.this,BookManagerService.class);
        bindService(intent,conn,Context.BIND_AUTO_CREATE);
    }

    private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub(){

        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            handler.obtainMessage(1,book).sendToTarget();
        }
    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

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

没什么特别的。不过,下面一点需要注意:

Binder是可能会意外死亡的。我们可以通过重连来解决。
1.

try {
                    service.linkToDeath(new IBinder.DeathRecipient() {
                        @Override
                        public void binderDied() {
                            bind();
                        }
                    },0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

bind()函数自己看。
2.在onServiceDisconnected()方法中重连

这2中方法的区别,第一种在客户端的Binder线程池中被毁掉,也就是说我们不能直接访问UI线程,而第二种则是在UI线程中被回调。

还有一点需要注意:客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中 ,同时,客户端线程会被挂起,那么,我们就不能再这个时候进行耗时操作。客户端的onServiceconnected()和onServiceDisconnected()方法都运行在UI线程中,所以我们也不能用来惊醒耗时操作。另外,由于服务端方法运行在Binder线程池中,本身就是耗时的,所以尽量不要开线程去进行异步任务。

最后,还有一种Binder验证的方法,就是在onTranscat()方法中进行认证。关于AIDL和Messenger,就这么多了。

你可能感兴趣的:(android,aidl,messenger)