学习笔记| (二)IPC机制
一.多进程
1.创建多进程:
- 在android 中通过
android:process
为四大组件创建新的进程 - 在特殊情况下也可以通过JNI去native层fork一个新的进程,但是一般不会这么做
-
android:process = ":remote"
是私有进程(其他应用的组件不会和他跑在同一个进程中) -
android:process = "com.test.remote"
是全局进程(其他应用通过ShareUID和它跑在同一个进程中)
2.Share UID:
- android 系统会为每一个应用都分配一个UID(钥匙)
- 只有UID相同的才能共享数据(房间)
- 注意:要想在同一个进程中运行,除了UID要一样,签名也得一样;这种情况下的两个应用,不光能访问私有数据(比如data目录),还能共享内存数据
3.多进程的运行机制
- 系统会为每个应用分配一个虚拟机,或者说,会为每一个进程分配一个虚拟机
- 同一个应用内不同进程直接就像是复制品一样,每个进程内的东西是完全一样的,一个进程内变量或其他东西的改变,不影响其他进程,他们都有自己的内存空间
4.多进程的影响:
- 静态成员变量和单例模式无效(原因就是3中所说的,每一个进程都有自己的内存空间了)
- SharePreference无效(SharePreference不允许多个进程同时进行读写操作)
- 线程同步机制无效(因为不同进程锁的都不是同一个对象)
- 会创建多个Application(创建一个新的进程,就要创建一个新的虚拟机,就相当于打开了一个新的应用,会重新创建一个application。可以这么理解,在同一个进程中的应用,用同一个虚拟机和同一个Application)
二.IPC机制
1.Serizable接口
- 使用:
- 写一个类实现Serializable接口
- 自己写一个serialVersionUID或者是系统自动生成,自己写的话可以保证序列化和反序列化正确
- 不参与序列化的有:
- 对于静态的成员变量
- 用transient关键字标记的不参与
String fname = Environment.getExternalStorageDirectory() + "/test.txt";
//序列化 User user = new User(1,"lili",true); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(fname)); outputStream.writeObject(user); outputStream.close(); } catch (IOException e) { e.printStackTrace(); Log.e("user","存入失败:"+e.getMessage()); }
//反序列化 try { ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fname)); User user = (User) inputStream.readObject(); Log.e("user","读取结果:"+user.toString()); inputStream.close(); textEdit.setText(user.toString()); } catch (IOException e) { e.printStackTrace(); Log.e("user","读取失败:"+e.getMessage()); } catch (ClassNotFoundException e) { e.printStackTrace(); Log.e("user","读取失败222:"+e.getMessage()); }
2.Parceable接口
Parcelable方法.png- 写一个类实现Parcelable接口
- parceable方法说明.png
- 在序列化对象中有一个是实现了Parcelable的类,序列化时需要:
//序列化 @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(book,0); }
//反序列化 protected User(Parcel in) { //需要上传当前上下文的线程 book = in.readParcelable(Thread.currentThread().getContextClassLoader()); }
3.这两个接口的区别
区别 | Serializable | Parcelable |
---|---|---|
平台 | java的序列化接口 | android的序列化接口 |
原理 | 将一个对象转成可存储或可传输的状态 | 将一个对象分解成若干个支持传输的类型 |
使用场景 | 将序列化对象存储到设备上或者是用于网络传输(推荐) | 内存序列化 |
优缺点 | 用到了大量的I/O操作(ObjectOutputStream/ObjectInoutStream),使用简单,但是消耗过大 | 高效,但是使用复杂 |
4.Binder
4.1 理解
- Binder是android的一个类,实现了IBinder接口
- 从IPC角度:是Android中一种跨进程通信方式,可以理解为一种虚拟的物理设备,它的驱动是/dev/binder
- 从Framework层,是ServiceManager连接各种Manager和相应ManagerService的桥梁
- 从应用层,是连接客户端和服务端的桥梁,bindService的时候,服务端会返回一个服务端的Binder对象给客户端,客户端可以通过这个Binder可以获取服务端提供的服务和数据
4.2 创建aidl
①创建Book.java实现Parceable接口
②创建Book.aidl(先取别的名字,之后再重命名)
// Book.aidlpackage ly.com.artres.aidl;parcelable Book;
③创建IBookManager.aidl
// IBookManager.aidlpackage ly.com.artres.aidl;//手动导包import ly.com.artres.aidl.Book;// Declare any non-default types here with import statementsinterface IBookManager { //获取所有图书列表 List getBookList(); //添加图书:AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out、inout,in表示输入型参数, //out输出型参数,inout表示输入输出型参数,AIDL中只支持方法,不支持声明静态常量,是不同于一般接口的 void addBook(in Book book);}
④Build-->Make project之后在
build/generate/source/aidl中可以看到编译后的IBookManager .java
4.3 手写aidl
所有通过Binder传输的接口都要继承IInterface接口
binder机制.png
/** * 手写aidl实现 * 所有通过Binder传输的接口都要继承IInterface接口 */public interface BookManager extends IInterface{ //1.1创建文件描述类 static final String DESCRIPTION = "ly.com.artres.aidl.BookManager"; //1.2创建两个方法的标识 static final int TRANSACT_GETBOOKLIST = IBinder.FIRST_CALL_TRANSACTION + 0; static final int TRANSACT_ADDBOOK = IBinder.FIRST_CALL_TRANSACTION + 1; //1.3创建客户端要调用的几个方法 List getBookList() throws RemoteException; void addBook(Book book) throws RemoteException; //1.4创建内部类Stub,实现外部接口,实际上是一个Binder public class BookManageImpl extends Binder implements BookManager{ //2.1要有一个构造函数和外部接口进行绑定 public BookManageImpl(){ this.attachInterface(this,DESCRIPTION); } //2.2 将服务端的Binder转化为客户端需要的接口(BookManager) public static BookManager asInterface(Binder binder){ if (binder == null){ //客户端与服务端连接失败 return null; } //根据描述,取出当前进程的接口 IInterface iin = binder.queryLocalInterface(DESCRIPTION); if (iin != null && iin instanceof BookManager){ //相同进程,取出的接口和客户端的接口相同,则返回服务端binder return (BookManager) binder; } //不同进程 return new BookManageImpl.Proxy(binder); } //2.3 返回当前的binder对象 @Override public IBinder asBinder() { return this; } /** * 2.4 客户端发起RPC(远程过程调用)请求--->运行在服务端的Binder线程池中,所以所有的Binder请求都要是同步的 * @param code : 请求的方法标识 * @param data : 请求参数 * @param reply : 请求的返回结果 * @param flags : 参数flags只有0和FLAG_ONEWAY两种,默认的跨进程操作是同步的,所以transact()方法的执行会阻塞, * 调用以同步的形式传递到远程的transact(),等待远端的transact()返回后继续执行——最好理解的方式就是把 * 两端的transact()看作一个方法,Binder机制的目标也就是这样。指定FLAG_ONEWAY时,表示Client的transact() * 是单向调用,执行后立即返回,无需等待Server端transact()返回 * @return 返回false,则客户端请求失败 * @throws RemoteException */ @Override public boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { //判断请求的哪个方法 switch (code){ case TRANSACT_GETBOOKLIST: //获取图书列表 //有请求参数,读取请求参数(没有) data.enforceInterface(DESCRIPTION); //调用请求方法 List list = this.getBookList(); //请求完成后,需要返回值,则写到reply中 reply.writeNoException(); reply.writeTypedList(list); return true; case TRANSACT_ADDBOOK: //添加图书 //有请求参数,读取请求参数(有) data.enforceInterface(DESCRIPTION); Book _arg0; if (0 != data.readInt()){ //将之前我们所存入的数据按照顺序进行获取,反序列化 _arg0 = Book.CREATOR.createFromParcel(data); }else { _arg0 = null; } //调用方法 this.addBook(_arg0); //请求完成后,需要返回值,则写到reply中(没有返回值) reply.writeNoException(); return true; case INTERFACE_TRANSACTION: reply.writeString(DESCRIPTION); return true; default: break; } return super.onTransact(code, data, reply, flags); } //2.4创建代理类 private static class Proxy implements BookManager{ IBinder mRemote; /** * 服务端的binder * @param binder */ Proxy(IBinder binder){ this.mRemote = binder; } /** * 运行在客户端 * @return * @throws RemoteException */ @Override public List getBookList() throws RemoteException { Parcel _data = Parcel.obtain();//创建输入型对象 Parcel _reply = Parcel.obtain();//创建输出型对象 List _result = null;//创建结果对象 try { //写入请求参数(为空) _data.writeInterfaceToken(DESCRIPTION); //发起RPC请求 mRemote.transact(TRANSACT_GETBOOKLIST,_data,_reply,0); //返回结果(list) _reply.readException(); _result = _reply.createTypedArrayList(Book.CREATOR); }catch (Exception e){ e.getMessage(); }finally { //回收 _reply.recycle(); _data.recycle(); } return _result; } /** * 运行在客户端 * @param book * @throws RemoteException */ @Override public void addBook(Book book) throws RemoteException { Parcel _data = Parcel.obtain();//创建输入型对象 Parcel _reply = Parcel.obtain();//创建输出型对象 try { //写入请求参数 _data.writeInterfaceToken(DESCRIPTION); if (book != null){ //序列化 _data.writeInt(1); book.writeToParcel(_data,0); }else { _data.writeInt(0); } //发起RPC请求 mRemote.transact(TRANSACT_ADDBOOK,_data,_reply,0); //返回结果(为空) _reply.readException(); }catch (Exception e){ e.getMessage(); }finally { //回收 _reply.recycle(); _data.recycle(); } } @Override public IBinder asBinder() { return mRemote; } public String getInterfaceDescription(){ return DESCRIPTION; } } @Override public List getBookList() throws RemoteException { return null; } @Override public void addBook(Book book) throws RemoteException { } }}
4.4 两个重要方法
- unlinkToDeath()
private IBinder.DeathRecipient mRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if (mManage == null){ //断开之前的连接 mManage.asBinder().unlinkToDeath(mRecipient,0); mManage = null; } } };
- linkToDeath():
binder.linkToDeath(mRecipient,0);
三、Android中的IPC方式:
- Bundle
- ContentProvider
- AIDL
- Messenger
- 文件共享
- Socket
3.1 Bundle
- Bundle实现了Parcelable接口,可以通过Intent传递
- Bundle可以放基本数据类型,实现了Parcelale或Serializable接口的类,还有特殊数据类型
Intent intent = new Intent(this,BActivity.class); Bundle bundle = new Bundle(); bundle.putString("key1","你在干啥"); intent.putExtra("intentkey",bundle); startActivity(intent);
- 特殊使用场景:
进程A中的计算结果要在进程B中展示,且这个计算结果不支持Bundle传输,可以通过Intent启动B的一个Service组件,然后B在后台进行计算,计算完成后在B中显示
3.2 Messenger(信使)
- 是一种轻量级的ipc方案
- 底层是AIDL
- 串行的方式处理消息,客户端发一个消息,服务端处理一个,当客户端发了大量的消息的时候,服务端也只能一个一个处理,效率低,不适合用Messenger处理。
- Messenger主要是用来传递消息,当要调用服务端的方法时就不适用了。
messenger流程图.jpg
原理分析:
①client和server绑定后
②client在onServiceConnected中通过IBinder生成一个Messenger发送Message消息
③server接收到client发来的消息后,会在Handler中进行处理
④server回复消息给client,也是通过Messenger,他发送的消息需要客户端处理,所以这个Messenger得是client的,怎么创建客户端的Messenger呢?只需要在②中,发送消息的时候,通过msg.replyTo = xx,就可以把client的messenger传过来了
- 注意:
在跨进程中不能传递自定义的Parceable对象
3.3 AIDL
-
(1)支持的数据类型:
aidl支持的数据类型.png
自定义的Parcelable对象和AIDL都要手动import (2)解注册跨进程的接口时:
@Override protected void onDestroy() { if (mRemoteManager != null && mRemoteManager.asBinder().isBinderAlive()){ //注销 Log.e("testaidl","onDestroy unregister listener:"+mNewBookListener); try { mRemoteManager.unregisterListener(mNewBookListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); }
这样写的时候会解注册失败,因为这时候的manager和service端的都不是同一个了,aidl不能传输对象,这里的manager相当于两个新对象。
要删除这个跨进程的接口,要用到RemoteCallBackListener;
使用方法:
//用于删除跨进程的接口private RemoteCallbackList mListennerist = new RemoteCallbackList<>();@Overridepublic void registerListener(IOnNewBookArrivedLisneer listener) throws RemoteException { mListennerist.register(listener); }@Overridepublic void unregisterListener(IOnNewBookArrivedLisneer listener) throws RemoteException { mListennerist.unregister(listener); }
注意:
在使用RemoteCallbackList时要注意:
//获取元素个数 int N = mListennerist.beginBroadcast(); for (int i=0;i
(3)在UI线程中调用远程端的耗时方法(互调):
- 服务端中的方法都运行在Binder线程池中,客户端调用服务端中的耗时方法时,会造成ANR,所以要在客户端开启一个子线程才能调用服务端的方法;
- 服务端的调用客户端Binder线程池中的某一个耗时方法时,也要开始一个线程才行;
(4)Binder意外死亡的时候:
- 可以在onServiceDisconnected中重新连接
- 可以在DeathRecipient 中断开之前的连接,再重新绑定
IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() { @Override public void binderDied() { } };
- 他们二者的区别:
onServiceDisconnected是运行在UI线程中,而DeathRecipient 是运行在客户端的Binder线程池中的
(5)权限验证:
声明权限
使用权限:
- 可以在onBinder()中验证权限
@Override public IBinder onBind(Intent intent) { //权限验证 int permission = checkCallingOrSelfPermission("ly.com.artres.aidl.ACCESS_BOOK_SERVICE"); if (permission == PackageManager.PERMISSION_DENIED){ return null; } return mBinder; }
可以在onTransact()中通过权限验证或者Uid,Pid进行验证
总结:创建一个service和AIDL接口,创建一个类实现AIDL接口中的Stub类并实现里面的抽象方法,在service的onBind()中返回这个类的对象,然后在客户端就可以绑定服务端Service,继而访问远程接口
3.4 ContentProvider
- 底层是Binder
- 写一个类继承ContentProvider
要实现6个方法:
①boolen onCreate():创建,做初始化工作,运行在UI线程中;
②String getType(Uri uri):根据uri获取对应的MIME(媒体)类型:文字/图片/视频,在主线程;
③Uri insert(@NonNull Uri uri, @Nullable ContentValues values):插入数据,在binder线程池中运行;
④int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs):删除数据,在binder线程池中运行;
⑤int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs):更新数据,在binder线程池中运行;
⑥Cursor query(...):查询数据,在binder线程池中运行;
实现步骤:
- 在manifest中定义这个provider
//这个可以不写
根据Uri-->得到UriCode--->再判断是哪一张表
- 将uri和uricode进行绑定
//创建Uri和UriCode private static String AUTHORITY = "ly.com.artres.provider"; private static Uri BOOK_URI = Uri.parse("content://"+AUTHORITY+"/book"); private static Uri USER_URI = Uri.parse("content://"+AUTHORITY+"/user"); public static final int BOOK_CODE = 0; private static final int USER_CODE = 1; //创建urimatch private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { uriMatcher.addURI(AUTHORITY,"book",BOOK_CODE); uriMatcher.addURI(AUTHORITY,"user",USER_CODE); } /** * 根据Uri得到Uricode,再判断是哪一张表 * @param uri * @return */ private String getTableName(Uri uri){ String tableName = null; switch (uriMatcher.match(uri)){ case BOOK_CODE: tableName = DBOpenHelper.BOOK_TABLE_NAME; break; case USER_CODE: tableName = DBOpenHelper.USER_TABLE_NAME; break; default: break; } return tableName; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { Log.e("testcp","insert():"+Thread.currentThread().getName()); String tableName = getTableName(uri); if (tableName == null){ throw new IllegalArgumentException("insert unsupport uri:"+uri); } mDb.insert(tableName,null,values); mContext.getContentResolver().notifyChange(uri,null); return null; }
SQLite数据库:
- 底部实现了线程同步,如果provider不使用sqlite数据库,要注意在CRUD中使用线程同步;
- 自定义一个类继承SQLiteOpenHelper,必须要有一个构造方法;
- SQLiteOpenHelper用于创建升级数据库;
public class DBOpenHelper extends SQLiteOpenHelper { public static final String DB_NAME = "book.db"; public static final String BOOK_TABLE_NAME = "booktb"; public static final String USER_TABLE_NAME = "usertb"; public static final int DB_VERSION = 1; private String CREATE_BOOK = "create table if not exists "+BOOK_TABLE_NAME+"(_id integer primary key,name text)"; private String CREATE_USER = "create table if not exists "+USER_TABLE_NAME+"(_id integer primary key,name text)"; /** * 必须要有一个构造函数 * @param context */ public DBOpenHelper(Context context) { super(context,DB_NAME,null,DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { //创建表 db.execSQL(CREATE_BOOK); db.execSQL(CREATE_USER); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }}
根据sqliteopenhelprt可以创建sqlitedatabase,然后在contentprovider的CRUD方法中调用sqlitedatabase的增删改查的方法;
SQLiteDatabase mDb = new DBOpenHelper(mContext).getWritableDatabase();
在Activity中往provider中插入数据时:
Uri uri = Uri.parse("content://ly.com.artres.provider/book"); ContentValues values = new ContentValues(); values.put("_id",4); values.put("name","语文"); getContentResolver().insert(uri,values);
在Activity中从provider中查询数据时:
Uri uri = Uri.parse("content://ly.com.artres.provider/book"); Cursor bookCursor = getContentResolver().query(uri,new String[]{"_id","name"},null,null,null); while (bookCursor.moveToNext()){ int id = bookCursor.getInt(0); String name = bookCursor.getString(1); Log.e("testcp","query book结果:"+id+",name:"+name+"\n"); } bookCursor.close();
3.5 文件共享
- 实现Serializable接口
- 通过ObjectOutputStream()和ObjectInputStream()实现
3.6 Socket(套接字)
1.分为两种:
- 流式套接字-->对应TCP(面向连接,提供稳定的双方通信功能,要建立三次握手,本身具有超时重连机制)
- 用户数据报套接字-->对应UDP(也能实现双方通信功能,性能会更高,但是数据不一定能传输成功,)
2.可以传输任意字节流
3.使用:
- 要有权限(不能在主线程访问网络)
- 步骤:
服务端:
①服务端创建SocketServerSocket serverSocket = new ServerSocket(8688);②接收客户端请求:final Socket client = serverSocket.accept();③接收客户端请求BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));String str = in.readLine();④向客户端发送消息PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);out.println("欢迎来到聊天室");
客户端:
①连接服务端Socket socket = new Socket("localhost",8688);②向服务端发送请求:PrintWriter mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);mPrintWriter.println(“hello”);③接收服务端的消息: BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));String msg = in.readLine();
四、Binder连接池:
五、选择合适的IPC方式:
选择ipc方式.png更多相关文章
- 【Android】17.5 利用Messenger实现进程间通信(IPC)
- Android跨进程通信——Activity
- Qt_Qtopia与Android的进程间通讯方式
- Android系统启动——SystemServer进程启动
- Android 获取当前进程
- Android跨进程通信:图文详解 Binder机制 原理(转载)
- 示例:Android使用AIDL实现跨进程通讯(IPC)
- Android Binder进程间通信-ServiceManager代理对象的获取过程