IPC机制

一、IPC简介

(1)IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。

(2)ANR是Application Not Responding的缩写,即应用无响应。主线程执行大量的耗时操作容易导致ANR现象发生。

(3)在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。

(4)Android还支持Socket,通过Socket也可以实现任意两个终端或者两个进程之间的通信。

二、Android中的多进程模式

1、在Android中使用多进程只有一种方法:

就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process属性。

可以在Eclipse的DDMS视图中查看进程信息,还可以用shell来查看,命令为:adb shell ps 或者 adb shell ps|grep com.ryg.chapter_2。

<activity  
    android:name=".MainActivity"  
    android:configChanges="orientation|screenSize"  
    android:label="@string/app_name"  
    android:launchMode="standard" >  
    <intent-filter>  
        <action android:name="android.intent.action.MAIN" />  
    </intent-filter>  
</activity>  
<activity  
    android:name=".SecondActivity"  
    android:configChanges="screenLayout"  
    android:label="@string/app_name"  
    android:process=":remote" />  
<activity  
    android:name=".ThirdActivity"  
    android:configChanges="screenLayout"  
    android:label="@string/app_name"  
    android:process="com.ryg.chapter_2.remote" /> 

上面的代码中,

(1)MainActivity没有指定process属性,所以它运行在默认的进程中,默认进程的进程名是包名。

(2)SecondActivity会运行在一个单独的进程中,进程名为“com.ryg.chapter_2:remote”,其中com.ryg.chapter_2是包名。在程序中的冒号“:”的含义是指要在当前的进程名前面附加上当前的包名,是一种简写的方法。而且以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。

(3)ThirdActivity会运行在另一个单独的进程中,进程名为“com.ryg.chapter_2.remote”。这是一种完整的命名方式。属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

注意点一:Android系统会为每一个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。要求两个应用具有相同的ShareUID并且签名相同才可以跑在同一个进程中。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。

2、多进程模式的运行机制

(1)多进程会带来很多意想不到的麻烦,因为Android为每一个应用都分配了一个独立的虚拟机,或者说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。这样很就容易导致数据不同步。

(2)所有运行在不同进程的四大组件,只要它们之间需要通过内存在共享数据,都会共享失败。

(3)主要有以下四方面的问题:

1)静态成员和单例模式完全失效。(由独立虚拟机造成)

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

3)SharedPreferences的可靠性下降。(存在并发读写的问题)

4)Application会多次创建。(新的进程中又会导致进程所在的Application在新的虚拟机中再次创建)

(4)运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的,同理运行在不同进程的组件是属于两个不同的虚拟机和Application的。

基于上面的这些问题,因为我们需要学习进程间通信机制!!!!!

三、IPC基础概念介绍

当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializeble。Serializable和Parcelable接口可以完成对象的序列化过程。还有时候我们需要把对象持久化到存储 设备上或者通过网络传输给其他客户端,这个时候也需要Serializable来完成对象的持久化。

1、Serializable接口

(1)Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化非常简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。

public class User implements Serializable{  
    private static final long serialVersionUID = 8723148825838841922L;  
      
    public int userId;  
    public String userName;  
    public boolean isMale;  
}

(2)只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现对象的序列化和反序列化过程:

// 序列化过程:  
User user = new User(0,"jake",true);  
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));  
out.writeObject(user);  
out.close();  
  
// 反序列化过程:  
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));  
User newUser = (User)in.readObject();  
in.close();

注意点一:序列化和反序列化的对象并不是同一个对象!
(3)一般来说,我们应该收到指定serialVersionUID的值,比如1L,也可以让Eclipse根据当前类的结构自动去生成它的hash值。当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。

注意点二:静态成员变量属于类不属于对象,所以不会参与序列化过程。

注意点三:用transient关键字标记的成员变量不参与序列化过程。

2、Parcelable接口

(1)Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

public class User implements Parcelable {  
  
    public int userId;  
    public String userName;  
    public boolean isMale;  
      
    public Book book;  
  
    public User(int userId, String userName, boolean isMale) {  
        this.userId = userId;  
        this.userName = userName;  
        this.isMale = isMale;  
    }  
      
    /* 
     * 内容描述功能几乎都是直接返回0的。 
     * */  
    public int describeContents() {  
        return 0;  
    }  
  
    /* 
     * 序列化由writeToParcel方法来完成,最终是通过Parcel中一系列write方法来完成的。 
     * 其中flags标识有两种值:0和1(PARCELABLE_WRITE_RETURN_VALUE)。 
     * 为1时标识当前对象需要作为返回值返回,不能立即释放资源, 
     * 几乎所有情况都为0。 
     * */  
    public void writeToParcel(Parcel out, int flags) {  
        out.writeInt(userId);  
        out.writeString(userName);  
        out.writeInt(isMale? 1:0);  
        out.writeParcelable(book, 0);  
    }  
  
    /* 
     * 反序列化功能是由CREATOR来完成,其内部标明了如何创建序列化对象和数组, 
     * 并通过Parcel的一些了read方法来完成反序列化过程。 
     * */  
    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {  
        // 从序列化后的对象中创建原始对象。  
        public User createFromParcel(Parcel in) {  
            return new User(in);  
        }  
  
        // 创建指定长度的原始对象数组  
        public User[] newArray(int size) {  
            return new User[size];  
        }  
    };  
  
    /* 
     * Parcel内部包装了可序列化的数据,可以在Binder中自由传输。 
     * 从序列化后的对象中创建原始对象。 
     * */  
    private User(Parcel in) {  
        userId = in.readInt();  
        userName = in.readString();  
        isMale = in.readInt() == 1;  
        /* 
         * 由于book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器, 
         * 否则会报无法找到类的错误。 
         * */  
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());  
    }  
}  

(2)系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。

3、Parcelable接口和Serializable接口的比较

(1)Serializable用起来简单,但开销很大,序列化和反序列化过程都需要大量的I/O操作。

(2)Parcelable是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高,首选。主要用在内存序列化上。

四、Binder

1、Binder简介

(1)Binder实现了IBinder接口。

(2)从IPC角度来说,Binder是Android中的一种跨进程通信方式。Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,这种通信方式在Linux中没有。

(3)从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁。

(4)从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

(5)AIDL即Android interface definition Language,即Android接口定义语言。

2、在分析Binder的工作原理之前,我们先补充一下Android设计模式之Proxy模式

(1)Proxy代理模式简介

代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

模式的使用场景:就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

image
  • 抽象对象角色AbstarctObject:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。

  • 目标对象角色RealObject:定义了代理对象所代表的目标对象。

  • 代理对象角色ProxyObject:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。

(2)Proxy代理模式的简单实现

抽象对象角色:

public abstract class AbstractObject {  
    //操作  
    public abstract void operation();  
} 

目标对象角色:

public class RealObject extends AbstractObject {  
    @Override  
    public void operation() {  
        //一些操作  
        System.out.println("一些操作");  
    }  
} 

代理对象角色:

public class ProxyObject extends AbstractObject{  
    RealObject realObject = new RealObject();//目标对象角色  
    @Override  
    public void operation() {  
        //调用目标对象之前可以做相关操作  
        System.out.println("before");          
        realObject.operation();        //目标对象角色的操作函数  
        //调用目标对象之后可以做相关操作  
        System.out.println("after");  
    }  
}

客户端:

public class Client {  
    public static void main(String[] args) {  
        AbstractObject obj = new ProxyObject();  
        obj.operation();  
    }  
} 

(3)代理模式在Binder中的使用

直观来说,Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,etc)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Binder一个很重要的作用是:将客户端的请求参数通过Parcel包装后传到远程服务端,远程服务端解析数据并执行对应的操作,同时客户端线程挂起,当服务端方法执行完毕后,再将返回结果写入到另外一个Parcel中并将其通过Binder传回到客户端,客户端接收到返回数据的Parcel后,Binder会解析数据包中的内容并将原始结果返回给客户端,至此,整个Binder的工作过程就完成了。由此可见,Binder更像一个数据通道,Parcel对象就在这个通道中跨进程传输,至于双方如何通信,这并不负责,只需要双方按照约定好的规范去打包和解包数据即可。

为了更好地说明Binder,这里我们先手动实现了一个Binder。为了使得逻辑更清晰,这里简化一下,我们来模拟一个银行系统,这个银行提供的功能只有一个:即查询余额,只有传递一个int的id过来,银行就会将你的余额设置为id*10,满足下大家的发财梦。

1)先定义一个Binder接口(抽象对象角色):

package com.ryg.design.manualbinder;  
  
  
import android.os.IBinder;  
import android.os.IInterface;  
import android.os.RemoteException;  
  
  
public interface IBank extends IInterface {  
  
  
/* 
 * Binder的唯一标识符: 
 * */  
    static final String DESCRIPTOR = "com.ryg.design.manualbinder.IBank";  
  
  
    /* 
     * queryMoney方法的code标识: 
     * */  
    static final int TRANSACTION_queryMoney = (IBinder.FIRST_CALL_TRANSACTION + 0);  
  
  
    /* 
     * queryMoney方法声明: 
     * */  
    public long queryMoney(int uid) throws RemoteException;  
}  

2)创建一个Binder并实现上述接口:

