Android开发进阶—跨进程通信(IPC)使用AIDL(上)

1.Android跨进程通信

       IPC是Inter-Process Communication的缩写,含义就是进程间通信或者跨进程通信,是指两个进程之间进行数据交互的过程。在说起进程间通信之前我们需要了解什么是进程,什么又是线程。在操作系统中线程指的是CPU调度的最小单元,同时在一个设备中线程是一种有限的系统资源。而进程一般指的是一个执行单元,在PC或者移动设备中指一个程序或者一个应用,因此一个进程可以包含多个线程,进程与线程之间是包含与被包含的关系。

2.Android中的多进程模式

       说起使用IPC机制就必须要提到多进程,因为只有面对多进程的这种场景下才需要考虑使用进程间的通信。在Android中使用多进程的情况大致分为以下两种:

       (1)应用因为某些原因需要采用多进程的模式来实现,至于原因可能有很多种,比如有些模块由于特殊原因需要单独运行在一个进程中,又或者一个应用需要使用更多的内存所以需要通过多进程来获取多份的内存空间。Android对单个应用所使用的最大内存做了限制,早期的一些版本可能是16MB,不同的设备有不同的大小。

       (2)当前的应用需要向其它应用获取数据,由于是两个应用,所以必须采用跨进程的方式来获取所需的数据。

       在同一个应用中我们只需要给四大组件指定android:process属性,就可以轻易的开启多进程模式,这看起来很简单但是在使用的过程中会出现各种各样的问题。在Android系统中每一个应用都会分配一个独立的虚拟机,或者说为每一个进程分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多个副本。所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进行所带来的主要影响。一般的来说使用多进程会造成如下几个方面的问题:

       (1)静态成员和单例模式完全失效

       (2)线程同步机制完全失效

       (3)SharedPreferences的可靠性下降

       (4)Application会多次创建

