Android IPC 机制

老话长谈,趁现在有时间对IPC做一个具体的总结。
IPC是Inter-Process Communication的缩写,含义就是进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。那么什么是进程,什么是线程,进程和线程是两个截然不同的概念。在操作系统中,线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程指的一个执行单元,在PC和移动设备上指的是一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含被包含的关系,最简单情况下,一个进程可以只有一个线程,即主线程,在Android里面也叫UI线程,在UI线程里才能操作界面元素。
那么在Android中,有特色的进程间通信方式就是Binder了,通过Binder可以轻松实现进程间通信。除了Binder,Android还支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然一个设备上的两个进程之间通过Socket通信自然也是可以的。

说到IPC的使用场景就必须提到多进程,只有面对多进程这种场景下,才需要考虑进程间通信。所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程所带来的主要影响。正常情况下,四大组件中间不可能不通过一些中间层来共享数据,那么通过简单地指定进程名来开启多进程都会无法正确运行。一般来说,使用多进程会造成如下几方面的问题:

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效
  • SharedPreferences的可靠性下降
  • Application会多次创建

为了解决这个问题,系统提供了很多跨进程通信方法,虽然说不能直接地共享内存,但是通过跨进程通信我们还是可以实现数据交互。实现跨进程通信的方式有很多,比如通过Intent来传递数据,共享文件SharedPreference,基于Binder的Messenger和AIDL以及Socket等。

IPC基础概念介绍

Serializable接口

Serializable是Java提供的一个序列化接口,它是一个空接口,为对象标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,一句话即可。

public class User implements Serializable {private static final long seriaVersionUID = 519067123721295773L
}

Parcelable接口

Parcel内部包装了可序列化的数据,可以在Binder中自由传输,在序列化过程中需要实现的功能有序列化、反序列化和内容描述序列化功能有writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的。用法如下:

public class MyParcelable implements Parcelable {// You can include parcel data typesprivate int mData;private String mName;// We can also include child Parcelable objects. Assume MySubParcel is such a Parcelable:private MySubParcelable mInfo;// This is where you write the values you want to save to the `Parcel`.  // The `Parcel` class has methods defined to help you save all of your values.  // Note that there are only methods defined for simple values, lists, and other Parcelable objects.  // You may need to make several classes Parcelable to send the data you want.@Overridepublic void writeToParcel(Parcel out, int flags) {out.writeInt(mData);out.writeString(mName);out.writeParcelable(mInfo, flags);}// Using the `in` variable, we can retrieve the values that // we originally wrote into the `Parcel`.  This constructor is usually // private so that only the `CREATOR` field can access.private MyParcelable(Parcel in) {mData = in.readInt();mName = in.readString();mInfo = in.readParcelable(MySubParcelable.class.getClassLoader());}public MyParcelable() {// Normal actions performed by class, since this is still a normal object!}// In the vast majority of cases you can simply return 0 for this.  // There are cases where you need to use the constant `CONTENTS_FILE_DESCRIPTOR`// But this is out of scope of this tutorial@Overridepublic int describeContents() {return 0;}// After implementing the `Parcelable` interface, we need to create the // `Parcelable.Creator<MyParcelable> CREATOR` constant for our class; // Notice how it has our class specified as its type.  public static final Parcelable.Creator<MyParcelable> CREATOR= new Parcelable.Creator<MyParcelable>() {// This simply calls our new constructor (typically private) and // passes along the unmarshalled `Parcel`, and then returns the new object!@Overridepublic MyParcelable createFromParcel(Parcel in) {return new MyParcelable(in);}// We just need to copy this and change the type to match our class.@Overridepublic MyParcelable[] newArray(int size) {return new MyParcelable[size];}};
}

Serializable和Parcelable区别

Serializable是Java中的序列化接口,其使用起来简单但是开销很大,在序列化和反序列化过程中需要大量的I/O操作。而Parcelable是Android中的序列化方式,因此更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高。

Serializable和Parcelable区别

Serializable是Java中的序列化接口,其使用起来简单但是开销很大,在序列化和反序列化过程中需要大量的I/O操作。而Parcelable是Android中的序列化方式,因此更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高。

Android中的IPC方式