package com.ryg.design.manualbinder;  
  
  
import android.os.Binder;  
import android.os.IBinder;  
import android.os.Parcel;  
import android.os.RemoteException;  
  
  
public class BankImpl extends Binder implements IBank {  
  
  
    public BankImpl() {  
        this.attachInterface(this, DESCRIPTOR);  
    }  
  
  
    /* 
     * 如果在同一进程,则返回目标对象本身, 
     * 如果在不同仅此,则返回代理类 
     * */  
    public static IBank asInterface(IBinder obj) {  
        if ((obj == null)) {  
            return null;  
        }  
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  
        if (((iin != null) && (iin instanceof IBank))) {  
            return ((IBank) iin);  
        }  
        return new BankImpl.Proxy(obj);  
    }  
  
  
    @Override  
    public IBinder asBinder() {  
        return this;  
    }  
  
  
    /* 
     * 这个onTransact方法是在目标对象角色中重写的, 
     * 在目标对象角色调用Transact方法时回调的! 
     * */  
    @Override  
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)  
            throws RemoteException {  
        switch (code) {  
        case INTERFACE_TRANSACTION: {  
            reply.writeString(DESCRIPTOR);  
            return true;  
        }  
        case TRANSACTION_queryMoney: {  
            data.enforceInterface(DESCRIPTOR);  
            int uid = data.readInt();  
            long result = this.queryMoney(uid);  
            reply.writeNoException();  
            reply.writeLong(result);  
            return true;  
        }  
        }  
        return super.onTransact(code, data, reply, flags);  
    }  
  
  
    /* 
     * 这是正儿八经的目标对象角色的queryMoney函数: 
     * */  
    @Override  
    public long queryMoney(int uid) throws RemoteException {  
        return uid * 10l;  
    }  
  
  
    /* 
     * 内部代理类(代理对象角色) 
     * */  
    private static class Proxy implements IBank {  
    /* 
     * 代表目标对象角色: 
     * */  
        private IBinder mRemote;  
  
  
        /* 
         * 构造函数: 
         * */  
        Proxy(IBinder remote) {  
        >// 接收目标对象角色:  
            mRemote = remote;  
        }  
  
  
        /* 
         * 返回目标对象角色: 
         * */  
        @Override  
        public IBinder asBinder() {  
            return mRemote;  
        }  
  
  
        /* 
         * 返回Binder唯一标识符: 
         * */  
        public java.lang.String getInterfaceDescriptor() {  
            return DESCRIPTOR;  
        }  
  
  
        /* 
         * 这是代理类Proxy中的queryMoney方法: 
         * */  
        @Override  
        public long queryMoney(int uid) throws RemoteException {  
        /* 
         * 先创建两个Parcel对象 
         * */  
            Parcel data = Parcel.obtain();  
            Parcel reply = Parcel.obtain();  
            long result;  
            try {  
            *  
             * 在操作前向data中写入一些数据:  
             * */  
                data.writeInterfaceToken(DESCRIPTOR);  
                data.writeInt(uid);  
                /* 
                 * 这里执行的mRemote.transact其实是目标对象角色的transact函数。 
                 * 因为mRemote是IBinder对象,所以调用它的transact函数会回调它的onTransact方法, 
                 * 这个onTransact方法是在mRemote这个目标对象角色中重写了的,哈 
                 * 然后要根据TRANSACTION_queryMoney的code代码来执行相应的函数。 
                 * data负责传递信息, 
                 * reply负责回收信息。 
                 * */  
                mRemote.transact(TRANSACTION_queryMoney, data, reply, 0);  
                /* 
                 * 这里是返回的数据。 
                 * */  
                reply.readException();  
                result = reply.readLong();  
            } finally {  
                reply.recycle();  
                data.recycle();  
            }  
            return result;  
        }  
    }  
}  

ok,到此为止,我们的Binder就完成了,这里只要创建服务端和客户端,二者就能通过我们的Binder来通信了。这里就不做这个示例了,我们的目的是分析代理模式在Binder中的使用。

我们看上述Binder的实现中,有一个叫做“Proxy”的类,它的构造方法如下:

Proxy(IBinder remote) {  
    mRemote = remote;  
} 

Proxy类接收一个IBinder参数,这个参数实际上就是服务端Service中的onBind方法返回的Binder对象在客户端重新打包后的结果,因为客户端无法直接通过这个打包的Binder和服务端通信,因此客户端必须借助Proxy类来和服务端通信,这里Proxy的作用就是代理的作用,客户端所有的请求全部通过Proxy来代理,具体工作流程为:Proxy接收到客户端的请求后,会将客户端的请求参数打包到Parcel对象中,然后将Parcel对象通过它内部持有的Ibinder对象传送到服务端,服务端接收数据、执行方法后返回结果给客户端的Proxy,Proxy解析数据后返回给客户端的真正调用者。很显然,上述所分析的就是典型的代理模式。至于Binder如何传输数据,这涉及到很底层的知识,这个很难搞懂,但是数据传输的核心思想是共享内存。

3、我们通过一个案例来分析Binder工作原理

我们需要新建一个AIDL示例,SDK会自动为我们生产AIDL所对应的Binder类。

(1)Book.java:这里面没有什么特殊之处,为了实现Parcelable,添加了几个方法,上面在Parcelable部分已经介绍过了。

package com.ryg.chapter_2.aidl;  
  
  
import android.os.Parcel;  
import android.os.Parcelable;  
  
  
/* 
 * (1)它是一个表示图示信息的类, 
 * 它实现了Parcelable接口,因为实现了Parcelable接口便可以进行序列化 
 * (2)Book.aidl是Book类在ADIL中的声明。 
 * (3)IBookManager.aidl是我们定义的一个接口,里面有两个方法:getBookList和addBook, 
 * 其中getBookList用于从远程服务端获取图书列表,而addBook用于往图书列表中添加一本书, 
 * 当然这两个方法主要是示例用,不一定要有实际意义。 
 * (4)尽管Book类和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类, 
 * 这就是AIDL的特殊之处。 
 * */  
public class Book implements Parcelable {      
<span style="white-space:pre">    </span>public int bookId;  
    public String bookName;      
      
    /* 
     * 普通构造函数: 
     * */  
    public Book() {      
    <span style="white-space:pre">  </span>  
    }      
      
    /* 
     * 普通构造函数: 
     * */  
    public Book(int bookId, String bookName) {  
        this.bookId = bookId;  
        this.bookName = bookName;  
    }  
      
    public int describeContents() {  
        return 0;  
    }  
  
  
    /* 
     * 序列化: 
     * */  
    public void writeToParcel(Parcel out, int flags) {  
        out.writeInt(bookId);  
        out.writeString(bookName);  
    }      
      
    /* 
     * 反序列化, 
     * 这个creator就是通过一个Parcle来创建一个book对象或者数组。 
     * */  
    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {  
        public Book createFromParcel(Parcel in) {  
            return new Book(in);  
        }          
        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);  
    }  
}  

(2)Book.aidl:它是Book在AIDL中的声明。

package com.ryg.chapter_2.aidl;  
  
parcelable Book; 

(3)IBookManager.aidl:虽然Book类已经和IBookManager位于相同的包中,但是这里依然需要导入Book类。这是AIDL的特殊之处。

它是一个接口,里面有四个方法。

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

(4)下面我们要看一下系统为IBookManager.aidl生产的Binder类,在gen目录下有一个IBookManager.java的类,这就是我们要找的类。

/* 
 * This file is auto-generated.  DO NOT MODIFY. 
 */  
package com.ryg.chapter_2.aidl;  
/* 
 * IBookManager它继承了IInterface这个接口,同时它自己也还是个接口, 
 * 所有可以在Binder中传输的接口都要继承IInterface接口。 
 * 首先,它声明了两个方法getBookList和addBook,显然这就是我们在IBookManager.aidl中所声明的方法, 
 * 同时它还声明了两个整型的id分别用于标识这两个方法。 
 * 接着,它声明了一个内部类Stub,这个Stub就是一个Binder类, 
 * 当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程, 
 * 而当两者位于不同进程时,方法调用需要走transact过程, 
 * 这个逻辑由Stub的内部代理类Proxy来完成。 
 * */  