3.AIDL是什么?

       AIDL(Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

4.在同一个应用中使用AIDL来进行不同进程间的通信

       使用AIDL来进行进程间通信的流程,主要分为服务端和客户端两个方面

       (1)服务端

       服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

       (2)客户端

       客户端首先要绑定服务端的Service,在绑定成功以后将服务端onBind()方法返回的Binder对象转成AIDL接口所属的类型,接下来就可以通过转成的AIDL接口所属的类型来调用AIDL其中的方法了。

       因为我们此次所要传递的数据类型是一个对象,不是默认的数据类型,所以我们首先先要创建Book的类(Book.java),并且实现Parcelable接口

package com.example.administrator.aidldemo;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by ChuPeng on 2017/3/15.
 */

public class Book implements Parcelable
{
    private String name;
    private int price;

    public Book()
    {

    }

    protected Book(Parcel in)
    {
        name = in.readString();
        price = in.readInt();
    }

    public void writeToParcel(Parcel dest, int flags)
    {
        dest.writeString(name);
        dest.writeInt(price);
    }

    public int describeContents()
    {
        return 0;
    }

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

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

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public int getPrice()
    {
        return price;
    }

    public void setPrice(int price)
    {
        this.price = price;
    }
}

      在AIDL文件中,不是所有的数据类型都是可以使用的,AIDL文件所支持的数据类型如下所示:

  • 基本数据类型(int、long、char、boolean、double等)
  • String和CharSequence
  • List:只支持ArrayList,里面的每个元素的类型都必须能够被AIDL支持
  • Map:只支持HashMap,里面的每个元素的类型都必须能够被AIDL支持,包括Key和value
  • Parcelable:所有实现了Parcelable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

       在Android Studio中可以直接新建AIDL文件,入下图所示

Android开发进阶—跨进程通信(IPC)使用AIDL(上)_第1张图片

       其次我们需要创建AIDL文件(IBookManager.aidl),并且在里面声明一个接口和两个接口方法  

// IBookManager.aidl
//第二类AIDL文件
//作用是定义方法接口
package com.example.administrator.aidldemo;

// Declare any non-default types here with import statements
// 声明任何非默认类型和导入语句
//导入所需要使用的非默认支持数据类型的包
import com.example.administrator.aidldemo.Book;

interface IBookManager
{
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    List getBooks();

    Book getBook();

    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
    void addBook(in Book book);
}
        在IBookManager.aidl文件中由于使用了非默认支持的数据类型,因此需要通过使用import com.example.administrator.aidldemo.Book;来手动的导入上一步我们自己新建的Book.java,否则会在编译中报错。另外一个需要注意的地方是,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并且在其中声明为parcelable类型(parcelable是小写),在上面的IBookManager.aidl中用到了Book.java的类,所以我们必须要创建Book.aidl。

       除此之外,AIDL中除了基本数据类型,其它类型的参数必须标上方向:in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,最好根据实际需求来指定参数类型,不能一概使用out或者inout,因为在底层的实现是有开销的。最后,AIDL接口中只支持方法,不支持声明静态常量,这一点有别于传统的接口。

// Book.aidl
//第一类AIDL文件
//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
//注意:Book.aidl与Book.java的包名应当是一样的
package com.example.administrator.aidldemo;

//注意parcelable是小写
parcelable Book;

       接下来需要在服务端的Service中实现IBookManager.aidl中暴露的方法

package com.example.administrator.aidldemo;

import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by ChuPeng on 2017/3/15.
 */

public class BookManagerService extends Service
{

    private static final String TAG = "BookManagerService";
    //包含Book对象的list
    private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList();
    private String processName;
    private Book newBook;
    private int pid;
    //首先初始化,在 onCreate() 方法里面我进行了一些数据的初始化操作
    public void onCreate()
    {
        super.onCreate();
        Book book = new Book();
        book.setName("Android开发艺术探索");
        book.setPrice(79);
        newBook = new Book();
        mBookList.add(book);
        //得到当前进程的pid
        pid = Process.myPid();
        //通过pid得到进程名
        ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        for(ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses())
        {
            if(process.pid == pid)
            {
                processName = process.processName;
                break;
            }
        }
        Log.d(TAG, "当前进程为:" + processName);
    }
    //其次重写BookManager.Stub中的方法,这里面提供AIDL里面定义的方法接口的具体实现逻辑
    //由AIDL文件生成的BookManager
    private final IBookManager.Stub mBookManager = new IBookManager.Stub()
    {
        public List getBooks() throws RemoteException
        {
            return mBookList;
        }

        public Book getBook() throws RemoteException
        {
            return newBook;
        }

        public void addBook(Book book) throws RemoteException
        {
            mBookList.add(book);
        }
    };
    //最后重写onBind()方法,在里面返回写好的BookManager.Stub
    public IBinder onBind(Intent intent)
    {
        return mBookManager;
    }
}

       以上的代码是服务端Service的典型实现,首先在onCreate()中初始化添加了一本图书的信息,然后创建了一个Binder的对象mBookManager并且在onBind()方法中返回这个对象。这个对象继承自IBookManager.Stub()并且实现了内部暴露的方法。这里使用的CopyOnWriteArrayList是为了支持并发的读/写,AIDL的方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候会同时存在多个线程同时访问的情况,因此这里使用CopyOnWriteArrayList来进行自动的线程同步。

       最后还需要在AndroidManifest.xml中将BookManagerService设置运行在独立进程中,这样就和客户端构成了多进程的模式。



    
        
            
                
                
            
        
        
    

       在完成服务端以后还需要实现客户端,与服务端相比较客户端就比较简单了,在客户端中首先需要绑定远程服务,在绑定成功以后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。

package com.example.administrator.aidldemo;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
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.View;
import android.widget.Button;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity
{
    private static final String TAG = "MainActivity";
    private Button bindButton;
    private List bookList;
    private String processName;
    private int pid;
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindButton = (Button) findViewById(R.id.bind);
        bookList = new ArrayList();
        //得到当前进程的pid
        pid = Process.myPid();
        //通过pid得到进程名
        ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        for(ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses())
        {
            if(process.pid == pid)
            {
                processName = process.processName;
                break;
            }
        }
        bindButton.setOnClickListener(new View.OnClickListener()
        {
            public void onClick(View v)
            {
                Intent intent = new Intent(MainActivity.this, BookManagerService.class);
                bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
            }
        });
    }

    private ServiceConnection serviceConnection = new ServiceConnection()
    {
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try
            {
          
                List listAfter = bookManager.getBooks();
                Log.d(TAG, "当前进程为:" + processName);
                Log.d(TAG, listAfter.getClass().getCanonicalName());
                Log.d(TAG, "当前书的数量为:" + listAfter.size());
                Book book = new Book();
                book.setName("Android进阶");
                book.setPrice(46);
                bookManager.addBook(book);
                List bookList = bookManager.getBooks();
                Log.d(TAG, "当前书的数量为:" + bookList.size());
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName name)
        {
            Log.d(TAG, "binder died");
        }
    };

    protected void onDestroy()
    {
        unbindService(serviceConnection);
        super.onDestroy();
    }
}

       在绑定成功以后,会通过bookManager去调用addBook()方法和getBooks()方法,需要注意的是在调用服务端的方法时,客户端会先被挂起,被调用的方法会运行在服务端的Binder线程池中,这个时候如果服务端方法执行比较耗时就会导致客户端线程长时间低阻塞在这里,而如果这个客户端线程指UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的,因此,如果我们明确某个服务端的方法是耗时的,那么就要避免在在客户端的UI线程中去访问远程方法。

   Android开发进阶—跨进程通信(IPC)使用AIDL(上)_第2张图片

       运行之后我们可以通过log,很显然BookManagerService和MainActivity运行不在一个进程中,可以看出通过调用服务端的addBook()方法添加了一本书,并且通过调用服务端的getBook()方法得到了服务端的mBookList,并且将mBookList中的内容打印出来。

