(1)IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。 (2)ANR是Application Not Responding的缩写,即应用无响应。主线程执行大量的耗时操作容易导致ANR现象发生。5s,广播10s (3)在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。 (4)Android还支持Socket,通过Socket也可以实现任意两个终端或者两个进程之间的通信。
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的。
因此学习进程间通信机制
当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializeble。Serializable和Parcelable接口可以完成对象的序列化过程。还有时候我们需要把对象持久化到存储 设备上或者通过网络传输给其他客户端,这个时候也需要Serializable来完成对象的持久化。
(1)Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化非常简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。
private static final long serialVersionUID = 8723148825838841922L;
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();
注意点一:序列化和反序列化的对象并不是同一个对象!在传递过程中进行多次binder 调用; (3)一般来说,我们应该收到指定serialVersionUID的值,比如1L,也可以让Eclipse根据当前类的结构自动去生成它的hash值。当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。 注意点二:静态成员变量属于类不属于对象,所以不会参与序列化过程。 注意点三:用transient关键字标记的成员变量不参与序列化过程。
(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也可以序列化,前提是它们里面的每个元素都是可序列化的。
(1)Serializable用起来简单,但开销很大,序列化和反序列化过程都需要大量的I/O操作。 (2)Parcelable是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高,首选。主要用在内存序列化上。
(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接口定义语言。
(1)Proxy代理模式简介 代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。 模式的使用场景:就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
抽象对象角色AbstarctObject:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
目标对象角色RealObject:定义了代理对象所代表的目标对象。
代理对象角色ProxyObject:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。
AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板
AIDL简单理解:https://www.jianshu.com/p/29999c1a93cd
Binder一个很重要的作用是:将客户端的请求参数通过Parcel包装后传到远程服务端,远程服务端解析数据并执行对应的操作,同时客户端线程挂起,当服务端方法执行完毕后,再将返回结果写入到另外一个Parcel中并将其通过Binder传回到客户端,客户端接收到返回数据的Parcel后,Binder会解析数据包中的内容并将原始结果返回给客户端
至此,整个Binder的工作过程就完成了。由此可见,Binder更像一个数据通道,Parcel对象就在这个通道中跨进程传输,至于双方如何通信,这并不负责,只需要双方按照约定好的规范去打包和解包数据即可。
为了更好地说明Binder,这里我们先手动实现了一个Binder。为了使得逻辑更清晰,这里简化一下,我们来模拟一个银行系统,这个银行提供的功能只有一个:即查询余额,只有传递一个int的id过来,银行就会将你的余额设置为id*10,满足下大家的发财梦。
1)先定义一个Binder接口(抽象对象角色):
public interface IBank extends Interface {
/*
* 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并实现上述接口:
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、AIDL示例
通过AIDL文件生成Binder
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
新建AIDL文件,AS中会自动创建aidl的目录
// Book.aidl
package com.xxq2dream.aidl;
parcelable Book;
// IBookManager.aidl
package com.xxq2dream.aidl;
// Declare any non-default types here with import statements
//必须显示导入需要的Parcelable对象
import com.xxq2dream.aidl.Book;
//除了基本类型,其他参数都要标上方向,in表示输入型参数,out表示输出型参数,inout表示输入输出参数
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
最好把AIDL相关的文件都放在一个目录下
通过make让系统自动生成Binder类
生成的Binder类
模拟客户端和服务端的进程间通信
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private Binder mBinder = new BookManagerImpl(){
@Override
public List<Book> getBookList() throws RemoteException {
Log.e(TAG, "getBookList-->"+ System.currentTimeMillis());
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
Log.e(TAG, "addBook-->");
mBookList.add(book);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind-->"+ System.currentTimeMillis());
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate-->"+ System.currentTimeMillis());
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "IOS"));
}
}
//AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.xxq2dream.service.BookManagerService"
android:process=":remote" />
</application>
可以看到2个进程
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e(TAG, "ServiceConnection-->"+ System.currentTimeMillis());
//通过服务端回传的Binder得到客户端所需要的AIDL接口类型的对象,即我们上面的IBookManager
IBookManager bookManager = BookManagerImpl.asInterface(iBinder);
try {
// 通过AIDL接口类型的对象bookManager调用服务端方法
List<Book> list = bookManager.getBookList();
Log.e(TAG, "query book list, list type:" + list.getClass().getCanonicalName());
Log.e(TAG, "query book list:" + list.toString());
Book newBook = new Book(3, "Android 进阶");
bookManager.addBook(newBook);
Log.e(TAG, "add book:" + newBook);
List<Book> newList = bookManager.getBookList();
Log.e(TAG, "query book list:" + newList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.e(TAG, "binder died");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, BookManagerService.class);
//绑定服务,后面会回调onServiceConnected方法
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
//解绑服务
unbindService(mConnection);
super.onDestroy();
}
}
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);
1、特点:
(1)Messenger对AIDL做了封装,使得我们可以更简便地进行进程间通信。由于它一次处理一个请求,所以在服务端我们不考虑线程同步的问题,因为服务端中不存在并发执行的情形。 (2)通过它可以在不同进程中传递Message对象,在Message中仿佛我们需要传递的数据,就可以轻松地实现数据的进程间传递了。 (3)有两个构造函数,分别接收Handler对象和IBinder对象。
1、对比Messenger和AIDL:
上一节讲的Messenger来进行进程间的通信,可以发现,Messenger是以串行的方式处理客户端发来的消息的, 如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。 而且Messenger的主要作用是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法实现了。 所以我们用AIDL来实现跨进程的方法调用。
AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装,从而方便上层的调用而已。
https://blog.csdn.net/qq_38998213/article/details/105095028
AIDL使用方法总结: 首先建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。
1、问题:随着AIDL数量的增加,我们不能无限制的增加Service。
所以,我们需要减少Service的数量,将所有的AIDL放在同一个Service中去管理。
2、工作机制
每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象。对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBind而接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。由此可见,Binder连接池的主要作用是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。
(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: 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:
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连接池。 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以后,不同业务模块就可以进行各自的操作了: 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连接池的效果了:看客户端。 /* * 这里是客户端 * */ 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中去执行。
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并结束通话线程。 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)客户端: 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地址是互相可见的。