public interface IBookManager extends android.os.IInterface  
{  
 /** Local-side IPC implementation stub class. */  
 /* 
  * 首先这个Stub,它是一个内部类,它继承了Binder,所以它是一个Binder, 
  * 同时Stub还实现了IBookManager中的方法。 
  * */  
 public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager  
 {  
  /* 
   * Binder的唯一标识符。 
   * */  
  private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";  
    
  /** Construct the stub at attach it to the interface. */  
  public Stub()  
  {  
   this.attachInterface(this, DESCRIPTOR);  
  }  
    
  /** 
   * Cast an IBinder object into an com.ryg.chapter_2.aidl.IBookManager interface, 
   * generating a proxy if needed. 
   */  
  /* 
   * 用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象, 
   * 这种转换过程是区分进程的, 
   * 如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身, 
   * 否则返回的是系统封装后的Stub.proxy代理对象。 
   * */  
  public static com.ryg.chapter_2.aidl.IBookManager asInterface(android.os.IBinder obj)  
  {  
   if ((obj==null)) {  
    return null;  
   }  
   android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  
   // 同一进程  
   if (((iin!=null)&&(iin instanceof com.ryg.chapter_2.aidl.IBookManager))) {  
    return ((com.ryg.chapter_2.aidl.IBookManager)iin);  
   }  
   // 不同进程  
   return new com.ryg.chapter_2.aidl.IBookManager.Stub.Proxy(obj);  
  }  
    
  /* 
   * 此方法用于返回当前Binder对象,也就是内部类Stub。 
   * */  
  @Override public android.os.IBinder asBinder()  
  {  
   return this;  
  }  
   
  /* 
   * 这个方法运行在服务端中的Binder线程池中, 
   * 当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。 
   * 服务端通过code可以确定客户端所请求的目标方法是什么, 
   * 接着从data中取出目标方法所需的参数, 
   * 然后执行目标方法。 
   * 当目标方法执行完毕后,就向reply中写入返回值。 
   * 如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证。 
   * */  
  @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException  
  {  
   switch (code)  
   {  
    case INTERFACE_TRANSACTION:  
    {  
     reply.writeString(DESCRIPTOR);  
     return true;  
    }  
    case TRANSACTION_getBookList:  
    {  
     data.enforceInterface(DESCRIPTOR);  
     /* 
      * 这句才是调用了真正的执行过程呢 
      * */  
     java.util.List<com.ryg.chapter_2.aidl.Book> _result = this.getBookList();  
     reply.writeNoException();  
     reply.writeTypedList(_result);  
     return true;  
    }  
    case TRANSACTION_addBook:  
    {  
     data.enforceInterface(DESCRIPTOR);  
     com.ryg.chapter_2.aidl.Book _arg0;  
     if ((0!=data.readInt())) {  
      _arg0 = com.ryg.chapter_2.aidl.Book.CREATOR.createFromParcel(data);  
     }  
     else {  
      _arg0 = null;  
     }  
     /* 
      * 这句才是调用了真正的执行过程呢 
      * */  
     this.addBook(_arg0);  
     reply.writeNoException();  
     return true;  
    }  
    case TRANSACTION_registerListener:  
    {  
     data.enforceInterface(DESCRIPTOR);  
     com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;  
     _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());  
     this.registerListener(_arg0);  
     reply.writeNoException();  
     return true;  
    }  
    case TRANSACTION_unregisterListener:  
    {  
     data.enforceInterface(DESCRIPTOR);  
     com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;  
     _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());  
     this.unregisterListener(_arg0);  
     reply.writeNoException();  
     return true;  
    }  
   }  
   return super.onTransact(code, data, reply, flags);  
  }  
    
  /* 
   * 代理类Proxy。 
   * */  
  private static class Proxy implements com.ryg.chapter_2.aidl.IBookManager  
  {  
   /* 
    * 这个mRemote代表的就是目标对象角色, 
    * */  
   private android.os.IBinder mRemote;  
     
   Proxy(android.os.IBinder remote)  
   {  
    mRemote = remote;  
   }  
     
   @Override public android.os.IBinder asBinder()  
   {  
    return mRemote;  
   }  
     
   public java.lang.String getInterfaceDescriptor()  
   {  
    return DESCRIPTOR;  
   }  
     
   /* 
    * 这个方法运行在客户端, 
    * 因为当客户端和服务端不在同一进程时,服务端返回代理类Proxy,所以客户端会通过Proxy调用到代理类的getBookList方法, 
    * 当客户端远程调用此方法时,它的内部实现是这样的: 
    * 首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List, 
    * 然后把该方法的参数信息写入_data中, 
    * 接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起, 
    * 然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行, 
    * 并从_reply中取出RPC过程的返回结果。 
    * 最后返回_reply中的数据。 
    * */  
   @Override public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException  
   {  
    android.os.Parcel _data = android.os.Parcel.obtain();  
    android.os.Parcel _reply = android.os.Parcel.obtain();  
    java.util.List<com.ryg.chapter_2.aidl.Book> _result;  
    try {  
    _data.writeInterfaceToken(DESCRIPTOR);  
    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);  
    _reply.readException();  
    _result = _reply.createTypedArrayList(com.ryg.chapter_2.aidl.Book.CREATOR);  
    }  
    finally {  
    _reply.recycle();  
    _data.recycle();  
    }  
    return _result;  
   }  
     
   @Override public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException  
   {  
    android.os.Parcel _data = android.os.Parcel.obtain();  
    android.os.Parcel _reply = android.os.Parcel.obtain();  
    try {  
     _data.writeInterfaceToken(DESCRIPTOR);  
     if ((book!=null)) {  
      _data.writeInt(1);  
      book.writeToParcel(_data, 0);  
     }  
     else {  
      _data.writeInt(0);  
     }  
     mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);  
     _reply.readException();  
    }  
    finally {  
     _reply.recycle();  
     _data.recycle();  
    }  
   }  
     
   @Override public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException  
   {  
    android.os.Parcel _data = android.os.Parcel.obtain();  
    android.os.Parcel _reply = android.os.Parcel.obtain();  
    try {  
     _data.writeInterfaceToken(DESCRIPTOR);  
     _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));  
     mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);  
     _reply.readException();  
    }  
    finally {  
     _reply.recycle();  
     _data.recycle();  
    }  
   }  
     
   @Override public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException  
   {  
    android.os.Parcel _data = android.os.Parcel.obtain();  
    android.os.Parcel _reply = android.os.Parcel.obtain();  
    try {  
     _data.writeInterfaceToken(DESCRIPTOR);  
     _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));  
     mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);  
     _reply.readException();  
    }  
    finally {  
     _reply.recycle();  
     _data.recycle();  
    }  
   }  
  }  
    
  /* 
   * 用于标识方法的整型id。 
   * 它们用于在transact过程总客户端所请求的到底是哪个方法。 
   * */  
  static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);  
  static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);  
  static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);  
  static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);  
   
 }  
   
 /* 
  * 声明了在IBookManager.aidl中所声明的方法。 
  * 这里才是真正的方法声明。具体实现我们仍然没有看到呢。 
  * */  
 public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException;  
 public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException;  
 public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;  
 public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;  
}  

注意点一:上面的Book类,就是一个可以Parcelable序列化的简单的Book类,它里面没有任何的方法,就是定义了一个简单的Book类结构。

注意点二:Book.aidl的存在是因为在IBookManager.aidl中出现的对象也必须有aidl声明。

注意点三:在IBookManager.aidl中,对于自动生成的IBookManager.java文件,它是服务器端的代码。当客户端向服务端发送连接请求时,如果客户端和服务端在同一进程中,那么服务端就向客户端返回Stub这个Binder对象,如果客户端和服务端在不同进程中,那么服务端就向客户端返回内部类Stub的内部代理类Proxy,然后客户端根据这个Proxy来调用Proxy内部的方法,这个Proxy内部含有服务端真正的Binder对象也就是那个内部类Stub,在客户端调用Proxy内部的方法也就会导致调用Stub的transact方法,而Stub的transact方法又会回调它自己的onTransact方法,onTransact方法是在服务端运行的,而transact方法是在客户端调用的,这样就实现了客户端调用服务端的方法了。当然这所有的传递过程也少不了Parcel这个数据包的协助。整个过程懂了吗?

image

这次应该完全懂了吧,再不懂去屎吧!

4、linkToDeath和unlinkToDeath

Binder运行在服务端进程,如果服务端进程由于某些原因异常终止,这个时候我们到服务端的Binder连接断裂,会导致我们的远程调用失败。Binder提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。

/* 
 * 声明这个接口就好: 
 * */  
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DearhRecipient(){  
    // 只需要重写这个方法就可以了。  
    @Override   
    public void binderDied(){  
        if(mBookManager == null)  
            return;  
        mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);  
        mBookManager = null;  
        // TODO: 这里重新绑定远程Service。  
    }  
}

在客户端绑定远程服务之后,给Binder设置死亡代理:

mService = IMessageBoxManager.Stub.asInterface(binder);  
binder.linkToDeath(mDeathRecipient, 0); 

五、使用Messenger

1、特点:

(1)Messenger对AIDL做了封装,使得我们可以更简便地进行进程间通信。由于它一次处理一个请求,所以在服务端我们不考虑线程同步的问题,因为服务端中不存在并发执行的情形。
(2)通过它可以在不同进程中传递Message对象,在Message中仿佛我们需要传递的数据,就可以轻松地实现数据的进程间传递了。
(3)有两个构造函数,分别接收Handler对象和IBinder对象。

2、 实现一个Messenger有如下步骤:

(1)服务端进程:
首先需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并以它作为参数来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。关键点就在于它的返回是返回给了要绑定这个服务端的客户端,然后客户端拿到这个Binder再去创建Messenger,再去发送Message等等。
(2)客户端进程:
客户端进程中,首先要绑定服务端的Service,绑定后服务端的onBind会返回一个Binder对象,然后客户端用服务端返回的这个Binder对象创建一个Messenger,通过这个Messenger就可以向服务器端发送消息了,发送消息类型为Message对象,如果需要服务端能够回应客户端,就像和服务端一个,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象在第一次客户端像服务端发送消息时通过Message的replyTo参数传递给服务端,服务端通过读取Message中的replyTo参数就是服务端给客户端的的Messenger,然后就可以回应客户端。
(3)注意点:
客户端给服务端发送消息的时候所用的Messenger是通过绑定服务端,然后依据onBind返回的Binder对象为参数来创建Messenger,而服务端在回应客户端的时候所用的Messenger是客户端在刚刚发送消息的时候将自身创建的Messenger作为刚刚发送消息的Message的replyTo参数传递给服务端的,所以在服务端直接读取出这个Messenger。

3、举例:客户端像服务端发送消息,服务端回应客户端

(1)先看服务端代码:

package com.ryg.chapter_2.messenger;  
  
  
import com.ryg.chapter_2.model.User;  
import com.ryg.chapter_2.utils.MyConstants;  
  
  
import android.app.Service;  
import android.content.Intent;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.IBinder;  
import android.os.Message;  
import android.os.Messenger;  
import android.os.RemoteException;  
import android.util.Log;  
  
  
/* 
 * 首先,这是一个服务。 
 * 其次,这个服务是需要注册的,并且要给它另起一个进程。 
 * <service 
        android:name=".messenger.MessengerService" 
        android:process=":remote" > 
        <intent-filter> 
            <action android:name="com.ryg.MessengerService.launch" /> 
        </intent-filter> 
   </service> 
 * */  
public class MessengerService extends Service {  
  
  
    private static final String TAG = "MessengerService";  
  
  
    /* 
     * 继承Handler, 
     * MessengerHandler用来处理客户端发送的消息, 
     * 并从消息中取出客户端发来的文本信息。 
     * */  
    private static class MessengerHandler extends Handler {  
        @Override  
        public void handleMessage(Message msg) {  
            switch (msg.what) {  
            /* 
             * MyConstants是我们这个应用中的一个类,其中包含了几个变量的声明: 
             *     public static final int MSG_FROM_CLIENT = 0; 
                   public static final int MSG_FROM_SERVICE = 1; 
             * */  
            case MyConstants.MSG_FROM_CLIENT:  
            /* 
             * 这一条语句是在处理从客户端发来的消息,用Log日志打印出来: 
             * */  
                Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));  
                /* 
                 * 这下面的语句是用来响应客户端,给客户端回馈消息的。 
                 * (1)第一步是通过replyTo来获取客户端的Messenger对象。 
                 * (2)第二步是创建一个Message消息, 
                 * Message.obtain这个方法的第一个参数是Handler,第二个参数是消息的what字段。 
                 * (3)第三步创建一个Bundle对象,然后向这个对象中添加String内容。 
                 * (4)第四步是将Bundle对象设置给Message。 
                 * (5)第五步是通过Messenger将Message发送出去, 
                 * 因为我们的Messenger是通过客户端来获取的,而在客户端那边这个Messenger是以Handler为参数创建的, 
                 * 所以在服务端通过客户端的Messenger发送消息后,在客户端的Handler就会处理这条消息,嘻嘻,就达到了消息传送的目的。 
                 * */  
                Messenger client = msg.replyTo;  
                Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);  
                Bundle bundle = new Bundle();  
                bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");  
                relpyMessage.setData(bundle);  
                try {  
                    client.send(relpyMessage);  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
                break;  
            default:  
                super.handleMessage(msg);  
            }  
        }  
    }  
  
  
    /* 
     * 这是我们服务端自己的Messenger,它是以上面的Handler对象为参数创建的, 
     * 这个Messenger是要通过绑定该服务器的时候onBind方法传递给客户端, 
     * 然后客户端获取了该Messenger,再以该Messenger来发送消息, 
     * 这样服务端就可以接收到该消息并处理。 
     * */  
    private final Messenger mMessenger = new Messenger(new MessengerHandler());  
  
  
    /* 
     * 这个方法是在绑定服务的过程中调用的并将结果返回给客户端的, 
     * 所以通过onBind方法客户端就可以获取我们Messenger的Binder对象了, 
     * 然后客户端可以根据该Binder对象来创建一个Messenger, 
     * 这样客户端中用的Messenger和这里的Messenger就是向对应的了。 
     * */  
    @Override  
    public IBinder onBind(Intent intent) {  
        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);  
    }  
  
  
}  

