Android跨进程通信IPC之1——Linux基础进阶之路 | 奇妙的Activity之旅 - 掘金
IPC
Binder机制原理_独饮敌敌畏丶的博客-CSDN博客_binder机制原理【IPC实战①】Messenger实战_独饮敌敌畏丶的博客-CSDN博客
IPC-AIDL
AIDL详解 安卓最新AIDL详解!!!!!!!!_卡卡技术猫的博客-CSDN博客_aidlCopyOnWriteArrayList原理,优缺点,使用场景_二十六画生的博客的博客-CSDN博客_copyonwritearraylist 缺点
Android Interface Definition Language,也就是Android接口定义语言
- 为什么要设计出这么一门语言?
- 它有哪些语法?
- 我们应该如何使用它?
- 再深入一点,我们可以思考,我们是如何通过它来达到我们的目的的?
- 更深入一点,为什么要这么设计这门语言?会不会有更好的方式来实现我们的目的?
Android:学习AIDL,这一篇文章就够了(上)_lypeer的博客-CSDN博客_aidl 在客户端修改,增加一本新书 观察者模式,客户端注册服务端方法,服务端触发事件回调客户端的监听方法 退出活动,应该取消注册,但是却找不到此客户端 demo app 的演示到从为止 注意这里为什么服务端无法成功解除注册 page71-84 退出BookManagerActivity后服务端无法找到注册的listener 对象无法跨进程直接传输 Binder会把 客户端传来的对象重新转化并生成一个新的对象。 新的demo chapter2_1 使用RemoteCallBackList成功解决取消注册 执行耗时操作 客户端在主线程中请求服务器端的耗时操作时 主线程长时间阻塞,客户端可能会ANR 服务端调用客户端的方法时,它也是运行在客户端的线程池中,并不是服务端的哦 因此主动调用还是被动调用都不要放在主线程中
Binder死亡代理
我们都知道,在和service进行交互时,service返回一个Binder对象。Binder是工作在service端,如果,由于某种原因,服务端出现故障而死亡,那么该返回的Binder对象也将消失,这时,如果我们在客户端在使用Binder对象进行某些函数调用将会出现错误。为了避免该情况的发生,我们可以为Binder对象设置死亡代理。当出现和服务端连接发生故障时,系统将自动调用死亡代理函数binderDied()。 死亡代理是由 DeathRecipient 来实现,使用它比较简单,只要重写里面的binderDied()方法即可。
- linkToDeath():为Binder对象设置死亡代理。
- unlinkToDeath():将设置的死亡代理标志清除。
//DeathRecipient方法应该是运行在Binder线程池
private IBinder.DeathRecipient mDeathRecipient =new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.e(TAG, "binderDied: "+Thread.currentThread().getName());
if(mRemoteBookManager==null){
return;
}
//unlinkToDeath():将设置的死亡代理标志清除。
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mRemoteBookManager=null;
//TODO:这里重新绑定远程Service
}
};
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
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);
Book newBook=new Book(3,"开源艺术探索");
bookManager.addBook(newBook);
Log.i(TAG, "add book: "+newBook);
List<Book> newList=bookManager.getBookList();
Log.i(TAG, "query book list: "+ newList);
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager=null;
//onServiceDisconnected应该是运行在主线程
Log.e(TAG, "onServiceDisconnected: binder died!"+Thread.currentThread().getName());
}
};
原文链接: android 之 linkToDeath和unlinkToDeath。(死亡代理)_L_L_R_A_C的博客-CSDN博客_linktodeath 在命令行以su命令杀死服务端进程
AIDL权限验证
权限验证有两种方式:
- 在onBind()中验证
- 在服务端的onTransact()方法中验证
在onBind()中验证(page90)
在menifest中声明
<!-- AIDL的权限验证 声明权限-->
<permission android:name="com.hnucm.chapter2_1.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
在服务端的onBind中
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
int check=checkCallingOrSelfPermission("com.hnucm.chapter2_1.permission.ACCESS_BOOK_SERVICE");
if(check== PackageManager.PERMISSION_DENIED){
Log.e(TAG, "onBind: 没有权限 无法绑定服务" );
return null;//验证不通过
}
return mBinder;
}
<!-- 内部应用绑定服务-->
<uses-permission android:name="com.hnucm.chapter2_1.permission.ACCESS_BOOK_SERVICE"/>
在onTeansact()中验证
即验证了包名又验证了permission
private Binder mBinder = new IBookManager.Stub() {
//....
//page90 权限验证的第二种方法
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
//采用permission验证
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;
//getCallingUid()方法获取到包名
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
Log.d(TAG, "onTransact: " + packageName);
if (!packageName.startsWith("com.hnucm")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
ContentProvider
Carson带你学Android:关于ContentProvider的知识都在这里了!_Carson带你学Android的博客-CSDN博客_contentprovider ContentProvider通过uri来标识其它应用要访问的数据,通过ContentResolver
的增、删、改、查方法实现对共享数据的操作。还可以通过注册ContentObserver来监听数据是否发生了变化来对应的刷新页面。 提供的专门用于不同应用间进行数据共享的方式,底层实现也是Binder(和Messenger一样)
URI是什么? URL是Uniform Resource Locator的缩写,译为"统一资源定位符"。URL是一种URI(Uniform Resource Identifier,URI),它标识一个互联网资源,并指定对其进行操作或获取该资源的方法。
ContentProvider的使用流程: 继承ContentProvider类并实现六个抽象方法。
- onCreate():由系统回调运行在主线程中
- getType():用来返回一个Uri请求所对应的媒体类型
- query()、update()、insert()、delete():由外界回调运行在Binder线程池中
ContentProvider主要易表格的形式组织数据。
1.自定义BookProvider继承ContentProvider
package com.example.chapter2_2;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class BookProvider extends ContentProvider {
private final static String TAG="BookProvider";
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate ,current thread: "+Thread.currentThread().getName());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d(TAG, "onCreate ,current thread: "+Thread.currentThread().getName());
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d(TAG, "getType");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d(TAG, "insert");
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "delete");
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "update");
return 0;
}
}
2.注册BookProvider
<provider
android:authorities="com.hnucm.chapter2_2.book.provider"
android:name=".BookProvider"
android:permission="com.hnucm.PROVIDER"
android:process=":provider">
</provider>
3.外部调用
package com.example.chapter2_2;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
public class ProviderActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
Uri uri=Uri.parse("content://com.hnucm.chapter2_2.book.provider");
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
}
}
首先BookProvider 调用onCreate()方法,运行在主线程 其后三次调用query()方法,执行在Binder线程池中(这里有一个疑问,Binder线程池是怎么处理进程和分配资源的)
4.通过BookProvider调用数据
首先建立一个数据库保存数据
package com.example.chapter2_2;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABlE_NAME = "user";
private static final int DB_VERSION = 3;
//图书和用户信息表
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
+ BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
+ USER_TABlE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT,"
+ "sex INT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
//执行SQL语句
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO ignored
}
}
ContentProvider通过Uri来区分外界要访问的数据集合 外界根据Uri取出Uri_Code,然后根据Uri_Code得到数据表的名称,从而对表中的数据CRUD
public class BookProvider extends ContentProvider {
private final static String TAG="BookProvider";
//...
//外界通过这个方法 根据Uri取出Uri_Code,然后根据Uri_Code得到数据表的名称,从而对表中的数据CRUD
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:break;
}
return tableName;
}
}
Socket套接字
不仅可以实现进程间通信,还可以实现设备间通信。 分类
- 流式套接字:TCP协议
- 用户数据报套接字:UDP协议
知识点TCP与UDP的区别
Socket实现跨进程聊天程序
1.声明权限
<!-- 声明权限-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
2.服务端设计
服务端记得注册哦,不然会报 no fund错误
package com.hnucm.chapter2_3;
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 android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class TCPServerService extends Service {
private final static String TAG="TCPServerService";
private boolean mIsServiceDestoryed = false;
private String[] mDefinedMessages = new String[] {
"你好啊,哈哈",
"请问你叫什么名字呀?",
"今天北京天气不错啊,shy",
"你知道吗?我可是可以和多个人同时聊天的哦",
"给你讲个笑话吧:据说爱笑的人运气不会太差,不知道真假。"
};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestoryed = true;
super.onDestroy();
}
private class TcpServer implements Runnable {
@SuppressWarnings("resource")
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
Log.i(TAG, "run: establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed) {
try {
// 接受客户端请求
final Socket client = serverSocket.accept();
Log.i(TAG, "run: accept");
new Thread() {
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
// 用于接收客户端消息
BufferedReader in = new BufferedReader(new InputStreamReader(
client.getInputStream()));
// 用于向客户端发送消息
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())), true);
out.println("欢迎来到聊天室!");
while (!mIsServiceDestoryed) {
String str = in.readLine();
Log.i(TAG, "responseClient: msg from client:" + str);
if (str == null) {
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg);
Log.i(TAG, "responseClient: send :" + msg);
}
Log.i(TAG, "responseClient: client quit.");
// 关闭流
MyUtils.close(out);
MyUtils.close(in);
client.close();
}
}
3.客户端
package com.hnucm.chapter2_3;
import java.io.*;
import java.net.Socket;
import java.sql.Date;
import java.text.SimpleDateFormat;
import android.annotation.SuppressLint;
import android.app.Activity;
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.util.Log;
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 String TAG="TCPClientActivity";
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_main);
mMessageTextView = (TextView) findViewById(R.id.msg_container);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
//这里mPrintWriter执行IO操作也必须在子线程中哦,书中有错误
new Thread(){
@Override
public void run() {
super.run();
mPrintWriter.println(msg);
}
}.start();
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));
}
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);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
Log.i(TAG, "connectTCPServer: connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
Log.i(TAG, "connectTCPServer: connect tcp server failed, retry...");
}
}
try {
// 接收服务器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msg = br.readLine();
Log.i(TAG, "connectTCPServer: 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();
}
}
Log.i(TAG, "connectTCPServer: quit...");
MyUtils.close(mPrintWriter);
MyUtils.close(br);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
主线程访问访问网络错误 书中客户端的代码有错误,IO操作不应该在主线程中执行,应将mPrintWriter.println(msg);
在子线程中执行
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
//这里mPrintWriter执行IO操作也必须在子线程中哦,书中有错误
new Thread(){
@Override
public void run() {
super.run();
mPrintWriter.println(msg);
}
}.start();
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
}
}
}
Binder连接池(page112-121)
AIDL是进程间通信的首选。
AIDL的使用流程
- 创建一个Service和AIDL接口
- 创建一个类继承自AIDL接口中的Stub类,并实现Stub中的抽象方法(有哪些抽象方法?)
- 在Service的onBind方法中返回这个类对象
- 客户端绑定服务端Service(怎么绑定)
缺点(限制):业务大时,Service的数量会增多,会消耗系统资源。如何减少Service的数量,将所有的AIDL放在同一个Service中管理??
Binder连接池的工作机制
Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。
多业务模块使用AIDL
一个客户端调用多个业务模块SecurityCenterImpl
和 ComputeImpl
,通过中间层Binder池调用queryBinder方法统一拿到不同的业务请求转发给远程Service中,这样便实现了一个Service管理多个AIDL接口:具体代码看module:chapter2_4
package com.hnucm.chapter2_4;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class BinderPoolActivity extends AppCompatActivity {
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_main);
//在子线程中连接远程Service 回顾一下客户端的主线程不要调用服务端的方法,有可能是耗时操作,所以最好在子线程中运行
new Thread(() -> doWork()).start();
}
private void doWork() {
//创建Binder池实例
BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity.this);
//Binder池调用queryBinder方法统一拿到不同的业务请求转发给远程Service中
IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER);//调用queryBinder方法
mSecurityCenter = (ISecurityCenter) SecurityCenterImpl.asInterface(securityBinder);
Log.d(TAG, "visit ISecurityCenter");
String msg = "hello world-安卓";
Log.d(TAG, "doWork content:" + msg);
try {
//执行加密操作
String password = mSecurityCenter.encrypt(msg);
Log.d(TAG, "doWork: encrypt:" + password);
Log.d(TAG, "doWork: decrypt:" + mSecurityCenter.decrypt(password));
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG, "visit ICompute");
//Binder池调用queryBinder方法统一拿到不同的业务请求转发给远程Service中
IBinder computeBinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTE);//调用queryBinder方法
mCompute = ComputeImpl.asInterface(computeBinder);
try {
Log.d(TAG, "doWork: 3+5=" + mCompute.add(3, 5));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
为什么要在子线程中执行
如何扩展业务
只需要新增AIDL接口,以及修改BinderPoolImpl池中的queryBinder方法,无需新增Service类 但缺点也很明显,BinderPoolImpl的逻辑判断会越来越多。