5.使用观察者模式来完成进程之间的通信

       现在还需要考虑一种情况,如果用户不想经常去查询图书列表,想等有新书来了就直接通知他。其实这就是一种典型的观察者模式,这种模式在实际的开发中用的很多,下面我们就在上面代码的基础上来增加观察者模式。

       首先,我们要提供一个AIDL接口,每个用户都必须实现这个接口并且向图书馆申请新书的提醒功能和取消提醒功能,这时应该创建一个IOnNewBookArrivedListener.aidl文件。

// IOnNewBookArrivedListener.aidl
package com.example.administrator.aidldemo;

// 声明任何非默认类型和导入语句
//导入所需要使用的非默认支持数据类型的包
import com.example.administrator.aidldemo.Book;

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

       除了要新添加一个AIDL接口还需要在原有的接口中添加注册和取消注册这两个新方法。

// IBookManager.aidl
//第二类AIDL文件
//作用是定义方法接口
package com.example.administrator.aidldemo;

// Declare any non-default types here with import statements
// 声明任何非默认类型和导入语句
//导入所需要使用的非默认支持数据类型的包
import com.example.administrator.aidldemo.Book;
import com.example.administrator.aidldemo.IOnNewBookArrivedListener;
interface IBookManager
{
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    List getBooks();

    Book getBook();

    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
    void addBook(in Book book);

    //注册提醒功能
    void registerListener(IOnNewBookArrivedListener listener);

    //取消注册提醒功能
    void unregisterListener(IOnNewBookArrivedListener listener);
}

       接下来还需要在BookManagerService中进行修改,这里通过开启一个线程,每隔5秒钟添加一本新书来模拟新书到达书库的过程。

package com.example.administrator.aidldemo;

import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by ChuPeng on 2017/3/15.
 */

public class BookManagerService extends Service
{

    private static final String TAG = "BookManagerService";
    //包含Book对象的list
    private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList();
    private RemoteCallbackList mListenerList = new RemoteCallbackList();
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    private String processName;
    private Book newBook;
    private int pid;
    private int bookId = 0;
    private int bookPrice = 10;
    //首先初始化,在 onCreate() 方法里面我进行了一些数据的初始化操作
    public void onCreate()
    {
        super.onCreate();
        //得到当前进程的pid
        pid = Process.myPid();
        //通过pid得到进程名
        ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        for(ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses())
        {
            if(process.pid == pid)
            {
                processName = process.processName;
                break;
            }
        }
        Log.d(TAG, "当前进程为:" + processName + "执行onCreate()方法");
        new Thread(new ServiceWorker()).start();
    }
    //其次重写BookManager.Stub中的方法,这里面提供AIDL里面定义的方法接口的具体实现逻辑
    //由AIDL文件生成的BookManager
    private final IBookManager.Stub mBookManager = new IBookManager.Stub()
    {
        public List getBooks() throws RemoteException
        {
            return mBookList;
        }

        public Book getBook() throws RemoteException
        {
            return newBook;
        }

        public void addBook(Book book) throws RemoteException
        {
            mBookList.add(book);
        }

        public void registerListener(IOnNewBookArrivedListener listener)
        {
            //新用户进行注册
            mListenerList.register(listener);
        }

        public void unregisterListener(IOnNewBookArrivedListener listener)
        {
            //已经注册的用户取消注册
            mListenerList.unregister(listener);
        }

    };
    //最后重写onBind()方法,在里面返回写好的BookManager.Stub
    public IBinder onBind(Intent intent)
    {
        Log.d(TAG, "当前进程为:" + processName + "执行onBind方法");
        return mBookManager;
    }