(2)再看看客户端代码:

package com.ryg.chapter_2.messenger;  
  
  
import com.ryg.chapter_2.R;  
import com.ryg.chapter_2.R.layout;  
import com.ryg.chapter_2.model.User;  
import com.ryg.chapter_2.utils.MyConstants;  
  
  
import android.app.Activity;  
import android.content.ComponentName;  
import android.content.Context;  
import android.content.Intent;  
import android.content.ServiceConnection;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.IBinder;  
import android.os.Message;  
import android.os.Messenger;  
import android.os.RemoteException;  
import android.util.Log;  
  
  
/* 
 * 客户端,首先它是一个活动。 
 * 其次它也需要注册的。 
 * */  
public class MessengerActivity extends Activity {  
  
  
    private static final String TAG = "MessengerActivity";  
  
  
    // 用来获取服务端的Messenger,用来给服务端传递消息用的。  
    private Messenger mService;  
    // 这是客户端自己的Messenger,传递给服务端,让服务端返回消息用的。  
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());  
      
    /* 
     * 这个Handler是用来处理服务端返回的消息的, 
     * 这个Handler将作为一个参数来创建自己的Messenger, 
     * 然后将这个Messenger传递给服务端,让服务端根据它返回消息。 
     * */  
    private static class MessengerHandler extends Handler {  
        @Override  
        public void handleMessage(Message msg) {  
            switch (msg.what) {  
            case MyConstants.MSG_FROM_SERVICE:  
            // 处理消息,以Log日志显示出来。  
                Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));  
                break;  
            default:  
                super.handleMessage(msg);  
            }  
        }  
    }  
  
  
    /* 
     * 这个是客户端用来绑定服务端用的, 
     * 在绑定过程中会调用onServiceConnected, 
     * 它的第二个参数IBinder service,就是在服务端中onBind方法返回的结果, 
     * 这个结果是服务端的Messenger对象的Binder对象, 
     * 然后客户端通过这个Binder对象就可以创建一个Messenger, 
     * 所以就是在绑定服务的过程中将服务端的Messenger传递给了客户端,建立起了两者之间的桥梁 
     * */  
    private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            /* 
             * (1)第一步是根据服务端的IBinder service对象为参数创建Messenger。 
             * (2)第二步是创建一个Message消息,其中第二个参数是msg的what字段。 
             * 这里有个重要的点就是设置msg的replyTo字段,这个字段保存了客户端自己的Messenger, 
             * 客户端将自己的Messenger传递给服务端,然后方便服务端根据这个Messenger将反馈消息用同样的方法传递回来。 
             * (3)第三步是创建一个Bundle对象,这个对象中添加了要返回的消息内容。 
             * (4)第四步将Bundle对象赋给Message。 
             * (5)第五步用Messenger的send方法将消息发送出去。 
             * */  
            mService = new Messenger(service);  
            Log.d(TAG, "bind service");  
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);  
            Bundle data = new Bundle();  
            data.putString("msg", "hello, this is client.");  
            msg.setData(data);  
            msg.replyTo = mGetReplyMessenger;  
            try {  
                mService.send(msg);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
  
  
        public void onServiceDisconnected(ComponentName className) {  
        }  
    };  
  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_messenger);  
        /* 
         * 这个Intent的跳转是需要服务端设置的: 
         * <service 
                android:name=".messenger.MessengerService" 
                android:process=":remote" > 
                <intent-filter> 
                    <action android:name="com.ryg.MessengerService.launch" /> 
                </intent-filter> 
           </service> 
         * */  
        Intent intent = new Intent("com.ryg.MessengerService.launch");  
        /* 
         * 在bindService的时候,服务端会通过onBind方法返回一个包含了服务端业务调用的Binder对象, 
         * 通过这个对象,客户端就可以获取服务端提供的服务或者数据, 
         * 具体情况去下面的第二个参数mConnection中查看。 
         * */  
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);  
    }  
      
    @Override  
    protected void onDestroy() {  
        unbindService(mConnection);  
        super.onDestroy();  
    }  
}  

(3)看完了是不是觉得很简单呀,嘿嘿。

六、使用AIDL

1、对比Messenger和AIDL:

上一节讲的Messenger来进行进程间的通信,可以发现,Messenger是以串行的方式处理客户端发来的消息的,

如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。

而且Messenger的主要作用是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法实现了。

所以我们用AIDL来实现跨进程的方法调用。

AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装,从而方便上层的调用而已。

2、AIDL使用的基本思想:

(0)先来放一下我们的Book.java类,它实现了Parcelable接口:

package com.ryg.chapter_2.aidl;  
  
import android.os.Parcel;  
import android.os.Parcelable;  
  
/* 
 * (1)它是一个表示图示信息的类, 
 * 它实现了Parcelable接口, 
 * (2)Book.aidl是Book类在ADIL中的声明。 
 * (3)IBookManager.aidl是我们定义的一个接口,里面有两个方法:getBookList和addBook, 
 * 其中getBookList用于从远程服务端获取图书列表,而addBook用于往图书列表中添加一本书, 
 * 当然这两个方法主要是示例用,不一定要有实际意义。 
 * (4)尽管Book类和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类, 
 * 这就是AIDL的特殊之处。 
 * */  
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;  
    }  
  
    public int describeContents() {  
        return 0;  
    }  
  
    public void writeToParcel(Parcel out, int flags) {  
        out.writeInt(bookId);  
        out.writeString(bookName);  
    }  
  
    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {  
        public Book createFromParcel(Parcel in) {  
            return new Book(in);  
        }  
  
        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);  
    }  
  
}  

(1)服务端:

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

(2)客户端:

客户端所要做的事情就稍微简单一些,首先需要绑定服务端的Service,在绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

(3)AIDL接口的创建:

首先看AIDL接口的创建,如下所示,我们创建一个后缀为AIDL的文件,在里面声明了一个接口和两个接口方法:

这个文件的名称是:IBookManager.aidl。

package com.ryg.chapter_2.aidl;  
  
import com.ryg.chapter_2.aidl.Book;  
  
interface IBookManager {  
     List<Book> getBookList();  
     void addBook(in Book book);  
}  

在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件支持哪些类型的数据呢?

基本数据类型(int、long、char、boolean、double等);

String和CharSequence;

List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;

Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;

Parcelable:所有实现了Parcelable接口的对象;

AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
(其中自定义的Parcelable对象和AIDL对象必须要显示import进来,不管它们是否和当前的AIDL文件位于同一个包内)

所以上面的IBookManager.aidl文件,里面用到了Book这个类,这个类实现了Parcelable接口并且和IBookManager位于同一个包中,但是遵守AIDL的规范,我们仍然需要显式的import进来:import com.ryg.chapter_2.aidl.Book;

还有一个注意点:

如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。

因为我们在IBookManager.aidl中用到了Book这个类,所以我们必须要创建Book.aidl,然后里面添加的内容如下:

package com.ryg.chapter_2.aidl;  
  
parcelable Book;

事实上,AIDL中每个实现了Parcelable接口的类都需要按照上面那种方式去创建相应的AIDL文件并声明那个类为Parcelable。

除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out、inout。

我们需要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的。

最后,AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。

为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样方便我们直接复制整个包,不容易遗漏。

需要注意的是,AIDL的包结构在服务端和客户端要保持一致,否则运行会出错。因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行。

(4)远程服务端Service的实现:

上面讲的是如何定义AIDL接口,下面讲如何实现这个接口。

package com.ryg.chapter_2.aidl;  
  
import java.util.List;  
import java.util.concurrent.CopyOnWriteArrayList;  
import java.util.concurrent.atomic.AtomicBoolean;  
  
import android.app.Service;  
import android.content.Intent;  
import android.content.pm.PackageManager;  
import android.os.Binder;  
import android.os.IBinder;  
import android.os.Parcel;  
import android.os.RemoteCallbackList;  
import android.os.RemoteException;  
import android.os.SystemClock;  
import android.util.Log;  
  
/* 
 * 这是一个服务端Service的典型实现。 
 * 首先在onCreate中初始化添加两本图书, 
 * 然后创建了一个Binder对象并在onBind方法中返回它。 
 * */  
public class BookManagerService extends Service {  
  
    private static final String TAG = "BMS";  
  
    /* 
     * 注意这里采用了CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读/写。 
     * 因为AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候, 
     * 会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步, 
     * 而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步。 
     * */  
    /* 
     * 在AIDL中能够使用的List只有ArrayList,但是我们这里却使用了CopyOnWriteArrayList(它并不是继承子ArrayList的), 
     * 但为什么还能工作呢? 
     * 因为AIDL中所支持的是抽象List,而List只是一个接口, 
     * 因此虽然服务端返回的是CopyOnWriteArrayList, 
     * 但是Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。 
     * 所以我们在服务端采用CopyOnWriteArrayList是完全可以的, 
     * 和此类似的还有ConcurrentHashMap。 
     * */  
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();  
  
    /* 
     * 创建一个Binder对象,并在onBind方法中返回它。 
     * 这个Binder对象继承自IBookManager.Stub,并实现了它内部的AIDL方法, 
     * 这里主要看getBookList和addBook这两个AIDL方法的实现,实现过程也比较简单, 
     * */  
    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 onCreate() {  
        super.onCreate();  
        mBookList.add(new Book(1, "Android"));  
        mBookList.add(new Book(2, "Ios"));  
    }  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        return mBinder;  
    }  
}  

