Android——进程间通信方式

Posted Yawn,

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——进程间通信方式相关的知识,希望对你有一定的参考价值。

0. 前置知识

首先我们需要知道几点:RPC,IDL,IPC分别是什么。

RPC

  • Remote Procedure Call (远程过程调用) 是一种计算机通讯协议,为我们定义了计算机 C 中的程序如何调用另外一台计算机 S 的程序
  • RPC 是 Client/Server 模式,客户端对服务器发出请求,服务器收到请求并且根据客户端提供的参数进行操作,然后结果返回给客户端
  • RPC 位于 OSI 模型中的会话层
    在这里插入图片描述

IDL

  • Interface Description Language (接口定义语言),通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流
  • RPC 只是一种协议,规定了通信的规则
  • 因为客户端与服务端平台的差异性,为了统一处理不同的实现,需要定义一个共同的接口,即就是IDL

IPC

  • Inter-Process Communication (进程间通信)
  • android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”
    在这里插入图片描述
  • 只有允许不同应用的客户端用 IPC 方式调用远程方法,并且想要在服务中处理多线程时,才有必要使用 AIDL
  • 如果需要调用远程方法,但不需要处理并发 IPC,就应该通过实现一个 Binder 创建接口
  • 如果您想执行 IPC,但只是传递数据,不涉及方法调用,也不需要高并发,就使用 Messenger 来实现接口
  • 如果需要处理一对多的进程间数据共享(主要是数据的 CRUD),就使用 ContentProvider
  • 如果要实现一对多的并发实时通信,就使用 Socket

1.Intent

Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle实现了 Parcelable 接口,可以在不同的进程间进行传输。

在一个进程中启动了另一个进程的 Activity,Service 和 Receiver ,可以在Bundle 中附加要传递的数据通过 Intent 发送出去。

可以看看,Android——Bundle浅析

2. 文件共享

  • Windows 上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而 Android 系统基于 Linux ,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,尽管这样可能会出问题。
  • 可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同)
  • SharedPreferences 是个特例,系统对它的读 / 写有一定的缓存策略,即内存中会有一份ShardPreferences 文件的缓存,系统对他的读 / 写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很多大的几率丢失数据。因此,IPC 不建议采用 SharedPreferences

3. Messenger

Messenger是一种轻量级的 IPC 方案,它的底层实现是 AIDL ,可以在不同进程中传递 Message 对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。

服务端进程:服务端创建一个 Service 来处理客户端请求,同时通过一个Handler 对象来实例化一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。

public class MessengerService extends Service {
	private static final String TAG = MessengerService.class.getSimpleName();
	private class MessengerHandler extends Handler {
	@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
				case Constants.MSG_FROM_CLIENT:
					Log.d(TAG, "receive msg from client: msg = ["+ msg.getData().getString(Constants.MSG_KEY) + "]");
					Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
					Messenger client = msg.replyTo;
					Message replyMsg = Message.obtain(null, Cons
					tants.MSG_FROM_SERVICE);
					Bundle bundle = new Bundle();
					bundle.putString(Constants.MSG_KEY, "我已经收到你的消息,稍后回复你!");
					replyMsg.setData(bundle);
					try {
						client.send(replyMsg);
					} catch (RemoteException e) {
						e.printStackTrace();
					}
					break;
				default:
					super.handleMessage(msg);
			}
		 }
	}
	private Messenger mMessenger = new Messenger(new MessengerHandler());
	@Nullable
	@Override
	public IBinder onBind(Intent intent) {
		return mMessenger.getBinder();
	}
}

客户端进程:首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。

