Android跨进程通信方案选型

跨进程通信方案#

原生方案#

对比项管道Pipe 管道Pipe socket AIDL Messenger MemoryFIle signal FileObserver UrlScheme
wiki Pipe
PipedWriter
PipedReader
Socket
ServerSocket
匿名共享内存(Anonymous Shared Memory-Ashmem)
支持双向 NO YES
支持本进程(多个线程)通信 YES YES
支持启动者数量(类型) 不限 不限 Activity\Service\BroadcastReceiver\ContentProvider
支持传输数据类型 基本数据类型、List、Map、String、、Parcelable
支持事件总线 YES
支持返回结果Callback NO YES YES
数据到达率高低 YES YES
数据最大长度 1024byte
管道流默认1024个字符
65536 同步调用为1MB-8KB<span class=”hint–top hint–error hint–medium hint–rounded hint–bounce” aria-label=”Cross Reference: /frameworks/native/libs/binder/ProcessState.cpp (androidxref.com)
“>[5]
oneway调用为(1MB-8kb)
数据传输速度
数据拷贝次数 2次
客户端拷贝一次,服务端拷贝一次
1次,操作的都是Binder驱动的对象
优点 支持设置超时时间 适合服务端被多个客户端访问,适合多线程访问服务端的场景
缺点 客户端和服务端需要各自维护一个子线程监听数据传输 仅适合在不同App之间使用
发生异常不回返回调用者
必须使用Service,需要考虑兼容系统版本
不能传递复杂消息,只能用来同步
本质 IO流 传输层网络协议
  • AIDL仅适合:不同App通过IPC访问服务、服务中需要多线程处理
  • 不跨App,建议使用Binder执行并发IPC
  • 不跨App,且不存在多线程并发,建议使用Messager

    开源方案#

对比项 Andromeda Hermes\HermesEventBus ModularizationArchitecture CC ActivityRouter
参考wiki 跨进程通信框架Andromeda解析架 CC:业界首个支持渐进式组件化改造的Android组件化框架
开源时间 2018年1月 2016年6月 2017年1月 2017年11月
支持双向 YES
支持本进程通信 YES YES YES
支持启动者数量 Fragment\Activity
支持事件总线 YES YES
支持返回结果Callback YES NO YES
数据到达率高低
数据最大长度
数据传输速度
数据拷贝次数
缺点 不支持oneway、in、out、inout 仅支持开发期间
本质 AIDL AIDL AIDL
开发者 爱奇艺 饿了么

原生方案#

Pipe#

#

相关API#

  • Pipeopen()sink()source();

  • CountDownLatchcountDown()await()

  • ByteBufferallocate()、flip()clear()array()

Demo设计#


图-跨进程通信调研-Pipe

Demo代码#

Test.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.util.concurrent.CountDownLatch;

public class PipeTest {
public static void main(String[] args) {
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
Pipe pipe = Pipe.open();
Sender senderThread = new Sender(pipe, countDownLatch);
Receiver receiverThread = new Receiver(pipe, countDownLatch);
senderThread.start();
receiverThread.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

Sender.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.util.concurrent.CountDownLatch;

public class Sender extends Thread {

private Pipe pipe;
private Pipe.SinkChannel sinkChannel;
private CountDownLatch latch;

public Sender(Pipe pipe, CountDownLatch latch) {
this.pipe = pipe;
this.sinkChannel = pipe.sink();
this.latch = latch;
}

@Override
public void run() {
// test input content
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 3; i++) {
String outData = "jack:" + (i + 1) + " times good moring";
stringBuffer.append(outData);
}
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.clear();
buffer.put(String.valueOf(stringBuffer).getBytes());
buffer.flip();
try {
while (buffer.hasRemaining()) {
sinkChannel.write(buffer);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
System.out.println("senderThread send content:" + stringBuffer.toString());
latch.countDown();
System.out.println("senderThread send finish:");
}
}
}

Receiver.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.util.concurrent.CountDownLatch;

public class Receiver extends Thread {
private Pipe pipe;
private Pipe.SourceChannel sourceChannel;
private CountDownLatch latch;

public Receiver(Pipe pipe, CountDownLatch latch) {
this.pipe = pipe;
this.latch = latch;
this.sourceChannel = pipe.source();
}

@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
StringBuffer stringBuffer = new StringBuffer();
try {
len = sourceChannel.read(buffer);
buffer.flip();
String content = new String(buffer.array(), 0, len);
stringBuffer.append(content);
buffer.clear();
} catch (Exception exception) {
exception.printStackTrace();
} finally {
System.out.println("ReceiverThread has receive content:" + stringBuffer);
}

}

}

PipeReader、PipeWriter#

#

相关API#