还需要注意的是,我们需要注册这个服务端,并让它运行在独立的进程中,这样它和客户端的Activity不在同一个进程中,这样就构成了进程间通信的场景:

<service  
    android:name=".aidl.BookManagerService"  
    android:process=":remote" >  
</service>  

(5)客户端的实现:

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

package com.ryg.chapter_2.aidl;  
  
  
import java.util.List;  
import com.ryg.chapter_2.R;  
import com.ryg.chapter_2.aidl.IBookManager;  
import com.ryg.chapter_2.utils.MyConstants;  
import android.annotation.SuppressLint;  
import android.app.Activity;  
import android.content.ComponentName;  
import android.content.Context;  
import android.content.Intent;  
import android.content.ServiceConnection;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.IBinder;  
import android.os.Message;  
import android.os.RemoteException;  
import android.util.Log;  
import android.view.View;  
import android.widget.Toast;  
  
  
public class BookManagerActivity extends Activity {  
  
  
    private static final String TAG = "BookManagerActivity";  
      
    private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            /* 
             * 这里的实现方式和Messenger简直一样样的, 
             * 都是在绑定服务端的过程中,服务端通过onBind方法将它的Binder传递过来, 
             * 然后在客户端以这个传递来的Binder创建对应的对象 
             * */  
            IBookManager bookManager = IBookManager.Stub.asInterface(service);  
            try {  
              /* 
               * 然后就可以调用相应的方法了。 
               * */  
                List<Book> list = bookManager.getBookList();  
                Log.i(TAG, "query book list, list type:"  
                        + list.getClass().getCanonicalName());  
                Log.i(TAG, "query book list:" + list.toString());  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
  
  
        public void onServiceDisconnected(ComponentName className) {  
        }  
    };  
  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_book_manager);  
        Intent intent = new Intent(this, BookManagerService.class);  
        /* 
         * 绑定服务: 
         * */  
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);  
    }  
  
  
    @Override  
    protected void onDestroy() {  
        unbindService(mConnection);  
        super.onDestroy();  
    }  
  
  
}  

以上就算是一个比较简单的完整的AIDL进行IPC的过程。

3、AIDL使用过程中的一些问题,应用观察者模式

(1)我们用观察者模式来实现当图书馆接收到新书后,就为申请过新书到来通知的用户发送新书通知。我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,同时也可以取消这个功能。用AIDL接口而不用普通接口是因为AIDL中无法使用普通接口。

(2)首先我们创建一个IOnNewBookArrivedListener.aidl文件,当服务端有新书到来时,就会通知每一个已经申请提醒功能的用户。从程序上来说就是调用所有IOnNewBookArrivedListener对象中的onNewBookArrived方法,并把新书的对象通过参数传递给客户端。

package com.ryg.chapter_2.aidl;  
  
  
import java.util.List;  
import java.util.concurrent.CopyOnWriteArrayList;  
import java.util.concurrent.atomic.AtomicBoolean;  
  
  
import android.app.Service;  
import android.content.Intent;  
import android.content.pm.PackageManager;  
import android.os.Binder;  
import android.os.IBinder;  
import android.os.Parcel;  
import android.os.RemoteCallbackList;  
import android.os.RemoteException;  
import android.os.SystemClock;  
import android.util.Log;  
  
  
public class BookManagerService extends Service {  
  
  
    private static final String TAG = "BMS";  
  
  
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);  
  
  
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();  
    // private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList =  
    // new CopyOnWriteArrayList<IOnNewBookArrivedListener>();  
  
  
    /* 
     * 用来保存申请了新书通知的用户。 
     * (1)这里有一个注意点,RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。 
     * RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。 
     * 它的内部有一个Map结构专门用来保存所有的AIDL回调, 
     * 这个Map的key是IBinder类型,value是Callback类型,如下所示: 
     * ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); 
     * 其中Callback中封装了真正的远程listener。当客户端注册listener的时候,它会把这个listener的信息存入mCallbacks中, 
     * 其中key和value分别通过下面的方式获得: 
     * IBinder key = listener.asBinder(); 
     * Callback value = new Callback(listener, cookie); 
     * (2)注意点二:也就是说,虽然多次跨进程传输客户端的同一个对象会在服务端生成不同的对象, 
     * 但这些新生成的对象有一个共同点,那就是它们的底层Binder对象是同一个。也就是说key相同。 
     * (3)注意点三:RemoteCallbackList在客户端进程终止后,能够自动移除客户端所注册的listener。 
     * 另外RemoteCallbackList内部自动实现了线程同步的功能, 
     * 所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。 
     * */  
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();  
  
  
    /* 
     * 服务端的Binder对象,要传给客户端的,让客户端调用里面的方法: 
     * */  
    private Binder mBinder = new IBookManager.Stub() {  
  
  
    /* 
     * 具体的实现原来是在服务端的服务中实现的 
     * */  
        @Override  
        public List<Book> getBookList() throws RemoteException {  
            SystemClock.sleep(5000);  
            return mBookList;  
        }  
  
  
        @Override  
        public void addBook(Book book) throws RemoteException {  
            mBookList.add(book);  
        }  
  
  
        /* 
         * 第二种权限验证功能方法: 
         * */  
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)  
                throws RemoteException {  
        <span style="white-space:pre">    </span>// 首先查看自定义权限com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE  
            int check = checkCallingOrSelfPermission("com.ryg.chapter_2.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.ryg")) {  
                return false;  
            }  
  
  
            return super.onTransact(code, data, reply, flags);  
        }  
  
  
        /* 
         * 注册申请新书提醒的用户: 
         * */  
        @Override  
        public void registerListener(IOnNewBookArrivedListener listener)  
                throws RemoteException {  
        /* 
         * 用RemoteCallbackList,key和value都是通过listener来获取的: 
         * IBinder key = listener.asBinder(); 
         * Callback value = new Callback(listener, cookie); 
         * 这个Binder是IOnNewBookArrivedListener这个aidl的Binder, 
         * 和IBookManager这个aidl的binder不是同一个啦。 
         * */  
            mListenerList.register(listener);  
  
  
            /* 
             * RemoteCallbackList并不是一个List, 
             * 遍历RemoteCallbackList必须要使用beginBroadcast和finishBroadcast来配对使用, 
             * 哪怕只是为了获取RemoteCallbackList中的元素个数。 
             * */  
            final int N = mListenerList.beginBroadcast();  
            mListenerList.finishBroadcast();  
            Log.d(TAG, "registerListener, current size:" + N);  
        }  
  
  
        @Override  
        public void unregisterListener(IOnNewBookArrivedListener listener)  
                throws RemoteException {  
            boolean success = mListenerList.unregister(listener);  
  
  
            if (success) {  
                Log.d(TAG, "unregister success.");  
            } else {  
                Log.d(TAG, "not found, can not unregister.");  
            }  
              
            /* 
             * RemoteCallbackList并不是一个List, 
             * 遍历RemoteCallbackList必须要使用beginBroadcast和finishBroadcast来配对使用, 
             * 哪怕只是为了获取RemoteCallbackList中的元素个数。 
             * */  
            final int N = mListenerList.beginBroadcast();  
            mListenerList.finishBroadcast();  
            Log.d(TAG, "unregisterListener, current size:" + N);  
        };  
  
  
    };  
  
  
    @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 IBinder onBind(Intent intent) {  
    /* 
     * 我们可以在onBind方法中进行权限验证,验证不能通过就直接返回null。 
     * 这种方法需要服务端在AndroidManifest中声明所需的权限: 
     * <permission 
     * android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" 
    * android:protectionLevel="normal" /> 
     * 在客户端AndroidManifest中这样声明才可以: 
     * <uses-permission 
     * android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" /> 
     * */  
        int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");  
        Log.d(TAG, "onbind check=" + check);  
        /* 
         * 如果客户端没有使用这个权限,就会绑定失败。 
         * */  
        if (check == PackageManager.PERMISSION_DENIED) {  
            return null;  
        }  
        return mBinder;  
    }  
  
  
    @Override  
    public void onDestroy() {  
        mIsServiceDestoryed.set(true);  
        super.onDestroy();  
    }  
  
  
    /* 
     * 当有新书到来的时候,通知每一位用户: 
     * 这里需要注意一下的是,当新书到达的时候, 
     * 服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法, 
     * 这个方法是在客户端的Binder线程池中执行的 
     * */  
    private void onNewBookArrived(Book book) throws RemoteException {  
        mBookList.add(book);  
        final int N = mListenerList.beginBroadcast();  
        for (int i = 0; i < N; i++) {  
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);  
            if (l != null) {  
                try {  
                    l.onNewBookArrived(book);  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
        mListenerList.finishBroadcast();  
    }  
  
  
    /* 
     * 我们设定每隔5m添加一本新书: 
     * */  
    private class ServiceWorker implements Runnable {  
        @Override  
        public void run() {  
            // do background processing here.....  
            while (!mIsServiceDestoryed.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();  
                }  
            }  
        }  
    }  
}  

(3)客户端BookManagerActivity.java:

package com.ryg.chapter_2.aidl;  
  
import java.util.List;  
import com.ryg.chapter_2.R;  
import com.ryg.chapter_2.aidl.IBookManager;  
import com.ryg.chapter_2.utils.MyConstants;  
import android.annotation.SuppressLint;  
import android.app.Activity;  
import android.content.ComponentName;  
import android.content.Context;  
import android.content.Intent;  
import android.content.ServiceConnection;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.IBinder;  
import android.os.Message;  
import android.os.RemoteException;  
import android.util.Log;  
import android.view.View;  
import android.widget.Toast;  
  
public class BookManagerActivity extends Activity {  
  
    private static final String TAG = "BookManagerActivity";  
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;  
  
    private IBookManager mRemoteBookManager;  
  