public class MainActivity extends AppCompatActivity {
	private static final String TAG = MainActivity.class.getSimpleName();
	private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
	private Messenger mService;
	private class MessageHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
				case Constants.MSG_FROM_SERVICE:
					Log.d(TAG, "received msg form service: msg =[" + msg.getData().getString(Constants.MSG_KEY) + "]");
					Toast.makeText(MainActivity.this, "receivedmsg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
					break;
				default:
					super.handleMessage(msg);
			}
		}		
	}
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}
	public void bindService(View v) {
		Intent mIntent = new Intent(this, MessengerService.class);
		bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
	}
	public void sendMessage(View v) {
		Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
		Bundle data = new Bundle();
		data.putString(Constants.MSG_KEY, "Hello! This is client.");
		msg.setData(data);
		msg.replyTo = mGetReplyMessenger;
		try {
			mService.send(msg);
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	@Override
	protected void onDestroy() {
		unbindService(mServiceConnection);
		super.onDestroy();
	}
	private ServiceConnection mServiceConnection = new ServiceConnection() {
	/**
	* @param name
	* @param service
	*/
	@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mService = new Messenger(service);
			Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
			Bundle data = new Bundle();
			data.putString(Constants.MSG_KEY, "Hello! This is client.");
			msg.setData(data);
		
			msg.replyTo = mGetReplyMessenger;
			try {
				mService.send(msg);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
/**
* @param name
*/
@Override
		public void onServiceDisconnected(ComponentName name) {
		}
	};
}

注意:客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。只不过客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。Messenger 中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。

4. 使用 AIDL(难点)

AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。

Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。

AIDL 文件支持的数据类型:

  • 基本数据类型;
  • String 和 CharSequence
    在这里插入图片描述
  • ArrayList ,里面的元素必须能够被 AIDL 支持;
  • HashMap ,里面的元素必须能够被 AIDL 支持;
  • Parcelable ,实现 Parcelable 接口的对象; 注意:如果 AIDL 文件中用到了
    自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。
  • AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。

服务端:

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

客户端:

绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。

具体使用我们看看这篇博文:Android进阶——AIDL详解
接下来让我们看一个实例:

创建两个工程,一个作为服务端,一个作为客户端,客户端绑定服务端service,然后调用方法向服务端获取书籍列表,向服务端添加书籍。

1、服务端:
(1)创建aidl文件Book.aidl
在这里插入图片描述
创建后便可以在目录里看到aidl文件。
在这里插入图片描述
接下来定义Book类,注意Books类的包名必须与Book.aidl包名一样,但是不可与Book.aidl在同一个目录下。
在这里插入图片描述
Book.class的代码如下,其必须继承Parcelable接口:

package com.status.aidlproject.com.aidl;

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

public class Book implements Parcelable {

    private String bookName;

    public Book(String name) {
        bookName = name;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookName = in.readString();
    }

    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];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

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

    public void readFromParcel(Parcel desc) {
        bookName = desc.readString();
    }

    @Override
    public String toString() {
        return "bookName=" + bookName;
    }
}

接下来修改Book.aidl文件,将其声明为parcelable类型,并且需要注意的是,原先默认的interface接口需要去掉,否则编译会报错。

// Book.aidl
package com.status.aidlproject.com.aidl;

// Declare any non-default types here with import statements

parcelable Book;
//需要删除
/*interface Book {
    void default(String s);
}*/

接下来便是要定义服务端暴露给客户端的接口了(获取书籍列表,添加书籍)

在同样的目录定义aidl文件BookManager.aidl
在这里插入图片描述
代码如下:

// BookManager.aidl
package com.status.aidlproject.com.aidl;

// Declare any non-default types here with import statements
import com.status.aidlproject.com.aidl.Book;
interface   {
    List<Book> getBookList();

    void addBook(inout Book book);
}

注意要把包手动导进来。

接下来,make project便可以看到aidl编译成代码文件
在这里插入图片描述
在这里插入图片描述
这个文件才是我们真正需要用到的。

(2)创建Service,供客户端绑定

package com.status.aidlproject;

import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

import com.status.aidlproject.com.aidl.Book;
import com.status.aidlproject.com.aidl.BookManager;

import java.util.ArrayList;
import java.util.List;

public class BookService extends Service {
    private final String TAG = BookService.class.getSimpleName();
    private List<Book> list;

    @Override
    public void onCreate() {
        super.onCreate();
        list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Book book = new Book("第" + i + "本书");
            list.add(book);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return bookManager;
    }

    private BookManager.Stub bookManager = new BookManager.Stub() {
        @Override
        public List<Book> getBookList() {
            Log.d(TAG, "getBookList");
            return list;
        }

        @Override
        public void addBook(Book book) {
            Log.d(TAG, "addBook");
            if (book != null) {
                list.add(book);
            }
            Log.d(TAG, book.toString());
        }
    };

    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
    }

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

Manifests文件中可以这样写:

<service
     android:name=".BookService"
     android:enabled="true"
     android:exported="true"
     android:process=":remote">
        <intent-filter>
           <action android:name="android.intent.action.BookService" />
        </intent-filter>
</service>

onBind方法返回的是BookManager.Stub对象,实现里面的两个方法,客户端拿到这个对象后就可以与服务端通讯了。

2、客户端

(1)将aidl文件与Book.class拷贝到客户端的工程,注意他们的目录需要与服务端的一样,也就是说aidl与Book.class包名要与服务端的一样。
在这里插入图片描述
make project一下便会生成编译后的文件
在这里插入图片描述
(2)、编写代码,与服务端连接

package com.status.aidlclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.status.aidlproject.com.aidl.Book;
import com.status.aidlproject.com.aidl.BookManager;
import com.status.aidlproject.com.aidl.IMyConnect;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private final String TAG = MainActivity.class.Android进程间的通信之Messenger

Android进程间的通信之Messenger

Android进程间通信方式

Android初级教程进程间的通信AIDL

Android:安卓学习笔记之进程间通信方式(IPC)的简单理解和使用

Android:安卓学习笔记之进程间通信方式(IPC)的简单理解和使用