简介
IPC是 Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
进程和线程是包含与被包含的关系。
Android中的多进程模式
1. 在Android中创建多进程的方法:
① 通过JNI在native层去fork一个新的进程(不常用)
② 给四大组件添加属性 android:process = "进程名" 就可以开启多进程模式,如果没有指定这个属性,那么默认进程名是包名
2. 给进程命名:
① android:process = ": 名字" ,这是一种简写的方法,完整的进程名为 "包名+名字";是属于当前应用的私有进程,其他应用的组件不可以和它跑到同一个进程中
<service android:name=".services.BookManagerService" android:enabled="true" android:exported="true" android:process=":myaidl" />
② android:process = "全名" ,属于全局进程,其它的应用通过ShareUID方法可以和它跑到同一个进程中。
3. 一般来说,使用多进程会造成如下几方面的问题:
① 静态成员和单例模式完全失效:
Android为每个进程都会分配一个独立的虚拟机,这导致在不同的虚拟机中访问同一个类的对象会产生多个副本
② 线程同步机制完全失效
Android为每个进程都会分配一个独立的虚拟机(内存)
③ SharedPreferences的可靠性下降
底层是通过XML文件来实现的,并发读写都可能出问题
④ Application会创建多次
运行在不同进程的组件是属于不同的虚拟机和Application的
Android中的序列化机制
Serializable是java中的序列化接口,其使用起来简单但是开销很大,序列化、反序列化过程都需要大量的I/O操作
Parceable是Adnroid中的序列化接口,效率很高,就是使用起来麻烦一点
一、Serializable接口:
① 为该类指定 private static final long serialVersionUID = 465465464l;
作用:当版本升级后,可能删除或者添加了某个成员变量,这个时候我们仍然能够反序列化成功,程序能够最大限度的恢复数据,相反,如果不指定serialVersionUID,
程序则会挂掉。
② 采用ObjectOutputStream、ObjectInputStream实现序列化和反序列化操作:
File file = new File(Environment.getExternalStorageDirectory() , "test.text"); //序列化 UsersS usersS = new UsersS(20 , "xiaoming" , true); try { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(usersS); out.close(); } catch (IOException e) { e.printStackTrace(); } //反序列化 try { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); UsersS usersS1 = (UsersS) in.readObject(); in.close(); LogUtils.e("反序列化得到的名字是:" + usersS1.getName()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }
二、Parceable接口:
① 实现这个接口可以实现序列化,并可以通过Intent和Binder传递:
//返回当前对象的描述(一般返回0) @Override public int describeContents() { return 0; } //将当前对象写入序列化结构中 @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(age); dest.writeString(name); dest.writeInt(isMale ? 1 : 0); dest.writeParcelable(usersS , 0);//users另外是一个序列化的类的对象;flag一般是0 } public static final Creator<UsersP> CREATOR = new Creator<UsersP>() { //从序列化后的对象中创建原始对象 @Override public UsersP createFromParcel(Parcel in) { return new UsersP(in); } //创建指定长度的原始对象数组 @Override public UsersP[] newArray(int size) { return new UsersP[size]; } }; protected UsersP(Parcel in) { age = in.readInt(); name = in.readString(); isMale = in.readInt() == 1; in.readParcelableArray(Thread.currentThread().getContextClassLoader());//反序列化过程需要传入当前线程的上下文类加载器 }
Bundle
可以在Bundle上附加需要传输的信息并通过Intent发送出去
Intent intent = new Intent();
Bundle bundle = new Bundle();
intent.putExtra("1" , bundle);
文件共享
适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读、写的问题
例子:在两个进程中分别进行序列化和反序列化一个java类,存在问题:
一个序列化、一个反序列化,那么反序列化出来的可能不是最新的;
如果两个同时进行序列化就有可能出现更严重的问题;
Messenger(翻译为“信使”,对AIDL做了封装)
① 通过它可以在不同的进程中传递Message对象,在Message中可以放入我们需要传递的数据
② 两个构造函数表明它的底层实现是AIDL:
public Messenger(Handler handler){
mTarget = target.getIMessenger();
}
public Messenger(IBinder target){
mTarget = IMessenger.Stub.asInterface(target);
}
③ 一次只处理一个请求,在服务端不需要考虑线程同步的问题。
④ Messenger是串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,
那么Messenger就不太合适了。
⑤ Messenger的主要作用是为了传递消息,很多时候我们很可能需要跨进程调用服务端的方法,这种情形Messenger就无法做到了,aidl可以。
下面通过一个例子来加深理解:
客户端通过bindService()来启动服务,然后根据返回的IBinder对象得到Messenger对象:
private ServiceConnection conn = new ServiceConnection(){ @Override public void onServiceConnected(ComponentName name, IBinder service) { //通过返回的IBinder对象,得到一个Messenger对象 Messenger messenger = new Messenger(service); Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT); Bundle bundle = new Bundle(); bundle.putString("msg" , "this is test"); msg.setData(bundle); //将客户端的Messenger通过Message的replyTo参数传递给服务端 msg.replyTo = mMessenger; try { messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { //断开连接会触发此方法 } };
服务端接收到客户端传来的信息,并返回数据:
private final Messenger messenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case MyConstants.MSG_FROM_CLIENT: LogUtils.e("服务端:" + msg.getData().getString("msg")); //通过Message的replyTo得到客户端传递过来的Messenger对象 Messenger replyTo = msg.replyTo; Message replyMsg = Message.obtain(null, MyConstants.MSG_FROM_SERVICE); Bundle bundle = new Bundle(); bundle.putString("reply" , "this is reply"); replyMsg.setData(bundle); try { replyTo.send(replyMsg); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } @Override public IBinder onBind(Intent intent) { //绑定成功,返回Messenger对象底层的Binder return messenger.getBinder(); }
然后再在客户端通过handler来进行处理:
switch (msg.what){ case MyConstants.MSG_FROM_SERVICE: LogUtils.e("服务端返回回来的消息:" + msg.getData().getString("reply")); break;
AIDL
一、创建AIDL文件:
① 自定义的Parcelable对象和AIDL对象必须要显示import进来,不管是否位于同一个包
② aidl接口只支持方法,不支持静态常量
③ aidl中除了基本的数据类型,其他类型的参数必须标上方向:in 、out或者inout。其中:
in: 表示输入型的参数
out:表示输出型的参数
inout:表示输入输出型的参数
④ 如果aidl中用到了自定义的Parcelable对象,那么就必须新建一个和它同名的AIDL文件,并且在其中申明它为 Parcelable
⑤ build--->Rebuild project 生成对应的java文件,java文件路径 build/generated/source/aidl/debug/XX.java
二、保持客户端、服务端的aidl文件一致:
AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类。如果类的完整路径不一样,就无法成功反序列化
三、问题:
① 添加单个元素集合(list)
CopyOnWriteArrayList:支持并发的读、写(会存在多个线程同时访问的情形);相对应的ConcurrentHashMap
/** * CopyOnWriteArrayList: * 支持并发的读、写(会存在多个线程同时访问的情形);相对应的ConcurrentHashMap */ private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();② 添加注册注销接口集合(map):
RemoteCallbackList:是系统专门提供的用于删除跨进程listener的接口,其内部实现了自动同步的功能。
int N = RemoteCallbackList.beginBroadcast();
RemoteCallbackList.finishBroadcast();必须配对使用
/** * RemoteCallbackList: 是系统专门提供的用于删除跨进程listener的接口,其内部实现了自动同步的功能。 * int N = RemoteCallbackList.beginBroadcast(); * RemoteCallbackList.finishBroadcast();必须配对使用 */ private RemoteCallbackList<OnNewBookListener> mListeners = new RemoteCallbackList<>();② Binder意外死亡需要重新连接:
binderDied():默认在子线程执行
onServiceDisconnected():在默认主线程执行
四、添加aidl的访问权限:
在客户端和服务端都添加自定义的权限
<permission android:name="zidingyiquanxian"
android:protectionLevel="normal"/>
在服务端进行权限验证
@Override public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("自定义权限"); if(check == PackageManager.PERMISSION_DENIED){ //权限拒绝,直接返回null } return mBinder; }
五、配置 builder.gradle文件:
//把src/main/aidl文件也作为Java.srcDirs, resources.srcDirs;否则找不到自定义的类 sourceSets { main { manifest.srcFile 'src/main/AndroidManifest.xml' java.srcDirs = ['src/main/java', 'src/main/aidl'] resources.srcDirs = ['src/main/java', 'src/main/aidl'] aidl.srcDirs = ['src/main/aidl'] res.srcDirs = ['src/main/res'] assets.srcDirs = ['src/main/assets'] } }
ContentProvider
这里以数据库的存储方式的例子来加深理解:
创建一个contentprovider:
/** * 定义单独的uri和uri_code,并关联在一起。当外界请求时可以根据请求的uri得到对应的uri_code,从而知道访问哪一张表 */ private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); static { matcher.addURI(AUTHORITIES , "book" , BOOK_URI_CODE); matcher.addURI(AUTHORITIES , "users" , USERS_URI_CODE); }
/** * 代表contentprovider的创建,一般做初始化操作,运行在主线程,其他的方法运行在Binder线程池中 * @return */ @Override public boolean onCreate() { context = getContext(); ProviderSqliteOpenHelper providerSqliteOpenHelper = new ProviderSqliteOpenHelper(context , ProviderSqliteOpenHelper.DB_NAME , null , 1); db = providerSqliteOpenHelper.getWritableDatabase(); return true; }
/** * @param uri Uri请求的路径 * @return 请求对应的MIME类型(媒体类型),如果不关心这个选项,可以直接返回null或者"星号/星号" */ @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { String tableName = getTableName(uri); if(tableName != null){ db.insert(tableName, null, values); context.getContentResolver().notifyChange(uri , null); return uri; } return null; }
public String getTableName(Uri uri){ String table = null; switch (matcher.match(uri)){ case BOOK_URI_CODE: table = ProviderSqliteOpenHelper.BOOK_TABLE_NAME; break; case USERS_URI_CODE: table = ProviderSqliteOpenHelper.USER_TABLE_NAME; break; } return table; }
在另外一个进程中访问contentprovider:
Cursor cursor = contentResolver.query(bookUri, null, null, null, null); if (cursor.moveToFirst()){ int index = cursor.getColumnIndex("name"); String name = cursor.getString(index); LogUtils.e("查询到的数据:name = " + name); while (cursor.moveToNext()){ int index1 = cursor.getColumnIndex("name"); String name1 = cursor.getString(index); LogUtils.e("查询到的数据:name = " + name1); } } cursor.close();
Socket(“套接字”)
① 流式套接字(网络传输控制层中的TCP协议):面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性。
Socket必须在发送数据之前与目的地的Socket取得连接,一旦连接建立了,Socket就可以使用一个流接口进行打开、读写以及关闭操作。并且,所有发送的数据在另一端都会以相同的顺序被接收。
② 用户数据报套接字(UDP协议):是无连接的,提供不稳定的单向通信功能(目的地址和要发送的内容),当然UDP也能实现双向通信功能。
1、Socket的构造方法
Java在包java.NET中提供了两个类Socket和ServerSocket,分别用来表示双向连接的Socket客户端和服务器端。
Socket的构造方法如下:
(1)Socket(InetAddress address, int port);
(2)Socket(InetAddress address, int port, boolean stream);
(3)Socket(String host, int port);
(4)Socket(String host, int port, boolean stream);
(5)Socket(SocketImpl impl);
(6)Socket(String host, int port, InetAddress localAddr, int localPort);
(7)Socket(InetAddress address, int port, InetAddrss localAddr, int localPort);
ServerSocket的构造方法如下:
(1)ServerSocket(int port);
(2)ServerSocket(int port, int backlog);
(3)ServerSocket(int port, int backlog, InetAddress bindAddr);
其中,参数address、host和port分别是双向连接中另一方的IP地址、主机名和端口号;参数stream表示Socket是流Socket还是数据报Socket;参数localAddr和localPort表示本地主机的IP地址和端口号;SocketImpl是Socket的父类,
既可以用来创建ServerSocket,也可以用来创建Socket。
2、输入流和输出流
Socket提供了方法getInputStream()和getOutPutStream()来获得对应的输入流和输出流,以便对Socket进行读写操作,这两个方法的返回值分别是InputStream和OutPutStream对象。
为了便于读写数据,我们可以在返回的输入输出流对象上建立过滤流,如PrintStream、InputStreamReader和OutputStreamWriter等。
3、关闭Socket
可以通过调用Socket的close()方法来关闭Socket。在关闭Socket之前,应该先关闭与Socket有关的所有输入输出流,然后再关闭Socket。
例子:
客户端与服务端建立连接:
public void connectTcp(){ while (mSocket == null){ //和tcp服务建立连接 try { mSocket = new Socket("localhost" , 8688); printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())) , true); isConnected = true; } catch (IOException e) { e.printStackTrace(); SystemClock.sleep(1000); isConnected = false; } if(isConnected) { //接收服务端的消息 try { br = new BufferedReader(new InputStreamReader(mSocket.getInputStream())); while (!SocketTcpActivity.this.isFinishing()){ String msg = br.readLine(); if(msg != null){ Message message = Message.obtain(); Bundle bundle = new Bundle(); bundle.putString("serverMsg" , msg); message.setData(bundle); message.what = MyConstants.ACCPT_SERVER_MSG; mHandler.sendMessage(message); } } } catch (IOException e) { e.printStackTrace(); } } } br.close(); printWriter.close(); mSocket.close();
再次发送消息:
printWriter.println(msg);
在服务端开一个线程监听客户端的连接请求:
public void run() { try { serverSocket = new ServerSocket(8688); } catch (IOException e) { e.printStackTrace(); return; } while (!isServiceDied){ try { //指定端口实例化一个ServerSocket,并调用ServerSocket的accept()方法在等待客户端连接期间造成阻塞。 final Socket socket = serverSocket.accept(); new Thread(new Runnable() { @Override public void run() { responseClient(socket);
public void responseClient(Socket socket){ try { //接收客户端的消息 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); //用于向客户端发送消息 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); while (!isServiceDied){ String str = in.readLine(); if(str == null){ //客户端传来的数据为空(可以做一些断开连接、不回复消息等操作) break; } int i = new Random().nextInt(strMsg.length); String msg = strMsg[i]; out.println(packMessage(msg)); } out.close(); in.close(); socket.close();
选择合适的IPC机制
完整demo路径
http://download.csdn.net/detail/fanghana/9762302