    /* 
     * 当有新书到来的时候,服务端通知每一位用户: 
     * 这里需要注意一下的是,当新书到达的时候, 
     * 服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法, 
     * 这个方法是在客户端的Binder线程池中执行的, 
     * 因此为了便于进行UI操作,我们需要一个Handler可以将其切换到客户端的主线程中去执行。 
     * */  
    @SuppressLint("HandlerLeak")  
    private Handler mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            switch (msg.what) {  
            case MESSAGE_NEW_BOOK_ARRIVED:  
                Log.d(TAG, "receive new book :" + msg.obj);  
                break;  
            default:  
                super.handleMessage(msg);  
            }  
        }  
    };  
  
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {  
        @Override  
        public void binderDied() {  
            Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());  
            if (mRemoteBookManager == null)  
                return;  
            mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);  
            mRemoteBookManager = null;  
            // TODO:杩欓噷閲嶆柊缁戝畾杩滅▼Service  
        }  
    };  
  
    /* 
     * 连接服务器,这个IBinder service就是服务器返回给我们的Binder对象。 
     * */  
    private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            /* 
             * 如果客户端和服务端在同一进程,那么asInterface返回内部类Stub, 
             * 否则返回内部类Stub的内部代理类Proxy: 
             * */  
            IBookManager bookManager = IBookManager.Stub.asInterface(service);  
            mRemoteBookManager = bookManager;  
            try {  
                /* 
                 * 给Binder设置死亡代理: 
                 * */  
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);  
                List<Book> list = bookManager.getBookList();  
                Log.i(TAG, "query book list, list type:"  
                        + list.getClass().getCanonicalName());  
                Log.i(TAG, "query book list:" + list.toString());  
                Book newBook = new Book(3, "Android杩涢樁");  
                bookManager.addBook(newBook);  
                Log.i(TAG, "add book:" + newBook);  
                List<Book> newList = bookManager.getBookList();  
                Log.i(TAG, "query book list:" + newList.toString());  
                /* 
                 * 申请新书提醒功能: 
                 * */  
                bookManager.registerListener(mOnNewBookArrivedListener);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
  
        public void onServiceDisconnected(ComponentName className) {  
            mRemoteBookManager = null;  
            Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());  
        }  
    };  
  
    /* 
     * 每个客户端用户内部都有这样一个对象的,用来传递给服务端注册新书提醒的。 
     * */  
    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {  
  
        /* 
         * 当有新书到来的时候,服务端通知每一位用户: 
         * 这里需要注意一下的是,当新书到达的时候, 
         * 服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法, 
         * 这个方法是在客户端的Binder线程池中执行的, 
         * 因此为了便于进行UI操作,我们需要一个Handler可以将其切换到客户端的主线程中去执行。 
         * */  
        @Override  
        public void onNewBookArrived(Book newBook) throws RemoteException {  
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook)  
                    .sendToTarget();  
        }  
    };  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_book_manager);  
        Intent intent = new Intent(this, BookManagerService.class);  
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);  
    }  
  
    public void onButton1Click(View view) {  
        Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();  
        new Thread(new Runnable() {  
  
            @Override  
            public void run() {  
                if (mRemoteBookManager != null) {  
                    try {  
                        List<Book> newList = mRemoteBookManager.getBookList();  
                    } catch (RemoteException e) {  
                        e.printStackTrace();  
                    }  
                }  
            }  
        }).start();  
    }  
  
    @Override  
    protected void onDestroy() {  
        if (mRemoteBookManager != null  
                && mRemoteBookManager.asBinder().isBinderAlive()) {  
            try {  
                Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);  
                mRemoteBookManager  
                        .unregisterListener(mOnNewBookArrivedListener);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
        unbindService(mConnection);  
        super.onDestroy();  
    }  
  
}  

注意点一:客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起。这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间的阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR。

注意点二:由于客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以也不可以在他们里面直接调用服务端的耗时方法,这点要尤其注意。

注意点三:由于服务端的方法本身就运行在服务端的Binder线程中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程去执行异步任务。懂?就是耗时操作在服务端方法中直接执行,不要再开启其他的线程来执行耗时操作啦。

注意点四:同理,当远程服务端需要调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池,所以,我们同样不可以在服务端中调用客户端的耗时方法。如果非要调用耗时方法,请确保这个方法运行在非UI线程中,否则将导致服务端无法响应。

注意点五:AIDL使用方法总结:

首先建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。

七、Binder连接池

1、问题:随着AIDL数量的增加,我们不能无限制的增加Service。

所以,我们需要减少Service的数量,将所有的AIDL放在同一个Service中去管理。

2、工作机制

每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象。对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBind而接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。由此可见,Binder连接池的主要作用是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。

image

3、举例说明吧

(1)我们有两个AIDL接口(ISecurityCenter和ICompute)来模拟两个业务模块。然后系统会为它们两个在gen目录下分别生成ISecurityCenter.java和ICompute.java文件。

ISecurityCenter.aidl:(加密解密)

package com.ryg.chapter_2.binderpool;  
  
interface ISecurityCenter {  
    String encrypt(String content);  
    String decrypt(String password);  
} 

ICompute.aidl:

package com.ryg.chapter_2.binderpool;  
  
interface ICompute {  
    int add(int a, int b);  
}

(2)这是上面两个AIDL接口的实现:其中ISecurityCenter.Stub和ICompute.Stub是在系统在gen目录下自动生成的ISecurityCenter.java和ICompute.java文件中的内部类Stub。在内部类中有它们方法的声明,在这里我们继承这个内部类并重写实现这些方法。

SecurityCenterImpl.java:

package com.ryg.chapter_2.binderpool;  
  
import android.os.RemoteException;  
  
public class SecurityCenterImpl extends ISecurityCenter.Stub {  
  
    private static final char SECRET_CODE = '^';  
  
    @Override  
    public String encrypt(String content) throws RemoteException {  
        char[] chars = content.toCharArray();  
        for (int i = 0; i < chars.length; i++) {  
            chars[i] ^= SECRET_CODE;  
        }  
        return new String(chars);  
    }  
  
    @Override  
    public String decrypt(String password) throws RemoteException {  
        return encrypt(password);  
    }  
  
}  

ComputeImpl.java:

package com.ryg.chapter_2.binderpool;  
  
import android.os.RemoteException;  
  
public class ComputeImpl extends ICompute.Stub {  
  
    @Override  
    public int add(int a, int b) throws RemoteException {  
        return a + b;  
    }  
  
} 

(3)为Binder连接池创建AIDL接口IBinderPool.aidl:

package com.ryg.chapter_2.binderpool;  
  
interface IBinderPool {  
  
    /** 
     * @param binderCode, the unique token of specific Binder<br/> 
     * @return specific Binder who's token is binderCode. 
     */  
    IBinder queryBinder(int binderCode);  
}  

(4)为Binder连接池创建远程Service并实现IBinderPool。下面是queryBinder的具体实现,当Binder连接池连接上远程服务时,会根据不同模块的标识即binderCode返回不同的Binder对象,通过这个Binder对象所执行的操作全部发生在远程服务端:

                                @Override  
        public IBinder queryBinder(int binderCode) throws RemoteException {  
            IBinder binder = null;  
            switch (binderCode) {  
            case BINDER_SECURITY_CENTER: {  
                binder = new SecurityCenterImpl();  
                break;  
            }  
            case BINDER_COMPUTE: {  
                binder = new ComputeImpl();  
                break;  
            }  
            default:  
                break;  
            }  
  
            return binder;  
        }  

(5)远程Service的实现就比较简单了:以前直接返回的是服务端的Binder对象,如今在onBind中返回的是BinderPool连接池。

package com.ryg.chapter_2.binderpool;  
  
  
import android.app.Service;  
import android.content.Intent;  
import android.os.Binder;  
import android.os.IBinder;  
import android.util.Log;  
  
  
public class BinderPoolService extends Service {  
  
  
    private static final String TAG = "BinderPoolService";  
  
  
    /* 
     * 在服务端创建一个连接池,BinderPoolImpl是BinderPool的内部类, 
     * 它继承了IBinderPool.Stub,并实现了queryBinder方法。 
     * */  
    private Binder mBinderPool = new BinderPool.BinderPoolImpl();  
  
  
    @Override  
    public void onCreate() {  
        super.onCreate();  
    }  
  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        Log.d(TAG, "onBind");  
        /* 
         * 返回连接池对象: 
         * */  
        return mBinderPool;  
    }  
  
  
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
    }  
  
}  

(6)Binder连接池的具体实现,在它的内部首先它要去绑定远程服务,绑定成功后,客户端就可以通过它的queryBinder方法去获取各自对应的Binder,拿到所需的Binder以后,不同业务模块就可以进行各自的操作了:

package com.ryg.chapter_2.binderpool;  
  
import java.util.concurrent.CountDownLatch;  
  
import android.content.ComponentName;  
import android.content.Context;  
import android.content.Intent;  
import android.content.ServiceConnection;  
import android.os.Binder;  
import android.os.IBinder;  
import android.os.RemoteException;  
import android.util.Log;  
  
public class BinderPool {  
    private static final String TAG = "BinderPool";  
    public static final int BINDER_NONE = -1;  
    public static final int BINDER_COMPUTE = 0;  
    public static final int BINDER_SECURITY_CENTER = 1;  
  
    private Context mContext;  
    private IBinderPool mBinderPool;  
    private static volatile BinderPool sInstance;  
    private CountDownLatch mConnectBinderPoolCountDownLatch;  
  
    private BinderPool(Context context) {  
        mContext = context.getApplicationContext();  
        connectBinderPoolService();  
    }  
  
    /* 
     * 返回BinderPool的实例,如果没有的话就创建,有的话就直接返回。 
     * */  
    public static BinderPool getInsance(Context context) {  
        if (sInstance == null) {  
            synchronized (BinderPool.class) {  
                if (sInstance == null) {  
                    sInstance = new BinderPool(context);  
                }  
            }  
        }  
        return sInstance;  
    }  
  