  • PipedWriterwrite()
  • PipedReader类PipedWriter(PipedWriter)read(buffer)connect()
  • CountDownLatchcountDown()await()
  • CharBufferallocate()、flip()clear()array()

Demo设计#


图-跨进程通信调研-PipedWriter

Demo代码#

Test.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PipedReaderTest {
public static void main(String[] args) {
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
PipedWriter pipedWriter = new PipedWriter();
PipedReader pipedReader = new PipedReader(pipedWriter);
Sender sender = new Sender(pipedWriter,countDownLatch);
Receiver receiver = new Receiver(pipedReader, pipedWriter,countDownLatch);
ExecutorService service = Executors.newCachedThreadPool();
service.execute(sender);
service.execute(receiver);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

Sender.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.IOException;
import java.io.PipedWriter;
import java.util.concurrent.CountDownLatch;

public class Sender implements Runnable {
PipedWriter pipedWriter;
CountDownLatch latch;

public Sender(PipedWriter pipedWriter, CountDownLatch latch) {
this.pipedWriter = pipedWriter;
this.latch = latch;
}

@Override
public void run() {
try {
// TODO Auto-generated method stub
StringBuffer stringBuffer = new StringBuffer();
for (char a = 'a'; a <= 'z'; a++) {
stringBuffer.append(a);
}
pipedWriter.write(stringBuffer.toString().toCharArray(), 0, stringBuffer.length());
System.out.println("sedner content :"+stringBuffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
latch.countDown();
System.out.println("sedner has finish");
}
}
}

Receiver.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.concurrent.CountDownLatch;

public class Receiver implements Runnable {
private PipedWriter pipedWriter;
private PipedReader pipedReader;
private CountDownLatch latch;

public Receiver(PipedReader pipedReader, PipedWriter pipedWriter, CountDownLatch latch) {
this.pipedReader = pipedReader;
this.pipedWriter = pipedWriter;
this.latch = latch;
}

@Override
public void run() {
try {
latch.await();
StringBuffer stringBuffer = new StringBuffer();
CharBuffer buffer = CharBuffer.allocate(1024);
int len = pipedReader.read(buffer);
buffer.flip();
String a = new String(buffer.array(),0,len);
stringBuffer.append(a);
buffer.clear();
System.out.println("receiver content:" + stringBuffer);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
System.out.print("receiver has finish");
}
}

}

Socket#

#

相关API#

  • ServerSocket类:accept()
  • Socket类:getInputStream()getOutputStream()
  • PrintStream类:println()
  • BufferedReader类:readLine()
  • System类:System.in
  • Collections

Demo设计#


图-跨进程通信调研-Socket

Demo代码#

MySever.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MySever {
public static List<Socket> socketList = Collections.synchronizedList(new ArrayList<>());

public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(10086);
System.out.println("服务器启动");
int count = 0;
while (true) {
Socket s = serverSocket.accept();
System.out.println("识别到新用户进入:");
count++;
socketList.add(s);
new Thread(new ServerThread(s)).start();
System.out.println("client enter chat home successful!");
System.out.println("chat room has"+count+" users");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

ServerThread.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;

public class ServerThread implements Runnable {
Socket Socket;
BufferedReader br = null;

public ServerThread(Socket socket) {
this.Socket = socket;
try {
this.br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Override
public void run() {
// TODO Auto-generated method stub
String conent = null;
while ((conent = readFromClient()) != null) {
for (Socket s : MySever.socketList) {
PrintStream printStream;
try {
printStream = new PrintStream(s.getOutputStream());
// client message print
System.out.println("server receive message:" + conent);
InetAddress inetAddress = s.getInetAddress();
StringBuffer stringReply = new StringBuffer();
stringReply.append("{'to_client':{");
stringReply.append("client_ip:" + inetAddress.getHostAddress() + ",");
stringReply.append("server_message:" + "服务器很好,勿念");
stringReply.append("}}");
// response message to client
printStream.println("server say:" + stringReply + " ,by");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

private String readFromClient() {
try {
return br.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
MySever.socketList.remove(Socket);
}
return null;
}

}

MyClient.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class MyClient {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("127.0.0.1", 10086);
new Thread(new ClientThread(socket)).start();
PrintStream printStream = new PrintStream(socket.getOutputStream());
String line = null;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
while ((line = bufferedReader.readLine()) != null) {
printStream.println(line);
}
}
}

ClientThread.java#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ClientThread implements Runnable {
Socket socket;
BufferedReader bufferedReader = null;

public ClientThread(Socket socket) {
this.socket = socket;
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Override
public void run() {
// TODO Auto-generated method stub
String conent = null;
try {
while ((conent = bufferedReader.readLine()) != null) {
System.out.println(conent);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

Binder#

文档地址#

Cross Reference: /frameworks/native/libs/binder/ (androidxref.com)

binder三层架构<span class=”hint–top hint–error hint–medium hint–rounded hint–bounce” aria-label=”Binder系列2—Binder Driver再探 - Gityuan博客 | 袁辉辉的技术博客#

“>[6]

从上至下应用层、IPC传输层、内核层

img

Binder通信架构#

通信模型分为客户端和服务端,通过读取Driver驱动的数据完成通信

img

  • 请求码:BC_开头,简称BC码,用于从IPC层传递到BinderDriver层
  • 响应码:BR_开头,简称BR码,用于从BinderDriver层传递数据到IPC层

binder内存机制mmap#

img

内存分为两个类型:虚拟地址和物理地址

  • 虚拟地址分两个类型:用户空间和内核空间
    • 虚拟地址是什么?
    • 用户空间是什么?
    • 内核空间是什么?
  • 物理地址空间是指什么?存疑:CPU上具体的内存地址

用户空间上的某块地址vm_area_struct,内核空间上的vm_struct都会映射到同一块物理内存空间地址上。这一块物理内存叫mmap

这块mmap内存有什么特征?

  • 客户端向服务端发送数据时,先从客户端所在的用户空间,把数据通过**copy_from_user**拷贝到内核空间
  • 服务端所在的用户空间与内核空间共享位于物理地址空间mmap空间处的同一份数据(该数据是位于mmap空间中,不代表等于mmp空间),服务端所在的用户空间读取该位置的数据时,不需要拷贝数据,而是直接得到物理地址空间的内存地址。
  • 因此数据传输只发生一次拷贝,也就是客户端用户空间的数据拷贝至内核空间。
  • 服务端进程直接读取物理地址空间的内存地址。

数据流向图如下:

img

1。红色箭头,代表客户端地址空间将数据拷贝到内核地址空间

2.。绿色虚线箭头,是双向的,代表内核地址空间与物理地址空间是双向访问的,

3。绿色实现箭头,是双向的,代表服务端空间地址与内核空间地址是双向访问的,

4。2和3表明一个事实,服务端可以直接访问内核空间、物理空间的地址,而不需要创建一份拷贝

5。黑色箭头表明,服务端访问内核空间的地址时,需要将数据读取至缓存buffer中;写数据时,也需要将数据写至缓存buffer中

AIDL#

#

相关API#

  • Service类:onBind()
  • IRemoteService类:setUser() isLogin()asInterface()
  • IRemoteService.Stub类:setUser() isLogin()asInterface()
  • SharedPreferences类:putStringcommitapplySharedPreferences.Editor
  • ServiceConnection类:onServiceConnected() onServiceDisconnected()
  • Activity类:bindService()
  • AIDL语法:in out inout oneway

?>in out inout区别?

  • in 表示数据只能由客户端流向服务端

  • out 表示数据只能由服务端流向客户端

  • inout 则表示数据可在服务端与客户端之间双向流通。

    其中,数据流向是针对在客户端中的那个传入方法的对象而言的。

    in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;

    out 的话表现为服务端将会接收到那个对象的参数为空的对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;

    inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动

    in参数使得实参顺利传到服务方,但服务方对实参的任何改变,不会反应回调用方。

    out参数使得实参不会真正传到服务方,只是传一个实参的初始值过去(这里实参只是作为返回值来使用的,这样除了return那里的返回值,还可以返回另外的东西),但服务方对实参的任何改变,在调用结束后会反应回调用方。

    inout参数则是上面二者的结合,实参会顺利传到服务方,且服务方对实参的任何改变,在调用结束后会反应回调用方。

    其实inout,都是相对于服务方。in参数使得实参传到了服务方,所以是in进入了服务方;out参数使得实参在调用结束后从服务方传回给调用方,所以是out从服务方出来。

Demo设计#


图-AIDLDemoUI设计

图-AIDLDemo UML类图设计

Demo代码-基本数据类型#

IRemoteService.aidl#

1.位于src\main\aidl\com\shaunsheep\aidlclient\IRemoteService.aidl

2.build/rebuild的时候会生成Java版本的IRemoteService接口,输出结果位于build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\com\shaunsheep\aidlclient\IRemoteService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// IRemoteService.aidl
package com.shaunsheep.aidlclient;

// Declare any non-default types here with import statements
interface IRemoteService {

/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
void setUser(String userName,String passWord);
boolean isLogin();
}

服务端RemoteService.java#

1.提供一个内部类IRemoteService.Stub mBinder

2.让客户端App调用,会返回给客户端App这个mBinder对象,通过mBinder对象,客户端可以生成IRemoteService的实现类。有了这个实现类,客户端App就可以IPC通信,调用RemoteService下的mBinder方法,获取登录状态,设置登录信息

3.让服务端App调用onServiceConnected,可以获取到mBinder,进而可以IPC通信,调用mBinder的方法,获取登录状态、设置登录信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.shaunsheep.server;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;

import com.shaunsheep.aidlclient.IRemoteService;

/**
* @author
*/
public class RemoteService extends Service {
private static final String SP_USERNAME_ = "userName";
private static final String SP_PASSWORD_ = "password";
private static final String SP_FILE_NAME = "accountdb";
private SharedPreferences mPreferences;
private SharedPreferences.Editor mEdit;

public RemoteService() {

}

@Override
public void onCreate() {
super.onCreate();
mPreferences = getSharedPreferences(SP_FILE_NAME, Context.MODE_PRIVATE);
mEdit = mPreferences.edit();
}

@Override
public IBinder onBind(Intent intent) {
// return stub implementation
return mBinder;
}

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

}

@Override
public void setUser(String userName, String passWord) throws RemoteException {
mEdit.putString(SP_USERNAME_,userName);
mEdit.putString(SP_PASSWORD_,passWord);
mEdit.commit();
Log.d("shaunsheep","setUser!!!");
}

@Override
public boolean isLogin() throws RemoteException {
String userName = mPreferences.getString(SP_USERNAME_,"");
String password = mPreferences.getString(SP_PASSWORD_,"");
Log.d("shaunsheep","isLogin!!!");
if (TextUtils.isEmpty(userName) || TextUtils.isEmpty(password)) {
return false;
}
return true;
}

};

}

记得在服务端清单文件注册该服务

1
2
3
4
5
6
7
8
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.shaunsheep.IRemoteService" />
</intent-filter>
</service>

服务端MainActivity#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.shaunsheep.server;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.shaunsheep.aidlclient.IRemoteService;

public class MainActivity extends AppCompatActivity {
private IRemoteService.Stub mBinder;

/**
* 获取app的信息显示到server上
*/
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = (IRemoteService.Stub) service;
changeStatus();
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};
private TextView mTextView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(MainActivity.this, RemoteService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
mTextView = findViewById(R.id.tv_data);
}

@Override
protected void onResume() {
super.onResume();
changeStatus();
}

@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
mBinder = null;
}

public void changeStatus() {
try {
if (mBinder == null) {
return;
}
if (mBinder.isLogin()) {
mTextView.setText("已登录");
} else {
mTextView.setText("未登录");
}
} catch (RemoteException e) {
e.printStackTrace();
}

}

public void logout(View view) {
try {
mBinder.setUser("", "");
changeStatus();
} catch (RemoteException e) {
e.printStackTrace();
}

}

public void login(View view) {
try {
mBinder.setUser("jack123", "123");
changeStatus();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

客户端MainActivity#

1.启动服务端App的Service,绑定ServiceConnection链接

2.从ServiceConnection中拿到服务端Service的内部类IBinder子类

3.通过IRemoteService.Stub.asInterface(ibinder)ibinder子类转为IRemoteService实现类

4.在客户端App任何位置都可以通过操作IRemoteService实现类,来达到访问服务端App的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package com.shaunsheep.aidlclient;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
private IRemoteService mRemoteService;
private ServiceConnection mServerConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mRemoteService = IRemoteService.Stub.asInterface(service);
if (mRemoteService != null) {
try {
boolean isLogin = mRemoteService.isLogin();
changeLoginState(isLogin);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteService = null;
}
};
private TextView mTvTest;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvTest = findViewById(R.id.tv_test);

Intent intent = new Intent(IRemoteService.class.getName());
intent.setPackage("com.shaunsheep.server");
intent.setAction("com.shaunsheep.IRemoteService");
bindService(intent, mServerConnection, BIND_AUTO_CREATE);
}

public void login(View view) {
if (mRemoteService != null) {
try {
mRemoteService.setUser("shaunshepp", "123123");
boolean isLogin = mRemoteService.isLogin();
changeLoginState(isLogin);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

public void changeLoginState(boolean isLogin) {
if (isLogin) {
mTvTest.setText("已登录");
} else {
mTvTest.setText("未登录");
}
}

@Override
protected void onResume() {
super.onResume();
try {
if (mRemoteService == null) {
return;
}
changeLoginState(mRemoteService.isLogin());
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServerConnection);
}

public void logout(View view) {
if (mRemoteService != null) {
try {
mRemoteService.setUser("", "");
boolean isLogin = mRemoteService.isLogin();
changeLoginState(isLogin);

} catch (RemoteException e) {
e.printStackTrace();
}
}

}
}

Demo代码-自定义数据类型#

我们假设提供一个User对象,供客户端和服务端通信使用

它有如下步骤;

在服务端进行如下步骤:

  1. 在src/main/aidl目录下创建User.java,继承Parcelable接口,实现必要的方法
  2. 在src/main/aidl目录下创建User.aidl,声明User.java可以被引用
  3. 在src/main/aidl/IRemoteService里import 导入User,提供User的设置和访问接口

在客户端进行如下步骤:

  1. 在src/main/aidl目录下创建User.java,继承Parcelable接口,实现必要的方法
  2. 在src/main/aidl目录下创建User.aidl,声明User.java可以被引用
  3. 在src/main/aidl/IRemoteService里import 导入User,提供User的设置和访问接口

!>注意事项

1.客户端和服务端的aidl包名必须一致

2.传输自定义数据类型的时候,注意in、out、inout的使用

IRemoteService.java#

文件位置:

服务端app-server/src/main/aidl/com/shaunsheep/server/IRemoteService.aidl

客户端app/src/main/aidl/com/shaunsheep/server/IRemoteService.aidl

!>注意客户端和服务端包名、文件夹名皆需要一致

比前一个demo相比,多了setUserObject接口和getUser接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// IRemoteService.aidl
// 客户端服务端包名一致
package com.shaunsheep.server;

// Declare any non-default types here with import statements
import com.shaunsheep.server.User;
interface IRemoteService {

/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);

void setUser(String userName,String passWord);
void setUserObject(in User user);
boolean isLogin();
User getUser();
}

User.aidl#

文件位置:

服务端app-server/src/main/aidl/com/shaunsheep/server/User.aidl

客户端app/src/main/aidl/com/shaunsheep/server/User.aidl

!>注意客户端和服务端包名、文件夹名皆需要一致

1
2
3
4
// User.aidl
package com.shaunsheep.server;
// Declare any non-default types here with import statements
parcelable User;

User.java#

文件位置:

服务端app-server/src/main/aidl/com/shaunsheep/server/User.java

客户端app/src/main/aidl/com/shaunsheep/server/User.java

!>注意客户端和服务端包名、文件夹名皆需要一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.shaunsheep.server;

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

public final class User implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;
public String userName;
public String passWord;

public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}

@Override
public User[] newArray(int size) {
return new User[size];
}
};

public User() {
}

private User(Parcel in) {
readFromParcel(in);
}

@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
out.writeString(userName);
out.writeString(passWord);

}

public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
userName = in.readString();
passWord = in.readString();
}

public int describeContents() {
return 0;
}
}

服务端RemoteService.java#

比上一个demo相比,内部类mBinder多了俩对外暴露的接口setUserObjectgetUser,需要实现一下,供客户端和服务端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.shaunsheep.server;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;


/**
* @author
*/
public class RemoteService extends Service {
private static final String SP_USERNAME_ = "userName";
private static final String SP_PASSWORD_ = "password";
private static final String SP_FILE_NAME = "accountdb";
private SharedPreferences mPreferences;
private SharedPreferences.Editor mEdit;

public RemoteService() {

}

@Override
public void onCreate() {
super.onCreate();
mPreferences = getSharedPreferences(SP_FILE_NAME, Context.MODE_PRIVATE);
mEdit = mPreferences.edit();
}

@Override
public IBinder onBind(Intent intent) {
// return stub implementation
return mBinder;
}

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

}

@Override
public void setUser(String userName, String passWord) throws RemoteException {
mEdit.putString(SP_USERNAME_, userName);
mEdit.putString(SP_PASSWORD_, passWord);
mEdit.commit();
Log.d("shaunsheep", "setUser!!!");
}

@Override
public boolean isLogin() throws RemoteException {
String userName = mPreferences.getString(SP_USERNAME_, "");
String password = mPreferences.getString(SP_PASSWORD_, "");
Log.d("shaunsheep", "isLogin!!!");
if (TextUtils.isEmpty(userName) || TextUtils.isEmpty(password)) {
return false;
}
return true;
}

@Override
public void setUserObject(User user) throws RemoteException {
if (user == null) {
return;
}
mEdit.putString(SP_USERNAME_, user.userName);
mEdit.putString(SP_PASSWORD_, user.passWord);
mEdit.commit();
}

@Override
public User getUser() throws RemoteException {
String userName = mPreferences.getString(SP_USERNAME_, "");
String password = mPreferences.getString(SP_PASSWORD_, "");
User user = new User();
user.userName = userName;
user.passWord = password;
return user;
}
};

}

客户端MainActivity#

调用获取服务端的getUser接口数据,显示到UI上;设置User对象,传递给服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void getUser(View view) {
try {
User user = mRemoteService.getUser();
mTvUserData.setText("{" + user.userName + ":" + user.passWord + "}");
} catch (RemoteException e) {
e.printStackTrace();
}
}

public void setUserObject(View view) {
try {
User user = new User();
user.passWord = "jack";
user.userName = "jack";
mRemoteService.setUserObject(user);
mTvUserData.setText("{" + user.userName + ":" + user.passWord + "}");
} catch (RemoteException e) {
e.printStackTrace();
}
}

服务端MainActivity#

新增俩UI控制接口getUsersetUserObject,一个是从服务端获取User对象,一个是设置User对象给服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void getUser(View view) {
try {
User user = mBinder.getUser();
mUserDataView.setText("{"+user.userName+":"+user.passWord+"}");
} catch (RemoteException e) {
e.printStackTrace();
}
}

public void setUserObject(View view) {
try {
User user = new User();
user.passWord = "shaunsheep";
user.userName = "shaunsheep";
mBinder.setUserObject(user);
mUserDataView.setText("{"+user.userName+":"+user.passWord+"}");
} catch (RemoteException e) {
e.printStackTrace();
}
}

Demo代码-Bundle数据类型#

?>必现问题-ClassNotFoundException 自定义类型找不到

Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.shaunsheep.server.User

解决办法,在获取bundle的所有位置,包括客户端和服务端,都加入类加载器[1]

IRemoteService.java#

文件位置:

服务端app-server/src/main/aidl/com/shaunsheep/server/IRemoteService.aidl

客户端app/src/main/aidl/com/shaunsheep/server/IRemoteService.aidl

!>注意客户端和服务端包名、文件夹名皆需要一致

比前一个demo相比,多了bundle操作接口setUserBundle getUserBundle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// IRemoteService.aidl
package com.shaunsheep.server;

// Declare any non-default types here with import statements
import com.shaunsheep.server.User;
interface IRemoteService {

/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);

void setUser(String userName,String passWord);
void setUserObject(in User user);
boolean isLogin();
User getUser();
void setUserBundle(in Bundle bundle); // new create
Bundle getUserBundle(); // new create
}

服务端RemoteService.java#

!>注意,一旦引入Bundle类,就需要为其设置类加载,set方法和get方法都需要设置:

bundle.setClassLoader(getClass().getClassLoader());

比上一个demo相比,内部类mBinder多了俩对外暴露的接口setUserBundlegetUserBundle,需要实现一下,供客户端和服务端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.shaunsheep.server;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;

/**
* @author
*/
public class RemoteService extends Service {
private static final String SP_USERNAME_ = "userName";
private static final String SP_PASSWORD_ = "password";
private static final String SP_FILE_NAME = "accountdb";
private SharedPreferences mPreferences;
private SharedPreferences.Editor mEdit;

public RemoteService() {

}

...

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {

...
@Override
public Bundle getUserBundle() throws RemoteException {
String userName = mPreferences.getString(SP_USERNAME_, "");
String password = mPreferences.getString(SP_PASSWORD_, "");
Bundle bundle = new Bundle();
// you must add
bundle.setClassLoader(getClass().getClassLoader());

User user = new User();
user.userName = userName;
user.passWord = password;
bundle.putParcelable("user", user);
return bundle;
}

@Override
public void setUserBundle(Bundle bundle) throws RemoteException {
// you must add
bundle.setClassLoader(getClass().getClassLoader());

if (bundle == null) {
return;
}
User user = bundle.getParcelable("user");
mEdit.putString(SP_USERNAME_, user.userName);
mEdit.putString(SP_PASSWORD_, user.passWord);
mEdit.commit();
}
};

}

服务端MainActivity#

新增俩UI控制接口getBundlesetBundle,一个是从Service中读取Bundle中的User对象,一个是设置Bundle里的User对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void getBundle(View view) {

try {
Bundle bundle = mBinder.getUserBundle();
User user = bundle.getParcelable("user");
mUserDataView.setText("{" + user.userName + ":" + user.passWord + "}");
} catch (RemoteException e) {
e.printStackTrace();
}
}

public void setBundle(View view) {
Bundle bundle = new Bundle();
User user = new User();
user.passWord = "jack-bundle";
user.userName = "jack-bundle";
bundle.putParcelable("user", user);
try {
mBinder.setUserBundle(bundle);
} catch (RemoteException e) {
e.printStackTrace();
}
}

客户端MainActivity#

!>注意,客户端读取Bundle中的User对象,务必加上类加载器的声明

undle.setClassLoader(getClass().getClassLoader());

其他代码跟服务端MainActivity代码很类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void getBundle(View view) {

try {
Bundle bundle = mRemoteService.getUserBundle();
// you must add
bundle.setClassLoader(getClass().getClassLoader());

User user = bundle.getParcelable("user");
mTvUserData.setText("{" + user.userName + ":" + user.passWord + "}");
} catch (RemoteException e) {
e.printStackTrace();
}
}

public void setBundle(View view) {
Bundle bundle = new Bundle();
User user = new User();
user.passWord = "jack-bundle";
user.userName = "jack-bundle";
bundle.putParcelable("user", user);
try {
mRemoteService.setUserBundle(bundle);
} catch (RemoteException e) {
e.printStackTrace();
}
}

AIDL的注意事项#

1.客户端和服务端的AIDL文件所在包包名必须相同[3]

2.自定义对象,在AIDL中引用,必须加import语句[1]

3.ADIL文件中函数参数有自定义对象,必须加in、out、inout数据流向关键字[1]

4.客户端与服务端的AIDL文件必须完全相同,一个关键字也不能有差异[2]

5.在实现自定义对象类的时候,变量序列化的顺序和反序列化的顺序必须完全一致

6.AIDL内不支持方法重载(存疑)

7.AIDL传输Bundle对象,必须在调用getBundle的位置指明类加载器[1]

AIDL常见问题#

1.in、out、inout有什么联系和区别

2.AIDL自定义对象的步骤,找不到该类如何解决?import

3.AIDL使用Bundle的步骤,找不到Bundle中的类如何解决?设置类加载器[1]

4.AIDL支持哪些数据类型

5.服务端挂掉,客户端能访问成功吗

6.客户端如何连接服务端?

7.客户端如何断开连接服务端?

8.序列化一个对象的步骤?

9.序列化的两种方式对比?

10.Bundle中为什么找不到自定义的User对象?-参考一个Integer找不到main方法错误体会双亲委派和类加载机制

11.oneway有什么用?异步

12.oneway异步会anr吗?

实战问题

假设有服务端A,客户端B、C、E…

1
2
3
4
5
6
7
8
9
10
接口1 Iplayer1.aidl
interface IPlayer1 {
oneway void start();//异步,执行2秒
oneway void stop();//异步,执行2秒
}
接口2 Iplayer2.aidl
interface IPlayer2 {
oneway void start();//异步,执行2秒
oneway void stop();//异步,执行2秒
}

13.多线程访问服务端的接口Iplayer1#start,1000个线程访问,start平均耗时2秒,有什么风险?[4]

14.多线访问服务端的异步oneway接口Iplayer1#start,1000个线程访问,start平均耗时2秒,有什么风险?[4]

15.多个客户端访问服务端的多个binder,如IPlayer1、IPlayer2

服务端处理binder调用,是单线程还是多线的?多个客户端访问服务端的binder服务,会怎么样?

  • 客户端B、C、E..同时访问服务端Iplayer1#start接口,会同时执行吗?会咋样?

  • oneway的binder通信,服务端进程一次只有一个binder线程处理一个oneway的binder请求,其余请求会排队等待执行

16.多个客户端访问同一个binder驱动,如Iplayer1

  • 同一时刻客户端B访问服务端A的IPlayer1#start,进程C访问服务端A的IPlayer2#start,服务端A能否同时响应两次Binder调用并执行?答:能
  • 同一时刻客户端B访问服务端A的IPlayer1#start,客户端C访问服务端A的IPlayer1#stop,服务端A能否同时响应Iplayer1的两个不同方法调用?答:不能

线程通信方案#

  • 全局变量

  • Handler

  • Semaphore

  • countDownLatch

  • 管道

  • ThreadLocal

  • EventBus

#

问题集合#

Service Intent must be explicit: Intent#

  • 使用隐式意图的话,加上包名信息即可;
1
2
3
4
Intent intent = new Intent();  
intent.setAction("com.yulore.recognize.android");
intent.setPackage(context.getPackageName()); //兼容Android 5.0
context.startService(intent);
  • 显示意图
1
2
Intent intent = new Intent(com.yulore.test.AppService.class);  
context.startService(intent);
点击查看
-------------------本文结束 感谢您的阅读-------------------