在讲解Binder前,我们先了解一些Linux的基础知识
IPC 即 Inter-Process Communication
(进程间通信)。Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。
进程隔离
跨进程通信( IPC )
跨进程通信的基本原理
Android应用和系统services运行在不同进程中是为了安全,稳定,以及内存管理的原因,但是应用和系统服务需要通信和分享数据。
优点
Serializable & Parcelable 原理和区别
Serializable是Java所提供的一个序列化接口,空接口。
重写如下两个方法可以重写系统默认的序列化和反序列化过程
private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}
Android中特有的序列化方式,效率相对Serializable更高,占用内存相对也更少,但使用起来稍微麻烦点。
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User() {
}
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;//返回当前对象的内容描述,含有文件描述符返回1,否则0
}
public void writeToParcel(Parcel out, int flags) {//将当前对象写入序列号结构中
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
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];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
@Override
public String toString() {
return String.format(
"User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
userId, userName, isMale, book);
}
}
writeToParcel
方法来完成,最终通过Parcel中的一系列write方法完成的。CREATOR
来完成,其内部标明了如何创建序列号对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。describeContents
方法来完成,几乎所有情况都返回0,只有当前对象存在文件描述符时,才返回1。对比
进程间通信(IPC)方式
我们都知道Android中三大组件Activity,Service,Receiver
都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间进行传输。
当我们在一个进程中启动另外一个进程的Activity、Service、Receiver时,我们就可以在Bundle中附加我们所需要传输给远程进程的信息并通过intent发送出去。这里注意,我们传输的数据必须能够被序列化。
下面我们看一下利用Bundle进行进程间通信的例子:
private void startWithIntent(){
Intent intent = new Intent();
//制定要打开的程序的包名(必须写全包名,不然会报错)和地址(activity名)
intent.setComponent(new ComponentName("PackageName",
"PackageName.intentIpcActivity"));
//通过budle传递数据,可以携带序列化数据
Bundle bundle = new Bundle();
bundle.putInt("intextra", 0);
bundle.putString("stringextra", "测试数据");
intent.putExtras(bundle);
try{
startActivity(intent);
}catch(Exception e){
ToastUtils.showMessage("没有找到对应文件");
}
}
利用Bundle进行进程间通信是很容易的,大家应该也注意到,这种方式进行进程间通信只能是单方向的简单数据传输,它的使用有一定的局限性。
共享文件也是种不错的进程间通信的方式,两个进程通过读/写同一个文件来交换数据
比如A在进程中创建一个线程进行写数据
new Thread(new Runnable(){
@Override
public void run(){
User user = new User(1, "user", false);
File cachedFile = new File(CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try{
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectOutputStream.close();
}
}
}).start();
在B进程创建一个线程进行读取数据
new Thread(new Runnable(){
@Override
public void run(){
User user = null;
File cachedFile = new File(CACHE_FILE_PATH);
if (cachedFile.exists()){
ObjectInputStream objectInputStream = null;
try{
objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
user = objectInputStream.readObject(user);
} catch(IOException e){
e.printStackTrace();
}finally{
objectInputStream.close();
}
}
try{
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch (IOException e){
e,printStackTrace();
}finally{
objectOutputStream.close();
}
}
文件共享的方式适合在对数据同步要求不高的进程之间通信
,并且要妥善处理并发读/写问题。SharedPreferences
是个特例,虽然也是文件的一种,但系统在内存中有一份SharedPreferences
文件的缓存,因此在多线程模式下,系统的读/写就变得不可靠
,高并发读写SharedPreferences
有一定几率会丢失数据,因此不建议在多进程通信中使用SharedPreferences
。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,可以在不同进程中传递Messenger对象,在Messenger中放入我们需要传递的数据。
Messenger
通常和Message、Handler
一起使用Messenger
中封装了Handler
,通过Messenger.send(Message)
最终也就是调用了Handler.sendMessage()
Messenger 有两个构造函数:
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target); //和前面的 AIDL 很相似吧
}
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
Handler.getIMessenger
() 源码:getIMessenger
方法获取MessengerImpl
final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl();
return mMessenger;
}
}
这个 IMessanger
应该也是个 AIDL 生成的类
吧,看下源码,果然是:
public interface IMessenger extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements
android.os.IMessenger {
private static final java.lang.String DESCRIPTOR = "android.os.IMessenger";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static android.os.IMessenger asInterface(...}
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags)
throws android.os.RemoteException {...}
private static class Proxy implements android.os.IMessenger {...}
public void send(android.os.Message msg)
throws android.os.RemoteException;
}
IMessenger
是 AIDL 生成的跨进程接口,里面定义了一个发送消息的方法:
public void send(android.os.Message msg)
throws android.os.RemoteException;
Handler
中 MessengerImpl
实现了这个send
方法,就是使用 Handler 将消息发出去:
public void send(Message message) throws RemoteException {
mTarget.send(message);
}
所以,在Handler中有如下代码
getIMessenger
方法获取MessengerImpl
MessengerImpl
又继承自IMessenger.Stub
,并且实现了send
方法,send
方法最终调用的是Handler.sendMessage
总结
Messenger
中持有一个 IMessenger
的引用,在构造函数中可以通过 Handler
或者 Binder
的形式获得最终的 IMessenger
实现,然后调用它的 send
() 方法。Messenger
其实就是 AIDL
的简化版,它把接口都封装好,我们只需在一个进程创建一个 Handler 传递给 Messenger
,Messenger
帮我们把消息跨进程传递到另一个进程,我们在另一个进程的 Handler 在处理消息
就可以了。实现一个Messenger有如下几步,分为服务端和客户端:
1、创建一个Service
来处理客户端的连接请求
2、创建一个Handler
并通过它来创建一个Messager
对象
3、在Service
的onBind
中返回这个Messager
对象底层的Binder
即可
1、绑定这个服务端的Server
2、用服务端返回的IBinder
对象创建一个Messager
对象,通过这个Messager
对象向服务端发送Message消息
3、若需要服务端回应客户端,需要创建一个Handler
并创建一个新的Messager
,并通过Messa
的replyTo
参数传递给服务端,服务端通过这个replyTo
参数就可以回应客户端
<service
android:name=".MyService"
android:process=":wangrui"></service>
2、activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="50dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_ipc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="IPC连接"/>
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_gravity="center"
android:text="IPC通信"/>
</LinearLayout>
3、MyBean.java
public class MyBean implements Parcelable {
private String name;
public MyBean(){
}
protected MyBean(Parcel in) {
name = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<MyBean> CREATOR = new Creator<MyBean>() {
@Override
public MyBean createFromParcel(Parcel in) {
return new MyBean(in);
}
@Override
public MyBean[] newArray(int size) {
return new MyBean[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4、MyService.java
1、创建一个Service来处理客户端的连接请求
2、创建一个Handler并通过它来创建一个Messager对象
3、在Service的onBind中返回这个Messager对象底层的Binder即可
public class MyService extends Service {
private Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//客户端→服务端
Bundle bundle = msg.getData();
bundle.setClassLoader(MyBean.class.getClassLoader());
MyBean myBean = bundle.getParcelable("message");
Toast.makeText(MyService.this,myBean.getName(),Toast.LENGTH_SHORT).show();
//服务端→客户端
try {
Messenger clientMessenger = msg.replyTo;
myBean = new MyBean();
myBean.setName("皮卡丘对王睿使用了十万伏特");
bundle = new Bundle();
bundle.putParcelable("message",myBean);
Message message = new Message();
message.setData(bundle);
message.replyTo = clientMessenger;
clientMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
private Messenger messenger = new Messenger(handler);
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
}
5、MainActivity.java
1、绑定这个服务端的Server
2、用服务端返回的IBinder对象创建一个Messager对象
3、回应客户端,需要创建一个Handler并创建一个新的Messager,并通过Messa的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端
public class MainActivity extends AppCompatActivity {
private Button btnIPC;
private Button btnSend;
private Messenger messengerProxy;
private Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
bundle.setClassLoader(MyBean.class.getClassLoader());
MyBean myBean = bundle.getParcelable("message");
handler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,myBean.getName(),Toast.LENGTH_SHORT).show();
}
},3000);
}
};
private Messenger clientMessenger = new Messenger(handler);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnIPC = findViewById(R.id.btn_ipc);
btnIPC.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MyService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder iBinder) {
messengerProxy = new Messenger(iBinder);
Toast.makeText(MainActivity.this,"连接成功",Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
}
});
btnSend = findViewById(R.id.btn_send);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyBean myBean = new MyBean();
myBean.setName("王睿对皮卡丘使用了精灵球");
try {
Message message = new Message();
message.replyTo = clientMessenger;
Bundle bundle = new Bundle();
bundle.putParcelable("message",myBean);
message.setData(bundle);
messengerProxy.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
特征:
以串行的方式处理客户端发送过来的消息的
并发量大的话用Messenger就不合适了
注意:
客户端和服务端是通过拿到对方的 Messenger
来发送 Message
的。只不过客户端通过 bindService onServiceConnected
而服务端通过 message.replyTo
来获得对方的 Messenger
。Messenger
中有一个 Hanlder
以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。
AIDL (Android Interface Definition Language)
是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类构架,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:
“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。
使用
1、如果ALDL文件中用到了自定义的Parcelable类型
,必须新建一个和它同名ALDL
文件,并在其中声明它为Parcelable
类型。
2、AIDL中除了基本数据类型,其他类型参数必须标上方向:in、out或inout
。
3、AIDL接口中只支持方法,不支持声明静态常量。
4、为了方便AIDL开发,建议把所有和AIDL相关的类和文件都放在同一个包中,好处在于,当客户端是另一个应用的时候,我们可以直接把整个包复制到客户端工程中去。
5、AIDL的包结构在服务端和客户端要保持一致,否则会运行出错。
6、客户端的listener和服务端的listener不是同一个对象,RemoteCallbackList是系统专门提供用于删除跨进程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因为所有AIDL接口都继承自android.os.IInterface接口。
7、需注意AIDL客户端发起RPC过程的时候,客户端的线程会挂起,如果是UI线程发起的RPC过程,如果服务端处理事件过长,就会导致ANR。
AIDL浅显易懂
1、 新建AIDL接口文件
// RemoteService.aidl
package com.example.mystudyapplication3;
interface IRemoteService {
int getUserId();
}
2、创建远程服务
public class RemoteService extends Service {
private int mId = -1;
private Binder binder = new IRemoteService.Stub() {
@Override
public int getUserId() throws RemoteException {
return mId;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
mId = 1256;
return binder;
}
}
3、声明远程服务
<service
android:name=".RemoteService"
android:process=":aidl" />
4、绑定远程服务
public class MainActivity extends AppCompatActivity {
public static final String TAG = "wzq";
IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iRemoteService = IRemoteService.Stub.asInterface(service);
try {
Log.d(TAG, String.valueOf(iRemoteService.getUserId()));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iRemoteService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(MainActivity.this, RemoteService.class), mConnection, Context.BIND_AUTO_CREATE);
}
}
关于ContentProvider的知识都在这里了!
ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider
开发一个ContentProvider的步骤:
ContentProvider
类,该类继承ContentProvider
基类;AndroidManifest.xml
中注册这个ContentProvider
,类似于Activity
注册,注册时要给ContentProvider
绑定一个域名;ContentProvider
后,其他应用就可以访问ContentProvider
暴露出来的数据了。ContentProvider
只是暴露出来可供其他应用操作的数据,其他应用则需要通过ContentReslover
来操作ContentProvider
所暴露出来的数据。Context提供了getContentResolver
()方法来获取ContentProvider
对象,获取之后皆可以对暴露出来的数据进行增、删、改、查操作了。
使用ContentResolver操作数据的步骤:
1、[学习笔记]Android开发艺术探索:IPC机制
2、Android基础-Android进程间通信方式
3、Messenger实现跨进程双向通信
4、Android面试吃透这一篇就没有拿不到的offer!