    /* 
     * 连接BinderPoolService服务器。 
     * */  
    private synchronized void connectBinderPoolService() {  
        /* 
         * mConnectBinderPoolCountDownLatch这个东西是干嘛的? 
         * */  
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);  
        Intent service = new Intent(mContext, BinderPoolService.class);  
        mContext.bindService(service, mBinderPoolConnection,  
                Context.BIND_AUTO_CREATE);  
        try {  
            mConnectBinderPoolCountDownLatch.await();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
  
    /** 
     * query binder by binderCode from binder pool 
     *  
     * @param binderCode 
     *            the unique token of binder 
     * @return binder who's token is binderCode<br> 
     *         return null when not found or BinderPoolService died. 
     */  
    /* 
     * queryBinder, 
     * */  
    public IBinder queryBinder(int binderCode) {  
        IBinder binder = null;  
        try {  
            /* 
             * 这个mBinderPool是一个BinderPool.BinderPoolImpl对象。 
             * 对于客户端来说调用的是BinderPool的queryBinder方法, 
             * 而BinderPool的queryBinder方法又调用了BinderPool.BinderPoolImpl对象的queryBinder方法。 
             * mBinderPool这个对象是服务端返回给BinderPool的,对客户端是隐藏的, 
             * 客户端只知道BinderPool, 
             * mBinderPool是服务端和连接池的桥梁, 
             * BinderPool是客户端和连接池的桥梁 
             * */  
            if (mBinderPool != null) {  
                binder = mBinderPool.queryBinder(binderCode);  
            }  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
        return binder;  
    }  
  
    /* 
     * 连接服务器的时候用的,里面有连接成功和连接断开后的操作。 
     * */  
    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {  
  
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
            // ignored.  
        }  
  
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            /* 
             * 将服务器端的Binder转换成客户端所需的AIDL接口对象: 
             * 服务端返回的是BinderPool连接池,而不是单纯的一个Binder对象。 
             * */  
            mBinderPool = IBinderPool.Stub.asInterface(service);  
            try {  
                /* 
                 * 设置死亡代理: 
                 * */  
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
            mConnectBinderPoolCountDownLatch.countDown();  
        }  
    };  
  
    /* 
     * 设置死亡代理: 
     * */  
    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {  
        @Override  
        public void binderDied() {  
            Log.w(TAG, "binder died.");  
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);  
            mBinderPool = null;  
            connectBinderPoolService();  
        }  
    };  
  
    /* 
     * (1)这个是我们的Binder连接池,它源于IBinderPool.aidl这个AIDL,它里面包含一个queryBinder方法, 
     * 我们的Binder连接池是放在服务端用, 
     * 所以在服务端需要有这样一个BinderPoolImpl的实例,并且它是一个Binder: 
     * private Binder mBinderPool = new BinderPool.BinderPoolImpl(); 
     * (2)那怎么用呢? 
     * 我们当前所在的类BinderPool.java就是用来绑定服务端的客户端, 
     * 在BinderPool绑定服务端的时候,服务端会将mBinderPool返回给客户端也就是我们这个类, 
     * 然后我们可以根据服务端返回的这个Binder来转换成客户端所需的AIDL接口对象,还是叫mBinderPool, 
     * 然后我们这个类中就可以调用mBinderPool中的方法: 
     * binder = mBinderPool.queryBinder(binderCode); 
     * (3)那另外的两个AIDL呢?ICompute.aidl和ISecurityCenter.aidl呢? 
     * 由于另外的两个AIDL的使用都是和服务端相关联的,是服务端的queryBinder方法将它们的Binder返回给客户端的, 
     * 客户端接到这两个AIDL的Binder以后,依旧是通过转换成AIDL接口对象来使用这两个AIDL中的方法的。 
     * */  
    public static class BinderPoolImpl extends IBinderPool.Stub {  
  
        public BinderPoolImpl() {  
            super();  
        }  
  
        @Override  
        public IBinder queryBinder(int binderCode) throws RemoteException {  
            IBinder binder = null;  
            switch (binderCode) {  
            case BINDER_SECURITY_CENTER: {  
                binder = new SecurityCenterImpl();  
                break;  
            }  
            case BINDER_COMPUTE: {  
                binder = new ComputeImpl();  
                break;  
            }  
            default:  
                break;  
            }  
  
            return binder;  
        }  
    }  
  
}  

注意点一:mBinderPool是BinderPool.BinderPoolImpl对象,这个BinderPool.BinderPoolImpl是BinderPool中的一个内部类,里面具体实现了queryBinder方法。服务端会创建一个mBinderPool对象然后在BinderPool对其绑定的过程中返回给BinderPool,这样BinderPool和服务端就通过mBinderPool这个对象进行联系。

注意点二:对于客户端来说,首先他要获取这个BinderPool连接池,然后根据BinderPool的queryBinder来获取它对应的Binder对象,然后根据这个Binder对象可以去执行具体的方法。

注意点三:首先需要搞清楚的是,哪里是在服务端运行的,哪里是在客户端运行的。

对于客户端而言,仅仅是有一个ISecurityCenter和ICompute的对象,它们是AIDL,这两个对象是没有方法的具体实现的,具体实现是在服务端的。在服务端有SecurityCenterImpl和ComputeImpl来继承ISecurityCenter.Stub和ICompute.Stub,因为Stub是Binder对象,所以它们两个也是Binder,里面还给出了方法的具体实现。但服务端和客户端并不在同一个进程中,那么客户端为了调用服务端的方法,就必须使用Binder对象,所以客户端要去绑定服务端,然后服务端返回Binder对象。但当我们使用了连接池BinderPool的时候,让连接池BinderPool与服务端BinderPoolService绑定。在服务端BinderPoolService中有这样一个对象:mBinderPool,它是BinderPool.BinderPoolImpl,BinderPool.BinderPoolImpl是BinderPool的一个内部类,里面有一个queryBinder方法,用来返回真正的对应客户端的Binder对象,在连接池BinderPool与服务端绑定以后,服务端将这个mBinderPool对象返回给连接池,这样连接池就可以通过这个mBinderPool对象为客户端返回相应的Binder对象。这样当多个种类的客户端想要绑定服务端的时候,只需要直接调用连接池就可以了,因为连接池根据服务端给它的mBinderPool掌管了所有的Binder对象,不过要注意的是,连接池是通过服务端返回的连接池实现对象才能管理这些Binder,所以说,所有的Binder对象还是由服务端来掌管的。连接池会为对应的客户端返回对应的Binder对象,这些Binder对象就是SecurityCenterImpl具体实现方法的Binder。

(7)下面就验证一下Binder连接池的效果了:看客户端。

package com.ryg.chapter_2.binderpool;  
  
import com.ryg.chapter_2.R;  
import android.app.Activity;  
import android.os.Bundle;  
import android.os.IBinder;  
import android.os.RemoteException;  
import android.util.Log;  
  
/* 
 * 这里是客户端 
 * */  
public class BinderPoolActivity extends Activity {  
    private static final String TAG = "BinderPoolActivity";  
  
    private ISecurityCenter mSecurityCenter;  
    private ICompute mCompute;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_binder_pool);  
        /* 
         * 在线程中去执行: 
         * */  
        new Thread(new Runnable() {  
  
            @Override  
            public void run() {  
                doWork();  
            }  
        }).start();  
    }  
  
    private void doWork() {  
        // 首先获取一个BinderPool的实例:这里是带了上下文的,避免创建多个。  
        BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity.this);  
        /* 
         * 然后根据客户端编号bindercode查询Binder,返回的是对应的客户端的Binder。 
         * 在binderPool.queryBinder中,是根据在绑定服务端过程中返回的BinderPoolImpl的Binder, 
         * 这个BinderPoolImpl就是继承了IBinderPool的,所以也实现了其中的queryBinder的。 
         * 这样返回的才是真正对应的securityBinder。 
         * */   
        IBinder securityBinder = binderPool  
                .queryBinder(BinderPool.BINDER_SECURITY_CENTER);  
        ;  
        /* 
         * 查到对应的Binder以后,就可以根据这个Binder来转换成客户端所需的AIDL接口对象: 
         * */  
        mSecurityCenter = (ISecurityCenter) SecurityCenterImpl  
                .asInterface(securityBinder);  
        Log.d(TAG, "visit ISecurityCenter");  
        String msg = "helloworld-安卓";  
        System.out.println("content:" + msg);  
        try {  
            /* 
             * 有了接口对象,自然就可以调用对象中的方法了: 
             * */  
            String password = mSecurityCenter.encrypt(msg);  
            System.out.println("encrypt:" + password);  
            System.out.println("decrypt:" + mSecurityCenter.decrypt(password));  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
  
          
        /* 
         * 下面这是另一个AIDL模块,使用方法和上面是一样的。 
         * */  
        Log.d(TAG, "visit ICompute");  
        IBinder computeBinder = binderPool  
                .queryBinder(BinderPool.BINDER_COMPUTE);  
        ;  
        mCompute = ComputeImpl.asInterface(computeBinder);  
        try {  
            System.out.println("3+5=" + mCompute.add(3, 5));  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
    }  
  
}  

八、使用Bundle

1、四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的

由于Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输。我们可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。

2、一个特殊的使用场景

比如A进程在进行一个计算,计算完成后它要启动B进程的一个组件并把计算结果传递给B进程,可是遗憾的是这个计算结果并不支持放入Bundle中,因此无法通过Intent来传输,这个时候如果我们用其他IPC方式就会略显复杂。可以考虑如下方式:我们通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台进行计算,计算完毕后在启动B进程中真正要启动的目标组件,由于Service也运行在B进程中,所以目标组件就可以直接获取计算结果,这样一来就轻松解决了跨进程的问题。这种方式的核心思想在于将原本需要在A进程的计算任务转移到B进程的后台Service中去执行。

九、使用ContentProvider

十、使用Socket

1、Socket套接字

(1)网络通信,分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议。

(2)Socket本身可以支持传输任意字节流。

2、使用Socket进行通信,首先需要声明权限

<uses-permission android:name="android.permission.INTERNET" />  
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  

3、使用Socket进行通信,不能再主线程中访问网络

因为这会导致我们的程序无法在Android4.0及以上的设备上运行,会抛出异常:android.os.NetworkOnMainThreadException。而且进行网络操作很可能是耗时的。

4、案例:跨进程的聊天程序

(1)首先在远程Service建立一个TCP服务,然后在主界面中连接TCP服务,连接上了以后,就可以给服务端发消息,然后服务端随机的回应我们一条信息。我们的服务端可以和多个客户建立连接并响应。

(2)服务端。当Service启动时,会在线程中建立TCP服务,这里监听的是8688端口,然后就可以等待客户端的连接请求。当有客户端连接时,就会生成一个新的Socket,通过每次新创建的Socket就可以分别和不同的客户端通信了。服务端每收到一次客户端的消息就会随机回复一句话给客户端。当客户端断开连接时,服务端这边也会相应的关闭对应的Socket并结束通话线程。