1.使用Bundle   ----> 用于android四大组件间的进程间通信
我们知道,四大组件中三大组件(activity、service、receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。

2.使用文件共享  ---->用于单线程读写

共享文件也是一种不错的进程间通信方式,两个进程间通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。

这种方式在单线程读写的时候比较好用 如果有多个线程并发读写的话需要限制线程的同步读写  
另外 SharePreference是个特例  它底层基于xml实现  但是系统对它的读写会基于缓存,也就是说再多进程模式下就变得不那么可靠了,有很大几率丢失数据。

3.使用Messenger   ---->用于可存放在message中的数据的传递

Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,实现Messenger有以下两个步骤,分为服务端进程和客户端进程。

使用这个方式可以在不同进程间传递message对象  这是一种轻量级的IPC方案  当传递的对象可以放入message中时  可以考虑用这种方式  但是msg.object最好不要放
因为不一定可以序列化  
使用它的步骤如下:
假设这样一个需求  需要在客户端A发送消息给服务端B接受  然后服务端B再回复给客户端A

1. 首先是客户端A发送消息给服务端B  所以在客户端A中 声明一个Handler用来接受消息  并创建一个Messenger对象 用Handler作为参数构造  然后onBinder方法返回messenger.getBinder() 即可

public class MyServiceA extends Service {  private class MessageHandler extends Handler{  //创建的接受消息的handler  @Override  public void handleMessage(Message msg) {  switch (msg.what){  case 1:  Bundle bundle = msg.getData();  String str = bundle.getString("aaa");  System.out.println("----"+str);  Messenger replyTo = msg.replyTo; //此处往下是用来回复消息给客户端A的     Message replyMsg = Message.obtain(null,2);  Bundle bundle1 = new Bundle();  bundle1.putString("bbb","remote222给主进程回复消息啦");  replyMsg.setData(bundle1);  try {  replyTo.send(replyMsg);  } catch (RemoteException e) {  e.printStackTrace();  }  break;  }  super.handleMessage(msg);  }  }  Messenger messenger = new Messenger(new MessageHandler());  public MyServiceA() {  }  public IBinder onBind(Intent intent) {  return messenger.getBinder();  }  }  

2.在客户端A自然是需要发送消息给服务端B的  所以需要在服务绑定完成之后  获取到binder对象  之后用该对象构造一个Messenger对象  然后用messenger发送
消息给服务端即可  代码如下  :

public void onServiceConnected(ComponentName name, IBinder service) {  Messenger messenger = new Messenger(service);  Message msg = Message.obtain(null,1);  Bundle bundle = new Bundle();  bundle.putString("aaa", "主进程给remote22进程发消息啦");  msg.setData(bundle);  msg.replyTo = mmessenger; //这行代码用于客户端A接收服务端请求 设置的消息接收者   try {  messenger.send(msg);  } catch (RemoteException e) {  e.printStackTrace();  }  }  

3.由于在服务端接收到了客户端的消息还需要回复  所以在服务端代码中获取 msg中的replyTo对象  用这个对象发送消息给 客户端即可 
 在客户端需要创建一个handler和Messenger  将发送的msg.replyTo设置成Messenger对象  就可 

4.AIDL android 接口定义语言  ---->主要用于调用远程服务的方法的情况 还可以注册接口 
使用方法很简单  
在服务端定义aidl文件 自动生成java文件  然后在service中实现这个aidl  在onbind中返回这个对象  
在客户端把服务端的aidl文件完全复制过来  包名必须完全一致   在onServiceConnected方法 中 把  Ibinder对象 用asInterface方法转化成 aidl对象
然后调用方法即可

需要注意的地方: 
在aidl文件中并不是支持所有类型 
仅支持如下6种类型:
基本数据类型---- int long  char  boolean double 
String  charSequence
List  只支持ArrayList  CopyOnWriteArrayList也可以。。  里面元素也必须被aidl支持
Map   只支持HashMap   ConCurrentHashMap也可以  里面元素也必须支持aidl
Parcelable  所有实现了此接口的对象 
AIDL  所有的AIDL接口   因此 如果需要使用接口 必须使用AIDL接口

其中自定义的类型和AIDL对象必须显示import进来 不管是不是在一个包中  
如果AIDL文件中用到了自定义的Parcelable对象  必须创建同名的AIDL文件 并声明为Parcelable类型
AIDL文件中除了基本数据类型外 其他类型必须标上方向  in  out  inout  
AIDL接口中只支持方法  不支持声明静态常量
在使用aidl时  最好把所有aidl文件都放在一个包中  这样方便复制到客户端 
其实所有的跨进程对象传递都是对象的序列化与反序列化  所以必须包名一致

现在加入有这样一个需求 如果服务端是 图书馆添加和查看书的任务   客户端可以查看和添加书   这时候需要添加一个功能  当服务端每添加了一本书 
需要通知客户端注册用户  有一本新书上架了   这个功能如何实现?
想想可知  这是一个观察者模式  如果在同一进程中很容易实现,只需要在服务端中的代码中维护一个集合 里面放的是注册监听的用户  然后用户需要实现一个新书到来的回调接口
当有新书上架时 遍历这个集合  调用每个注册者的接口方法  即可实现  
现在我们是跨进程通信   所以自然不能如此简单了  但也不是很复杂 想一想  其实就是把以往的接口定义 变成了aidl接口定义  然后其他的一样即可  
但是这样还是存在一个问题  如果注册了listener  我们又想解除注册  是不是在客户端传入listener对象 在服务端把它移除就可以呢? 
其实是不可以的   因为这是跨进程的  所以对象并不是真正的传递  只是在另一个进程中重新创建了一个一样的对象  内存地址不同 所以根本不是同一个对象
所以是不可以的   如果要解决这个问题  需要使用RemoteCallbackList 类  不要使用CopyWriteArrayList  
在RemoteCallBackList中封装了一个Map 专门用来保存所有的AIDL回调  key为IBinder  value是CallBack   使用IBinder 来区别不同的对象  ,
因为跨进程传输时会产生很多个不同的对象  但这些对象的底层的Binder都是同一个对象  所以可以  
在使用RemoteCallBackList时 add 变为 register  remove 变为 unregister  遍历的时候需要先 beginBroadcast  这个方法同时也获取集合大小 
获取集合中对象使用 getBoardCastItem(i)  最后不要忘记finishBoardCast方法

还有一个情况  由于onServiceConnected方法 是在主线程执行的  如果在这里执行服务端的耗时代码  会ANR  所以需要开启一个子线程执行  
同理在服务端中 也不可以运行客户端的耗时程序  
总结起来就是 在执行其他进程的耗时程序时  都需要开启另外的线程防止阻塞UI线程  如果要访问UI相关的东西  使用handler

为了程序的健壮性  有时候Binder可能意外死亡  这时候需要重连服务  有2种方法:
1.在onServiceDisconnected方法中  重连服务  
2. 给Binder注册DeathRecipient监听  当binder死亡时 我们可以收到回调  这时候我们可以重连远程服务

最后有时候我们不想所有的程序都可以访问我们的远程服务  所以可以给服务设置权限和过滤:
1.我们在onbind中进行校验 用某种方式 如果验证不通过那么就直接返回null 
2.我们可以在服务端的AndroidMiniFest.xml中  设置所需的权限  <permission android:name="aaaaaa" android:protectionLevel="normal"/>
然后在onbind中 检查是否有这个权限了  如果没有那么直接返回null即可  判断方法如下  :

int check = checkCallingOrSelfPermission("aaa");  if(check== PackageManager.PERMISSION_DENIED){  return null;  }  

3.可以在onTransact方法中 进行权限验证  如果验证失败直接返回false  可以采用permission方法验证  还可以用Uid和Pid验证  很多方法

其中声明权限与 添加权限的方式 是   在Service所在的AndroidMinifest中 声明权限 
比如    <permission android:name="com.yangsheng.ydzd_lb.myaidlpro.book" android:protectionLevel="normal"></permission>
然后在 需要远程调用的 app中添加 这个权限 <uses-permission android:name="com.yangsheng.ydzd_lb.myaidlpro.book"/>  
这样 就可以在  onbind中验证权限了

至此 AIDL  大体介绍完了   以后需要在使用中提升了

aidl demo 下载  :  http://download.csdn.NET/detail/u012760183/9520173

5.ContentProvider方式  实现对另一个应用进程开放provider数据的查询

此方法使用起来也比较简单  底层是对Binder的封装 使之可以实现进程间通信  使用方法如下  
1. 在需要共享数据的应用进程中建立一个ContentProvider类 重写它的CRUD 和getType方法  在这几个方法中调用对本应用进程数据的调用 
  然后在AndroidMinifest.xml文件中声明provider

<provider   android:authorities="com.yangsheng.book"  //这个是用来标识provider的唯一标识  路径uri也是这个  android:name=".BookProdiver"  android:process=":remote_provider"/>   //此句为了创建多进程  正常不需要使用  

2. 在需要获取共享数据的应用进程中调用getContentResolver().crud方法  即可实现数据的查询

需要注意的问题:
1.关于 sqlite crud的各个参数的意义 
query函数 参数 
Cursor query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
第一个参数 distinct 英语单词意思 独特的   如果true 那么返回的数据都是唯一的  意思就是实现查询数据的去重 
第二个参数 table  表名
第三个参数 columns  要查询的行的名字数组  例如  new String[]{"id","name","sex"} 
第四个参数 selection 选择语句  sql语句中where后面的语句  值用?代替  例如  "id=? and sex=?"
第五个参数 selectionArgs  对应第四个参数的 ?  例如  new String[]{"1","男"}
第六个参数 groupBy 用于分组  
第七个参数 having  筛选分组后的数据 
第八个参数 orderby 用于排序  desc/asc  升序和降序  例如  id desc / id asc 
最后一个参数 limit  用于限制查询的数据的个数  默认不限制
其他几个函数 根据query函数的参数猜想即可

2.由于每次ipc操作 都是靠uri来区别 想要获取的数据位置  所以provider在调取数据的时候根据uri并不知道要查询的数据是在哪个位置
 所以我们可以通过 UriMatcher 这个类来给每个uri标上号 根据编号 对应适当的位置   例如:

public static final int BOOK_CODE = 0;  public static final int USER_CODE = 1;  public static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);  static {  matcher.addURI("book uri", "book", BOOK_CODE);  matcher.addURI("user uri", "user", USER_CODE);  }  这样我们可以通过 下面这个样子来获取位置(此处是表名 其他类型也一样)  private String getTableName(Uri uri) {  switch (matcher.match(uri)) {  case BOOK_CODE:  return "bookTable";  case USER_CODE:  return "userTable";  }  return "";  }  

3.另外ContentProvider除了crud四个方法外,还支持自定义调用  通过ContentProvider 和ContentResolver的 call方法  来实现

ContentProviderdemo下载 : http://download.csdn.Net/detail/u012760183/9520175

6.Socket方法实现Ipc   这种方式也可以实现 但是不常用  

需要权限  
<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
这种方式需要一个服务端socket 和一个客户端socket  建立连接后 通过流循环获取消息即可  
1.在服务端开启一个serverSocket 不断获取客户端连接  注意要在子线程中开启

ServerSocket serverSocket = new ServerSocket(8688);  while(isActive) { //表示服务生存着  try {  final Socket client = serverSocket.accept();  //不断获取客户端连接  System.out.println("---服务端已获取客户端连接");  new Thread(){  @Override  public void run() {  try {  dealWithMessageFromClient(client);  //处理客户端的消息 就是开启一个线程循环获取out 和 in  流 进行通信  } catch (IOException e) {  e.printStackTrace();  }  }  }.start();  } catch (IOException e) {  e.printStackTrace();  }  }  

2.在客户端开启一个线程 使用ip和端口号连接服务端socket  连接成功后 一样 开启子线程 循环获取消息 处理

Socket socket = null;  while(socket==null){  //失败重连  try {  socket = new Socket("localhost",8688);  out = new PrintWriter(socket.getOutputStream(),true);  handler.sendEmptyMessage(1);  final Socket finalSocket = socket;  new Thread(){  @Override  public void run() {  try {  reader = new BufferedReader(new InputStreamReader(finalSocket.getInputStream()));  } catch (IOException e) {  e.printStackTrace();  }  while(!MainActivity.this.isFinishing()){  //循环获取消息  这里必须用 循环 否则 只能获取一条消息 服务端也一样  try {  String msg = reader.readLine();  System.out.println("---"+msg);  if (msg!=null){  handler.sendMessage(handler.obtainMessage(2,msg));  }  } catch (IOException e) {  e.printStackTrace();  }  }  }  }.start();  } catch (IOException e) {  SystemClock.sleep(1000);  e.printStackTrace();  }  }  

7.Binder 连接池的使用  很好用

我们在android中进程间通信 一般都使用 AIDL实现 因为它强大  但是普通的使用方法每次使用AIDL 都需要开启一个服务  如果有多个AIDL请求 那岂不是要开启很多个服务 
这明显是不可以的  比如你让你用户的手机 发现你这一个应用程序绑定了10个服务  那是极差的  所以 我们在多个AIDL 请求的时候可以使用Binder连接池技术 
只开启一个服务  根据需要获取的AIDL不同 转化成需要的AIDL 接口 执行不同的方法  
实现的基本原理  就是在onbind中返回一个BinderPool 接口 这个接口有个方法 可以根据不同的标志位返回不同的aidl接口  这样我们在asInTerface之后调用哪个方法
传入标志位即可返回需要的aidl接口 
说起来简单 让我们来实现一个试试吧
1.假设原来有2个AIDL接口需要实现(可以扩展成多个)  在服务端建立好AIDL文件  并且建立一个IBinderPool aidl接口  只有一个查询binder的方法 用于查询需要的binder

interface IBinderPool {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
IBinder queryBinder(int code);  //此方法返回Ibinder  用于转化成需要的AIDL接口
}

2.在服务端 onbind方法中返回 IBinderPool的实现类  实现query方法 按照传入的code 返回需要的ibinder

@Override
public IBinder onBind(Intent intent) {
return iBinderPool;
}private Binder iBinderPool = new IBinderPool.Stub() {@Override
public IBinder queryBinder(int code) throws RemoteException {
switch (code) {
case 1:
return new IBookManger.Stub() {@Override
public void getBook() throws RemoteException {
System.out.println("--->book");
}
};
case 2:
return new IPersonManager.Stub() {@Override
public void getPerson() throws RemoteException {
System.out.println("---->person");
}
};}
return null;
}
};

3.客户端实现一个BinderPool类  这个类主要是封装了 AIDL的一些实现方法 方便调用罢了 其中 涉及到一个可以实现同步机制的类
CountDownLatch  这个类 当他的值 不是0的时候  执行了await方法后会使方法一直停在await处  不进行 直到他的值变成了0 才可以继续执行  
也就是说 当执行了await方法  这个线程就会阻塞 等待这个数值变到0后继续执行  
而在BinderPool中的应用场景是这样的

private void connectService(){
countDownLatch = new CountDownLatch(1);  //实现同步机制
Intent intent = new Intent();
intent.setClass(ctx,MyService.class);
ctx.bindService(intent,connection,Context.BIND_AUTO_CREATE);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

首先为什么在这里要使用同步机制  我们要搞清楚 让我们看这个方法调用的时机 :

binderPool = BinderPool.getInstance(MainActivity.this);  //connectService方法 是在这个方法中调用的  IBinder iBinder = binderPool.queryBinder(2);iPersonManager = IPersonManager.Stub.asInterface(iBinder);try {iPersonManager.getPerson();} catch (RemoteException e) {e.printStackTrace();}

因为我们最终的目的是在bind服务  连接到远程服务之后获取到 binderPool对象调用它的 binderPool.queryBinder(2) 方法  如果不加同步机制
异步执行  就有可能在 connectService方法 执行完之后  执行IBinder iBinder = binderPool.queryBinder(2);这行代码的时候binderPool对象还
没有被赋值  这样就会产生问题  所以我们让 connectService方法 阻塞  当BinderPool中的 binderPool对象赋值之后 让CountDownLatch的值countDown到0

这样 connectService方法就会继续执行 然后执行下一行代码了

BinderPool demo下载 : http://download.csdn.net/detail/u012760183/9520188

最后 总结了这么多IPC通信方式  那我们该如何选择合适的IPC方式呢  针对这几种IPC通信方式分析一下优缺点
1.bundle :
简单易用  但是只能传输Bundle支持的对象 常用于四大组件间进程间通信 
2.文件共享:
简单易用  但不适合在高并发的情况下 并且读取文件需要时间 不能即时通信   常用于并发程度不高 并且实时性要求不高的情况
3.AIDL :
功能强大 支持一对多并发通信 支持即时通信   但是使用起来比其他的复杂 需要处理好多线程的同步问题  常用于一对多通信 且有RPC 需求的场合(服务端和客户端通信)
4.Messenger :
功能一般 支持一对多串行通信 支持实时通信  但是不能很好处理高并发情况 只能传输Bundle支持的类型  常用于低并发的无RPC需求一对多的场合 
5.ContentProvider :
在数据源访问方面功能强大 支持一对多并发操作 可扩展call方法  可以理解为约束版的AIDL  提供CRUD操作和自定义函数  常用于一对多的数据共享场合
6.Socket :
功能强大 可以通过网络传输字节流 支持一对多并发操作  但是实现起来比较麻烦 不支持直接的RPC   常用于网络数据交换

总结起来  
当仅仅是跨进程的四大组件间的传递数据时 使用Bundle就可以  简单方便  
当要共享一个应用程序的内部数据的时候  使用ContentProvider实现比较方便  
当并发程度不高  也就是偶尔访问一次那种 进程间通信 用Messenger就可以  
当设计网络数据的共享时  使用socket 
当需求比较复杂  高并发 并且还要求实时通信 而且有RPC需求时  就得使用AIDL了 
文件共享的方法用于一些缓存共享 之类的功能

Android IPC 进程进程间通信或跨进程通信相关推荐

  1. Android 模拟游戏手柄按键(跨进程 KeyEvent 事件)实践方案

    Android 模拟游戏手柄按键(跨进程 KeyEvent 事件)实践方案

  2. 进程线程007 进程挂靠与跨进程读写内存

    文章目录 进程挂靠 进程与线程的关系 线程与进程如何关联 为什么需要ApcState.Process CR3的值可以随便改吗 分析NtReadVirtualMemory函数 总结 跨进程读写内存 跨进 ...

  3. android四个组件的跨进程通信

    Android四大组件(Activity,service,broadcast,Content Provider)跨进程通信相信在android项目中进程用到,此处将一一做以说明以及总结. 1.简括: ...

  4. Android中使用ContentProvider进行跨进程方法调用

    原文同一时候发表在我的博客 点我进入还能看到很多其它 需求背景 近期接到这样一个需求,须要和别的 App 进行联动交互,比方下载器 App 和桌面 App 进行联动.桌面的 App 能直接显示下载器 ...

  5. Android中Sharedpreferences牵涉到跨进程时不能实时读取的问题

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/11271053 在做课程表应用时,由于要设置课前提醒的时间,我通过Sharedprefere ...

  6. 使用Android RemoteCallbackList简化远端接口跨进程回调

    RemoteCallbackList 负责维护远程接口列表的繁琐工作,通常用于执行从Service到其客户端的回调 跟踪一组已注册的IInterface回调,注意通过唯一的IBinder来识别它们(通 ...

  7. Android的跨进程通信

    Android系统的跨进程简介 为什么不能直接跨进程通信? 为了安全考虑,应用之间的内存是无法互相访问的,各自的数据都存在于自身的内存区域内. 如何跨进程通信? 要想跨进程通信,就要找到一个大家都能访 ...

  8. 【朝花夕拾】Android跨进程通信总结篇

    前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...

  9. 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10256379.html],谢谢! 只要是面试高级工程师岗位,Android跨进程通信就是最受面 ...

最新文章

  1. mobx使用数组提示越界_Mobx-State-Tree-分配给数组类型
  2. ORACLE 数据泵导入导出数据
  3. python在财务中的应用实训报告-DATATOM | 大数据实训
  4. ITK:使用写访问权访问图像中的迭代区域
  5. java执行python路径_java调用其它语言脚本(python、js)
  6. python设置图片透明度_学习python第40天
  7. cuid卡写入后锁死_荣耀手机NFC升级了:公交、门禁、支付、饭卡、加密卡还能写卡...
  8. 殷墟 太行山 红旗渠
  9. mysql 可视化监控_基于Prometheus构建MySQL可视化监控平台
  10. python中添加路径_python中添加模块导入路径的方法
  11. 从总数中生成一定数量的随机数
  12. 奇人有奇书(李渔、张岱、陈继儒、吴敬梓)
  13. Android 意图和意图过滤器(二)
  14. Effective系列经典著作,铺就程序员殿堂之路
  15. CPLEX求解器入门案例
  16. 交互式电子杂志_快速的创建交互式演示和翻转电子书工具-XFlip Enterprise(电子杂志相册制作器) V2.0.5.0 中文版 - 未来软件园...
  17. 几款引擎比较 BigWorld Unreal CryEngine等
  18. 【洞察】152号令,重量级行业信息技术法规
  19. 移动Web UI库(H5框架)有哪些,看这里就够了
  20. 2018 总结,2019 计划

热门文章

  1. xc7k325tffg900芯片手册_深圳XC7K325T-2FFG900I513所指定合供方(雅创芯城)
  2. 滤波时选用电感,电容值的方法
  3. 【软件工程】小学四则运算 “软件”之初版
  4. Jumony(外一)HTML和数据,同时发布第一个CTP源代码。
  5. N沟道增强型场效应晶体管NCE75H11
  6. 2022年认证杯SPSSPRO杯数学建模B题(第二阶段)唐宋诗的定量分析与比较研究求解全过程文档及程序
  7. 倍频器 CD4046 加74161
  8. JS日期简介(二)常用日期函数
  9. 【ROS-Navigation】Costmap2d代价地图源码分析——ObstacleLayer障碍物层
  10. 下列有关python语言的说法正确的是-python期末考试试题汇总