    private class ServiceWorker implements Runnable
    {
        public void run()
        {
            while (!mIsServiceDestoryed.get())
            {
                try
                {
                    Thread.sleep(5000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                Book newBook = new Book();
                newBook.setName("newBook    " + String.valueOf(bookId));
                newBook.setPrice(bookPrice);
                bookId = bookId + 1;
                bookPrice = bookPrice + 10;
                //通知客户端新书到了
                noticeClient(newBook);
            }
        }
    }

    //新书到了通知客户端
    private void noticeClient(Book book)
    {
         try
        {
            final int n = mListenerList.beginBroadcast();
            mBookList.add(book);
            for(int i = 0; i < n; i++)
            {
                IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
                if(listener != null)
                {
                    //通过调用客户端的onNewBookArrived(),通知已经注册的用户
                    listener.onNewBookArrived(book);
                }
            }
            mListenerList.finishBroadcast();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

       最后还要对客户端代码进行修改,当有新书到来时,服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法,但是这个方法是在客户端的Binder线程池中执行的,因为,为了将数据更新到UI上面,我们还需要使用Handler将线程切换到主线程中。

package com.example.administrator.aidldemo;

import android.app.ActivityManager;
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.Process;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity
{
    private static final String TAG = "MainActivity";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
    private Button bindButton;
    private Button unbindButton;
    private ListView listView;
    private ListViewAdapter adapter;
    private List bookList;
    private String processName;
    private int pid;
    private IBookManager mRemoteBookManager;
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindButton = (Button) findViewById(R.id.bind);
        unbindButton = (Button) findViewById(R.id.unbind);
        listView = (ListView) findViewById(R.id.listView);
        bookList = new ArrayList();
        //得到当前进程的pid
        pid = Process.myPid();
        //通过pid得到进程名
        ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        for(ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses())
        {
            if(process.pid == pid)
            {
                processName = process.processName;
                break;
            }
        }
        bindButton.setOnClickListener(new View.OnClickListener()
        {
            public void onClick(View v)
            {
                Intent intent = new Intent(MainActivity.this, BookManagerService.class);
                bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
            }
        });
        unbindButton.setOnClickListener(new View.OnClickListener()
        {
            public void onClick(View v)
            {
                try
                {
                    Log.d(TAG, "unregister listener:" + mOnNewBookArrivedListener);
                    mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
                    unbindService(serviceConnection);
                    Log.d(TAG, "unbindService:" + serviceConnection);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }

            }
        });
    }

    private ServiceConnection serviceConnection = new ServiceConnection()
    {
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            //通过绑定服务拿到服务端返回的Binder对象并且转换成AIDL接口所属的类型
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try
            {
                mRemoteBookManager = bookManager;
                List listBefore = bookManager.getBooks();
                Log.d(TAG, "当前进程为:" + processName);
                Log.d(TAG, listBefore.getClass().getCanonicalName());
                Log.d(TAG, "当前书的数量为:" + listBefore.size());
                //对需要提醒的客户通过调用服务端的registerListener()方法进行注册
                bookManager.registerListener(mOnNewBookArrivedListener);
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName name)
        {
            mRemoteBookManager = null;
            Log.d(TAG, "binder died");
        }
    };

    private Handler mHandler = new Handler()
    {
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    try
                    {
                        Log.d(TAG, "receive new book:" + msg.obj);
                        List newList = mRemoteBookManager.getBooks();
                        Log.d(TAG, "当前书的数量为:" + newList.size());
                        bookList.add((Book) msg.obj);
                        adapter = new ListViewAdapter(MainActivity.this, bookList);
                        adapter.notifyDataSetChanged();
                        listView.setAdapter(adapter);
                    }
                    catch(Exception e)
                    {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    //需要注册提醒功能的用户
    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub()
    {
        public void onNewBookArrived(Book newBook) throws RemoteException
        {
            Message msg = new Message();
            msg.what = MESSAGE_NEW_BOOK_ARRIVED;
            msg.obj = newBook;
            mHandler.sendMessage(msg);
        }
    };

    protected void onDestroy()
    {
        if(mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive())
        {
            try
            {
                Log.d(TAG, "unregister listener:" + mOnNewBookArrivedListener);
                mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
                unbindService(serviceConnection);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }
}

       在这里需要注意的是,在服务端的注册和取消注册这里的代码,通常写这里的代码都会这样写

private CopyOnWriteArrayList mListenerList = new CopyOnWriteArrayList();
public void registerListener(IOnNewBookArrivedListener listener)
{
    //新用户进行注册
    if(!mListenerList.contains(listener))
    {
	mListenerList.add(listener);
    }
    else
    {
	Log.d(TAG, "already exists");
    }
    Log.d(TAG, "current size:" + mListenerList.size());
}
public void unregisterListener(IOnNewBookArrivedListener listener)
{
    //已经注册的用户取消注册
    if(mListenerList.contains(listener))
    {
	mListenerList.remove(listener);
	Log.d(TAG, "unregister listener succeed");
    }
    else
    {
	Log.d(TAG, "not find, can not unregister");
    }
    Log.d(TAG, "current size:" + mListenerList.size());
}
       如果要是这样写,在取消注册的过程中会出问题,会打印出not find, can not unregister,其实这种方法如果放到同一进程中是没有问题的,但是放到多进程的环境下就无法奏效了,这是因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然我们在注册和取消注册的过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后会产生两个全新的对象。别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的对象都必须要实现Parcelable接口的原因。

       在这里如果要实现解除注册的功能就需要使用RemoteCallbackList,RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口,它的工作原理很简单,在内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型的,value是Callback类型的,其中Callback中封装了真正的远程listener,当客户端进行注册listener的时候会把这个listener的信息存入到Callback中,其中key和value通过以下方式获取到。

IBinder key = listener.asBinder();
Callback value = new Callback(listener, cookie);

       看到这里就应该明白了,虽然说多次跨进行传输客户端的同一个对象会在服务端生成不同的对象,但是这些生成的对象都有一个共同的特点,那就是底层的Binder对象是同一个,所以只要利用这个特性就可以分辨出客户端传递过来的对象对应的是服务端的哪一个。当客户端需要取消注册的时候,我们只需要遍历服务端所有的listener,并且找到那个和取消注册listener具有相同Binder对象的服务端listener,然后进行删除即可,这就是RemoteCallbackList为我们做的事情。另外,当客户端进程终止后它还能自动移出客户端所注册的listener,同时在RemoteCallbackList的内部还自动实现了线程同步的功能,所以在使用它的时候不需要考虑线程同步的问题。

private RemoteCallbackList mListenerList = new RemoteCallbackList();
public void registerListener(IOnNewBookArrivedListener listener)
{
    //新用户进行注册
    mListenerList.register(listener);
}
public void unregisterListener(IOnNewBookArrivedListener listener)
{
    //已经注册的用户取消注册
   mListenerList.unregister(listener);
}

       这是运行程序就会得到如下的样子

        Android开发进阶—跨进程通信(IPC)使用AIDL(上)_第3张图片

       当点击注册按钮时,每隔5秒中服务端会通知客户端有新书,客户端接收到数据后会将数据添加到列表中

6.总结

       当客户端调用远程方法的时候,被调用的方法会运行在服务端的Binder线程池中,同时客户端的当前线程会先挂起,如果这个时候调用服务端的方法比较耗时,就会导致客户端线程长时间低阻塞在这里,而如果此时这个客户端的线程是UI线程的话,就会导致ANR,因此如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中访问。

      同理当服务端要远程调用客户端的listener中的方法时,被调用的方法会运行在客户端的Binder线程池中,同时服务端的当前线程会先挂起,所以我们同样不能在服务端中调取客户端耗时的方法,除非是在服务端的非UI线程中调取。



       以上Demo的源代码地址:点击打开链接






















你可能感兴趣的:(Android开发进阶—跨进程通信(IPC)使用AIDL(上))