package com.ryg.chapter_2.socket;  
  
  
import java.io.BufferedReader;  
import java.io.BufferedWriter;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.io.OutputStreamWriter;  
import java.io.PrintWriter;  
import java.net.ServerSocket;  
import java.net.Socket;  
import java.util.Random;  
  
  
import com.ryg.chapter_2.utils.MyUtils;  
  
  
import android.app.Service;  
import android.content.Intent;  
import android.os.IBinder;  
  
  
public class TCPServerService extends Service {  
  
  
    private boolean mIsServiceDestoryed = false;  
    private String[] mDefinedMessages = new String[] {  
            "你好啊,哈哈",  
            "请问你叫什么名字呀?",  
            "巴拉巴拉",  
            "巴拉巴拉小魔仙",  
            "艹"  
    };  
  
  
    @Override  
    public void onCreate() {  
    <span style="white-space:pre">  </span>/* 
    <span style="white-space:pre">  </span> * 开启一个线程,TcpServer是实现了Runnable的, 
    <span style="white-space:pre">  </span> * 里面开启了服务端这边的Socket。 
    <span style="white-space:pre">  </span> * 这里的TcpServer是继承自Runnable的。 
    <span style="white-space:pre">  </span> * */  
        new Thread(new TcpServer()).start();  
        super.onCreate();  
    }  
  
  
    /* 
     * 这次不需要绑定服务端。 
     * */  
    @Override  
    public IBinder onBind(Intent intent) {  
        return null;  
    }  
  
  
    @Override  
    public void onDestroy() {  
    <span style="white-space:pre">  </span>/* 
    <span style="white-space:pre">  </span> * 用来告诉Socket线程,我们的服务结束了。 
    <span style="white-space:pre">  </span> * */  
        mIsServiceDestoryed = true;  
        super.onDestroy();  
    }  
  
  
    /* 
     * 在线程里面开启Socket通信。 
     * 对于服务端,就是开启一个Socket端口,等待客户端来发起连接请求: 
     * */  
    private class TcpServer implements Runnable {  
  
  
        @SuppressWarnings("resource")  
        @Override  
        public void run() {  
        <span style="white-space:pre">    </span>/* 
        <span style="white-space:pre">    </span> * ServerSocket这种东西是系统自带的啦,直接拿来用就好了, 
        <span style="white-space:pre">    </span> * 就这么容易服务端就开启了Socket等待客户端来连接: 
        <span style="white-space:pre">    </span> * */  
            ServerSocket serverSocket = null;  
            try {  
            <span style="white-space:pre">  </span>// 监听本地8688端口:  
                serverSocket = new ServerSocket(8688);  
            } catch (IOException e) {  
                System.err.println("establish tcp server failed, port:8688");  
                e.printStackTrace();  
                return;  
            }  
  
  
            /* 
             * 开启Socket以后,服务端只需要一直等待就好了。 
             * */  
            while (!mIsServiceDestoryed) {  
                try {  
                    // 接收客户端请求,如果一直没有客户端,程序就会卡在这句,卡着!  
                    final Socket client = serverSocket.accept();  
                    System.out.println("accept");  
                    /* 
                     * 注意:不能在主线程中访问网络。 
                     * 一来是不允许的, 
                     * 二来放在主线程也会影响程序的响应效率。 
                     * 每来一个客户端连接就开启一个线程。 
                     * */  
                    new Thread() {  
                        @Override  
                        public void run() {  
                            try {  
                            <span style="white-space:pre">  </span>// 这个响应方法在下面定义的。  
                                responseClient(client);  
                            } catch (IOException e) {  
                                e.printStackTrace();  
                            }  
                        };  
                    }.start();  
  
  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  
  
  
    /* 
     * 用来相应客户端: 
     * */  
    private void responseClient(Socket client) throws IOException {  
        // 用于接收客户端消息:  
    <span style="white-space:pre">  </span>// BufferedReader用于接收:  
        BufferedReader in = new BufferedReader(new InputStreamReader(  
                client.getInputStream()));  
        // 用于向客户端发送消息:  
        // PrintWriter用于发送:  
        PrintWriter out = new PrintWriter(new BufferedWriter(  
                new OutputStreamWriter(client.getOutputStream())), true);  
        out.println("欢迎来到聊天室");  
        /* 
         * 当客户端与服务端的连接没有断开时,服务器就一直监听来自客户端的Socket: 
         * */  
        while (!mIsServiceDestoryed) {  
            String str = in.readLine();  
            System.out.println("msg from client:" + str);  
            /* 
             * 当客户端断开连接后,服务端这边的输入流in会接收到null, 
             * 这个时候就要break退出了。 
             * */  
            if (str == null) {  
                break;  
            }  
            int i = new Random().nextInt(mDefinedMessages.length);  
            String msg = mDefinedMessages[i];  
            out.println(msg);  
            System.out.println("send :" + msg);  
        }  
        System.out.println("client quit.");  
        // 关闭流  
        MyUtils.close(out);  
        MyUtils.close(in);  
        client.close();  
    }  
  
  
}  

(3)客户端:

package com.ryg.chapter_2.socket;  
  
import java.io.*;  
import java.net.Socket;  
import java.sql.Date;  
import java.text.SimpleDateFormat;  
  
import com.ryg.chapter_2.R;  
import com.ryg.chapter_2.utils.MyUtils;  
  
import android.annotation.SuppressLint;  
import android.app.Activity;  
import android.content.Context;  
import android.content.Intent;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.Message;  
import android.os.SystemClock;  
import android.text.TextUtils;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Button;  
import android.widget.EditText;  
import android.widget.TextView;  
  
public class TCPClientActivity extends Activity implements OnClickListener {  
  
    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;  
    private static final int MESSAGE_SOCKET_CONNECTED = 2;  
  
    private Button mSendButton;  
    private TextView mMessageTextView;  
    private EditText mMessageEditText;  
  
    private PrintWriter mPrintWriter;  
    private Socket mClientSocket;  
  
    /* 
     *  
     * */  
    @SuppressLint("HandlerLeak")  
    private Handler mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            switch (msg.what) {  
            case MESSAGE_RECEIVE_NEW_MSG: {  
                mMessageTextView.setText(mMessageTextView.getText()  
                        + (String) msg.obj);  
                break;  
            }  
            case MESSAGE_SOCKET_CONNECTED: {  
                mSendButton.setEnabled(true);  
                break;  
            }  
            default:  
                break;  
            }  
        }  
    };  
  
    /* 
     *  
     * */  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_tcpclient);  
        mMessageTextView = (TextView) findViewById(R.id.msg_container);  
        mSendButton = (Button) findViewById(R.id.send);  
        mSendButton.setOnClickListener(this);  
        mMessageEditText = (EditText) findViewById(R.id.msg);  
          
        /* 
         * 先把服务端启动了:startService(service); 
         * 然后在一个线程中去连接服务端的Socket:connectTCPServer(); 
         * */  
        Intent service = new Intent(this, TCPServerService.class);  
        startService(service);  
        /* 
         * 开启一个线程去连接服务端Socket: 
         * */  
        new Thread() {  
            @Override  
            public void run() {  
                connectTCPServer();  
            }  
        }.start();  
    }  
  
    /* 
     * 当Activity退出的时候,记得关闭和服务端的Socket连接: 
     * */  
    @Override  
    protected void onDestroy() {  
        if (mClientSocket != null) {  
            try {  
                mClientSocket.shutdownInput();  
                mClientSocket.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        super.onDestroy();  
    }  
  
    /* 
     * 点击Button发送消息给服务端: 
     * */  
    @Override  
    public void onClick(View v) {  
        if (v == mSendButton) {  
            final String msg = mMessageEditText.getText().toString();  
            if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {  
                mPrintWriter.println(msg);  
                mMessageEditText.setText("");  
                String time = formatDateTime(System.currentTimeMillis());  
                final String showedMsg = "self " + time + ":" + msg + "\n";  
                mMessageTextView.setText(mMessageTextView.getText() + showedMsg);  
            }  
        }  
    }  
  
    @SuppressLint("SimpleDateFormat")  
    private String formatDateTime(long time) {  
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));  
    }  
  
    /* 
     * 连接服务端的Socket。 
     * */  
    private void connectTCPServer() {  
        Socket socket = null;  
        /* 
         * 为了确定能够连接成功,这里采用了超时重连的策略, 
         * 每次连接失败后都会重新建立尝试连理连接。 
         * */  
        while (socket == null) {  
            try {  
                socket = new Socket("localhost", 8688);  
                mClientSocket = socket;  
                /* 
                 * 这是客户端用来发送消息的输出流: 
                 * */   
                mPrintWriter = new PrintWriter(new BufferedWriter(  
                        new OutputStreamWriter(socket.getOutputStream())), true);  
                /* 
                 * 用一个Handler来进行和UI交互,因为我们的客户端是在线程中与服务端进行连接的: 
                 * */  
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);  
                System.out.println("connect server success");  
            } catch (IOException e) {  
                /* 
                 * 为了降低重试机制的开销,我们加入了休眠机制, 
                 * 即每次重试的时间间隔为1000毫秒。 
                 * */  
                SystemClock.sleep(1000);  
                System.out.println("connect tcp server failed, retry...");  
            }  
        }  
  
        try {  
            // 用于接收服务器端的消息  
            BufferedReader br = new BufferedReader(new InputStreamReader(  
                    socket.getInputStream()));  
            // 不断循环的接收消息,当Activity退出时,循环也退出并终止线程:  
            while (!TCPClientActivity.this.isFinishing()) {  
                // 如果没有消息会卡住的:  
                String msg = br.readLine();  
                System.out.println("receive :" + msg);  
                if (msg != null) {  
                    String time = formatDateTime(System.currentTimeMillis());  
                    final String showedMsg = "server " + time + ":" + msg  
                            + "\n";  
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)  
                            .sendToTarget();  
                }  
            }  
            System.out.println("quit...");  
            // 关闭流:  
            MyUtils.close(mPrintWriter);  
            MyUtils.close(br);  
            socket.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

(4)其实Socket不仅仅能实现进程间的通信,还可以实现设备间的通信,前提是这些设备之间的IP地址是互相可见的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容