详解 CmProcess 跨进程通信的实现
CmProcess 是 Android 一个跨进程通信框架,整体代码比较简单,总共 20 多个类,能够很好的便于我们去了解跨进程实现的原理。
个人猜测 CmProcess 也是借鉴了 VirtualApp(该 APP 很强大,是一个沙盒,可以在里面安装其他 apk) 的源码,从中整理出来一套通信方案。VirtualAPP 功能很强大,实现比较复杂,需要较深的 framework 方面的知识才好理解。
按作者所说,CmProcess 是更方便更简洁的 Android 进程通信方案,无需进行 bindService() 操作,不用定义 Service,也不需要定义 aidl。 支持 IPC 级的 Callback,并且支持跨进程的事件总线。可同步获取服务。采用面向接口方式进行服务注册与调用,服务调用方和使用者完全解耦。
基础知识准备:
多进程:
Android多进程概念:一般一个 app 只有一个进程,所有的 components 都运行在同一个进程中,进程名称就是 app 包名。但是每一个进程都有内存的限制,如果一个进程的内存超过了这个限制的时候就会报 OOM 错误。为了解决内存限制的问题,Android 引入了多进程的概念,将占用内存的操作放在一个单独的进程中分担主进程的压力。
多进程的好处:
分担主进程的内存压力。
常驻后台任务。
守护进程,主进程和守护进程相互监视,有一方被杀就重新启动它。
多么块,对有风险的模块放在单独进程,崩溃后不会影响主进程的运行。
多进程的缺点:
Applicaton的重新创建,每个进程有自己独立的virtual machine,每次创建新的进程就像创建一个新的Application
静态成员变量和单例模式失效,每个进程有自己独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这就导致不同虚拟机在访问同一个对象时会产生多分副本。
SharedPreference的可靠性下降,不支持多进程
线程同步机制失效
Bundle类
bundle 定义 bundle 是一个 final 类,final 类通常功能是完整的,它们不能被继承。Java 中有许多类是 final 的,譬如 String, Interger 以及其他包装类。
public final class Bundle extends BaseBundle implements Cloneable, Parcelable
bundle 传递的数据可以是 boolean、byte、int、long、float、double、string 等基本类型或它们对应的数组,也可以是对象或对象数组。但是如果传递对象或对象数组,该对象必须实现 Serializable 或 Parcelable 接口。由 Bundle 定义我们也可以看到其实现了 Parcelable 接口,所以支持实现了Parcelable 接口的对象。
因此当我们在一个进程中启动了另外一个进程的 Activity、Service、Receiver,我们就可以在 Bundle 中附加我们需要传输给远程进程的信息(前提是能够被序列化)并通过 Intent 发送出去。
AIDL
对于 AIDL 还没接触过的小伙伴,可以看看 Android AIDL 实例与原理分析,可以让你有个印象。
代码解析
首先来看三个 AIDL 接口:
1、IEventReceiver:事件接收器
// 事件接受器
interface IEventReceiver {
// 这里的 event 是 bundle 类型
void onEventReceive(String key,in Bundle event);
}
2、IPCCallback:看名字也可以看出来是跨进程 callback
interface IPCCallback {
// result 也是 bundle
void onSuccess(in Bundle result);
void onFail(String reason);
}
3、IServiceFetcher:获取服务的。可以再此进行注册。
interface IServiceFetcher {
// service 是 Ibinder 类型
android.os.IBinder getService(java.lang.String name);
// 注册服务
void addService(java.lang.String name, android.os.IBinder service);
// 添加回调
void addEventListener(java.lang.String name, android.os.IBinder service);
// 移除 service
void removeService(java.lang.String name);
// 移除回调
void removeEventListener(java.lang.String name);
// 发送消息
void post(String key,in Bundle result);
}
启动分析
根据代码可知,咱们有三个进程,分别是:
com.ipc.code:vc :TestActivity 运行所在的进程;这是属于用户测的。
com.ipc.code:vm : 也就是 BinderProvider 存在的进程;IPCBus 也在该进程,主要是用于保存和传递数据
com.ipc.code :MainActivity 主进程;也就是
每个进程在初始化的时候,都会走一遍 Application 的初始化,因此如果需要对进程做啥操作,可以判断出具体的进程,然后做一些额外的操作。对于 CmProcess ,所有进程的初始化逻辑都是一样的。
public class App extends Application {
private static final String TAG = "App"; @Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 先启动主进程,之后才启动其他进程
VCore.init(base);
}
}
启动过程中,会主动为每个进程注册回调,注意是每个进程。
该 init 方法最终会走入到下面的方法中:
public void startup(Context context) {
if (!isStartUp) {
// 在主线程启动,每个进程都有一个自己的主线程
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("VirtualCore.startup() must called in main thread.");
} ServiceManagerNative.SERVICE_CP_AUTH = context.getPackageName() + "." + ServiceManagerNative.SERVICE_DEF_AUTH;
this.context = context;
// 传入了一个 cache 实例,这个实例是只有主线程有的
IPCBus.initialize(new IServerCache() {
@Override
public void join(String serverName, IBinder binder) {
ServiceManagerNative.addService(serverName, binder);
} @Override
public void joinLocal(String serverName, Object object) {
ServiceCache.addLocalService(serverName,object);
} @Override
public void removeService(String serverName) {
ServiceManagerNative.removeService(serverName);
} @Override
public void removeLocalService(String serverName) {
ServiceCache.removeLocalService(serverName);
} @Override
public IBinder query(String serverName) {
return ServiceManagerNative.getService(serverName);
} @Override
public Object queryLocal(String serverName) {
return ServiceCache.getLocalService(serverName);
} @Override
public void post(String key,Bundle bundle) {
ServiceManagerNative.post(key,bundle);
}
});
// 这里是根据进程名字添加注册的事件接收器
ServiceManagerNative.addEventListener(AppUtil.getProcessName(context, Process.myPid()), EventReceiver.getInstance());
isStartUp = true;
}
}
这里整个逻辑很简单,就是在主线程初始化了 IPCBus,然后给该进程注册了一个事件分发的监听。
EventReceiver
public class EventReceiver extends IEventReceiver.Stub { private static final String TAG = "EventReceiver"; private static final EventReceiver EVENT_RECEIVER = new EventReceiver(); private EventReceiver(){} public static final EventReceiver getInstance(){
return EVENT_RECEIVER;
} @Override
public void onEventReceive(String key,Bundle event) {
EventCenter.onEventReceive(key,event);
}
}
整个类的代码很简单。但是要注意的是,其继承了 IEventReceiver.Stub,说明他具有跨进程传输的能力。主要就是通过 EventCenter 来分发消息。
由于每个进程都会走一遍初始化逻辑,所以每个进程都注册了事件的接收。
ServiceManagerNative
从名字也可以看出来,这个跟我们平时看到的 ServiceManager 很像。主要就是用来获取 service 和注册 listener 的。
public static void addEventListener(String name, IBinder service) {
IServiceFetcher fetcher = getServiceFetcher();
if (fetcher != null) {
try {
fetcher.addEventListener(name, service);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
首先是调用 getServiceFetcher 来获取最终保存服务的 fetcher。
注册回调的时候,会先获取是否存在 (binder)ServiceFetcher ,在将其转化为本地 binder;这样 ServiceFetcher 的管理器就可以用了。
private static IServiceFetcher getServiceFetcher() {
if (sFetcher == null || !sFetcher.asBinder().isBinderAlive()) {
synchronized (ServiceManagerNative.class) {
Context context = VirtualCore.get().getContext();
Bundle response = new ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName("@").call();
if (response != null) {
IBinder binder = BundleCompat.getBinder(response, "_VM_|_binder_");
linkBinderDied(binder);
sFetcher = IServiceFetcher.Stub.asInterface(binder);
}
}
}
return sFetcher;
}
首先是看 ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName("@").call(),他最终会调用下面的方法:
//ContentProviderCompat
public static Bundle call(Context context, Uri uri, String method, String arg, Bundle extras) {
// 这里还区分了版本
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
return context.getContentResolver().call(uri, method, arg, extras);
}
ContentProviderClient client = crazyAcquireContentProvider(context, uri); // 这里会不断重试最终会获得对 BinderProvider 的引用
Bundle res = null;
try {
// 通过约定好的方法名字获得bindle
res = client.call(method, arg, extras);
} catch (RemoteException e) {
e.printStackTrace();
} finally {
releaseQuietly(client);
}
return res;
}
BinderProvider
下面看下 BinderProvider 的 call 方法。
新建了一个 bundle 对象,然后将 binder 保存在里面。注意这是通过跨进程调用,最终将 bundle 传回主进程,然后拿到了 ServiceFetcher 的 binder,并将其转为本地 binder。
可以发现这里对于方法名是 "@" 时,就会返回 bundle ,否则就是返回 null 。
public Bundle call(String method, String arg, Bundle extras) {
if ("@".equals(method)) {
Bundle bundle = new Bundle();
BundleCompat.putBinder(bundle, "_VM_|_binder_", mServiceFetcher);
return bundle;
}
return null;
}
简单来说,就是大家都通过 binderProvider 这个进程来保存对于回调的注册,保存是基于进城名字来的,因此可以保证不会被覆盖。
此处的 mServiceFetcher 是 BinderProvider 内部内的实例,但是其继承了 IServiceFetcher.Stub,因此也就有了跨进程的能力。
到这里,理一下前面的逻辑:
ServiceManagerNative.addEventListener(AppUtil.getProcessName(context, Process.myPid()), EventReceiver.getInstance());
某个进程的主线程调用这个方法,所做的具体事情如下:
通过 binder 拿到了binderProvider 中的 IServiceFetcher.Stub 的实例;
向 IServiceFetcher.Stub 注册回调,该回调最终会被保存 binderProvider 进程里面。
BinderProvider 启动分析
上面介绍了其是怎么将 listener 注册到 binderProvider 进程的,但是并没有讲到接下去我们看下 BinderProvider 的启动过程,
下图是 ContentProvider 的启动流程。当我们在主进程想获取 server 的时候,这时候,会看看 provider 存不存在,没有的就会进行启动,同时会走 Application 的初始化逻辑,
具体我们可以看下面这个启动流程图:
Application 的 attachBaseContext 方法是优先执行的;
ContentProvider 的 onCreate的方法 比 Application的onCreate的方法 先执行;
Activity、Service 的 onCreate 方法以及 BroadcastReceiver 的 onReceive 方法,是在 MainApplication 的 onCreate 方法之后执行的;
调用流程为: Application 的 attachBaseContext ---> ContentProvider 的 onCreate ----> Application 的 onCreate ---> Activity、Service 等的 onCreate(Activity 和 Service 不分先后);
这里主要是梳理了下 provider 的启动过程,并没有很细讲,但是有必要了解一下。
MainActivity
接下去,开始看 MainActivity 里面的代码。
调用 registerService 注册服务,传入 IPayManager.class 和 MainActivity;记得 MainActivity 也实现了 IPayManager 接口。
VCore.getCore().registerService(IPayManager.class, this);
看下,里面的具体代码逻辑
// Vcore
public VCore registerService(Class<?> interfaceClass, Object server){ if (VirtualCore.get().getContext() == null){
return this;
}
Object o = IPCBus.getLocalService(interfaceClass);
// 如果是第一次调用就会返回空
IBinder service = ServiceManagerNative.getService(interfaceClass.getName());
if (service != null && o != null){
return this;
}
IPCBus.registerLocal(interfaceClass,server);
// 这里的注册就是把 server 保存到 binder 中
IPCBus.register(interfaceClass,server);
return this;
}
这里使用了一个 registerLocal 和 register 方法,但是本质上两个方法是有区别的。registerLocal 意思很明确,就是本地 ServiceCache 保存一份。但是 register,确实做了一些额外的操作。
public static void register(Class<?> interfaceClass, Object server) {
checkInitialized();
ServerInterface serverInterface = new ServerInterface(interfaceClass);
// 这里主要是获取一个 binder,或者换句话来说,采用 binder 来保存相关数据
TransformBinder binder = new TransformBinder(serverInterface, server);
// 这里就是把 binder 保存到 binderProvider
sCache.join(serverInterface.getInterfaceName(), binder);
}
首先这里创建了一个 ServerInterface 实例,该实例内部保存了传过了来的接口和接口的方法,并将方法和 code 联系在一起。
public ServerInterface(Class<?> interfaceClass) {
this.interfaceClass = interfaceClass;
Method[] methods = interfaceClass.getMethods();
codeToInterfaceMethod = new SparseArray<>(methods.length);
methodToIPCMethodMap = new HashMap<>(methods.length);
for (int i = 0; i < methods.length; i++) {
// 这里每一个方法都有一个 code
int code = Binder.FIRST_CALL_TRANSACTION + i;
// 组成一个 ipcMenhod
IPCMethod ipcMethod = new IPCMethod(code, methods[i], interfaceClass.getName());
codeToInterfaceMethod.put(code, ipcMethod);
// 保存他们的映射关系
methodToIPCMethodMap.put(methods[i], ipcMethod);
}
}
同时利用 TransformBinder 将接口和 实例保存到 binder 中。再将 binder 和 接口名字 保存到 ServiceCache 中。
注册完以后,下面是调用获取本地服务:
// 其实 service 本质还是这个 MainActivity
IPayManager service = VCore.getCore().getLocalService(IPayManager.class);
最后注册了一个回调:
VCore.getCore().subscribe("key", new EventCallback() {
@Override
public void onEventCallBack(Bundle event) {
}
});
最终 EventCenter 会保存相关信息;
TestActivity
最后启动 TestActivity ,这个是在另一个进程。在 onCreate 里面调用下面的方法:
IPayManager service = VCore.getCore().getService(IPayManager.class);
进程刚刚创建,我们看看是怎么获取服务的:
// Vcore
public <T> T getService(Class<T> ipcClass){
T localService = IPCBus.getLocalService(ipcClass);
if (localService != null){
return localService;
}
return VManager.get().getService(ipcClass);
}
这里很明确,本地肯定是没有的,因此,最后会从 VManager 中获取:
// VManager
public <T> T getService(Class<T> ipcClass) {
T t = IPCBus.get(ipcClass);
if (t != null){
return t;
}
IPCSingleton<T> tipcSingleton = mIPCSingletonArrayMap.get(ipcClass);
if (tipcSingleton == null){
tipcSingleton = new IPCSingleton<>(ipcClass);
mIPCSingletonArrayMap.put(ipcClass,tipcSingleton);
}
return tipcSingleton.get();
}
接下去我们看下 IPCSingleton 相关逻辑
// IPCSingleton
public T get() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = IPCBus.get(ipcClass);
}
}
}
return instance;
}
这是一个单例,目的也很明确,就是只获取一次,可以看到后面又调到了 IPCBus 里面。
// IPCBus
public static <T> T get(Class<?> interfaceClass) {
checkInitialized();
ServerInterface serverInterface = new ServerInterface(interfaceClass);
// 这里获取的 binder 应该是 TransformBinder
IBinder binder = sCache.query(serverInterface.getInterfaceName());
if (binder == null) {
return null;
}
// 这里使用了动态代理
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new IPCInvocationBridge(serverInterface, binder));
}
这里采用了动态代理创造了一个实例,最终返回的实例被保存在一个单例中。
可以看到,这里回去查找存不存在 binder。
// VirtualCore
public IBinder query(String serverName) {
return ServiceManagerNative.getService(serverName);
}
还是通过 ServiceManagerNative 来获取的 service;这里又回到我们之前分析过的逻辑。先从 binderProvider 获取 fetcher, 也就是 ServiceFetcher。
IServiceFetcher fetcher = getServiceFetcher();
它也会从 ServiceFetcher 中获取到 binder ,而这个 binder 就是之前我们保存的 TransformBinder 。拿到这个之后,还是一样,将其转化为该进程的本地 binder .
// BinderProvider
private class ServiceFetcher extends IServiceFetcher.Stub {
最后,我们通过动态代理的形式,创建了一个 IPayManager 的实例。
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new IPCInvocationBridge(serverInterface, binder));
这里需要注意的是 IPCInvocationBridge 继承自 InvocationHandler。
拿到后,开始调用对应的方法:
if (service != null){
Log.d(TAG, "onCreate: shentest before vcore " + AppUtil.getAppName(this));
// 首先这个 service 是跨进程调用的,怎么才通知到其他组件?这里大家可以思考下
service.pay(5000, new BaseCallback() {
@Override
public void onSucceed(Bundle result) {
textview.setText(result.getString("pay"));
Bundle bundle = new Bundle();
bundle.putString("name", "DoDo");
VCore.getCore().post("key",bundle);
} @Override
public void onFailed(String reason) { }
});
}
调用 service.pay 的时候,就会调用动态代理中的 invoke 方法:
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
IPCMethod ipcMethod = serverInterface.getIPCMethod(method);
if (ipcMethod == null) {
throw new IllegalStateException("Can not found the ipc method : " + method.getDeclaringClass().getName() + "@" + method.getName());
}
// 这里很关键
return ipcMethod.callRemote(binder, args);
}
首先根据方法名获得 ipcMethod,里面保存了方法的 code,接口名字,参数,返回值。接着调用了 ipcMethod.callRemote, 该方法又会调用:
// IPCMethod
// 这个方法很重要,需要理解其实现过程
public Object callRemote(IBinder server, Object[] args) throws RemoteException {
Parcel data = Parcel.obtain(); // 获取一个新的 parcel 对象
Parcel reply = Parcel.obtain();
Object result;
try {
data.writeInterfaceToken(interfaceName);
data.writeArray(args);
// 这里 server 就是 transformBinder
server.transact(code, data, reply, 0);
reply.readException();
result = readValue(reply);
if (resultConverter != null) {
result = resultConverter.convert(result);
}
} finally {
data.recycle();
reply.recycle();
}
return result;
}
code 变量用于标识客户端期望调用服务端的哪个函数,因此,双方需要约定一组 int 值,不同的值代表不同的服务端函数,该值和客户端的 transact() 函数中第一个参数 code 的值是一致的。
enforceInterface() 是为了某种校验,它与客户端的 writeInterfaceToken() 对应,具体见下一小节。
readString() 用于从包裹中取出一个字符串。如果该 IPC 调用的客户端期望返回一些结果,则可以在返回包裹 reply 中调用 Parcel 提供的相关函数写入相应的结果。 Parcel.writeXXX();
现在要看的是怎么通过 binder 一步一步拿到参数。
使用 Parcel 一般是通过 Parcel.obtain() 从对象池中获取一个新的 Parcel 对象,如果对象池中没有则直接 new 的 Parcel 则直接创建新的一个 Parcel 对象,并且会自动创建一个Parcel-Native 对象。
writeInterfaceToken 用于写入 IBinder 接口标志,所带参数是 String 类型的,如 IServiceManager.descriptor = "android.os.IServiceManager"。
之前说的 code 在这里用上了,code 是一个私有变量,跟 method 绑定在一起的。
中间有个 server.transact(code, data, reply, 0); 该方法实现了跨进程调用,最终会走到 binderProvider 的下面 onTransact 方法:
// TransformBinder 运行在主进程
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
// 回调进来后,就到了 MainActivity 的进程
if (code == INTERFACE_TRANSACTION) {
reply.writeString(serverInterface.getInterfaceName());
return true;
}
IPCMethod method = serverInterface.getIPCMethod(code);
if (method != null) {
try {
method.handleTransact(server, data, reply);
} catch (Throwable e) {
e.printStackTrace();
}
return true;
}
return super.onTransact(code, data, reply, flags);
}
这里主要是根据 code 来获取到是哪个方法被调用了,下面才是真正的处理。
// IPCMethod
public void handleTransact(Object server, Parcel data, Parcel reply) {
data.enforceInterface(interfaceName); // 确保是目标接口
Object[] parameters = data.readArray(getClass().getClassLoader());
if (parameters != null && parameters.length > 0) {
for (int i = 0; i < parameters.length; i++) {
if (converters[i] != null) {
parameters[i] = converters[i].convert(parameters[i]);
}
// 如果参数里面包含有 binder
if (parameters[i] instanceof IBinder){
parameters[i] = IPCCallback.Stub.asInterface(((IBinder)parameters[i]));
}
}
}
try {
// 最终通过反射的形式实现了的调用
// 其实最主要的是通过 binder 拿到参数,然后知道对方调用的是哪个方法。
// 现在要分析的是,他怎么将数据传过来的
Object res = method.invoke(server, parameters);
reply.writeNoException();
reply.writeValue(res);
} catch (IllegalAccessException e) {
e.printStackTrace();
reply.writeException(e);
} catch (InvocationTargetException e) {
e.printStackTrace();
reply.writeException(e);
}
}
看看 convert 里面的操作:
public Object convert(Object param) {
if (param != null) {
if (asInterfaceMethod == null) {
synchronized (this) {
if (asInterfaceMethod == null) {
// 找到 asInterface 方法
asInterfaceMethod = findAsInterfaceMethod(type);
}
}
}
try {
// 因为 asInterface 方法是静态方法,所以对象可以传入空,最终转变成所需要的参数类型
return asInterfaceMethod.invoke(null, param);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
return null;
}
通过 convert 这个一调用,就转变成我们所需要的参数了。
private static Method findAsInterfaceMethod(Class<?> type) {
for (Class<?> innerClass : type.getDeclaredClasses()) {
// public static class Stub extends Binder implements IType
if (Modifier.isStatic(innerClass.getModifiers())
&& Binder.class.isAssignableFrom(innerClass)
&& type.isAssignableFrom(innerClass)) {
// public static IType asInterface(android.os.IBinder obj)
for (Method method : innerClass.getDeclaredMethods()) {
if (Modifier.isStatic(method.getModifiers())) {
Class<?>[] types = method.getParameterTypes();
if (types.length == 1 && types[0] == IBinder.class) {
return method;
}
}
}
}
}
throw new IllegalStateException("Can not found the " + type.getName() + "$Stub.asInterface method.");
}
findAsInterfaceMethod 通过层层筛选,最终获得需要的那个方法:
public static com.cmprocess.ipc.server.IPCCallback com.cmprocess.ipc.server.IPCCallback$Stub.asInterface(android.os.IBinder)
通过 invoke 方法,终将获得了我们需要的类型。
这里 server 就是 mainActivity。在把对应的参数传进去即可。最终调到了mainActivity 里面的 pay 方法。
public void pay(final int count, final IPCCallback callBack) { new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
Bundle bundle = new Bundle();
bundle.putString("pay", count + 100 + "");
try {
// callback 也是一个binder
callBack.onSuccess(bundle);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
此处,callback 也是一个binder,调用成功后,发送了post。 其实最终也是调用了 ServiceFetcher 。
// TestActivity
VCore.getCore().post("key",bundle);
其实也是通过 binder 来进行发送消息的。由于每个进程都注册了消息回调,因此,每个进程都会收到。
// ServiceCache
public static synchronized void sendEvent(String key,Bundle event){
if (sEventCache.isEmpty()){
return;
}
for (IBinder binder:sEventCache.values()){
IEventReceiver eventReceiver = IEventReceiver.Stub.asInterface(binder);
try {
eventReceiver.onEventReceive(key, event);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
EventReceiver 存在于每个进程,因此,对于 binderprovider 来说都是客户端,其他进程则是服务端。最终 EventCenter 会根据 KEY 值来做分发。
到这里整个流程就基本讲完了。
不过我们发现还有两个 service ,他们的作用是干嘛用的呢?感觉是用来保活的,防止 provider 死了。
参考文章:
详解 CmProcess 跨进程通信的实现的更多相关文章
- JavaScript系列----AJAX机制详解以及跨域通信
1.Ajax 1.1.Ajax简介 Ajax简介这一部分我们主要是谈一下ajax的起源,ajax是什么?因为这些是跟技术无关的.所以,大多细节都是一笔带过. Ajax的起源? Ajax一词源于2005 ...
- 图文详解 Android Binder跨进程通信机制 原理
图文详解 Android Binder跨进程通信机制 原理 目录 目录 1. Binder到底是什么? 中文即 粘合剂,意思为粘合了两个不同的进程 网上有很多对Binder的定义,但都说不清楚:Bin ...
- 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇
前言 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一.Android系统的运行由大量相互独立的进程相互协助来完成的,所以Android进程间通信问题,是做好Andro ...
- 【Chromium中文文档】跨进程通信 (IPC)
跨进程通信 (IPC) 转载请注明出处:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//General_Architecture/I ...
- Android随笔之——跨进程通信(一) Activity篇
在Android应用开发中,我们会碰到跨进程通信的情况,例如:你用QQ通讯录打电话的时候会调用系统的拨号应用.某些新闻客户端可以将新闻分享到QQ.微信等应用,这些都是跨进程通信的情况.简而言之,就是一 ...
- WinForm实现跨进程通信的方法
public class WinMessageHelper { private struct COPYDATASTRUCT { public IntPtr dwData; public int cbD ...
- AIDL跨进程通信
Android跨进程通信会用到AIDL,当然跨进程通信不一定要用AIDL,像广播也是可以的,当然这里用到AIDL相对比较安全一些: AIDL允许传递基本数据类型(Java 的原生类型如int/long ...
- Android中的跨进程通信方法实例及特点分析(二):ContentProvider
1.ContentProvider简单介绍 在Android中有些数据(如通讯录.音频.视频文件等)是要供非常多应用程序使用的.为了更好地对外提供数据.Android系统给我们提供了Content P ...
- 跨进程通信之Messenger
1.简介 Messenger,顾名思义即为信使,通过它可以在不同进程中传递Message对象,通过在Message中放入我们需要的入局,就可以轻松实现数据的跨进程传递了.Messenger是一种轻量级 ...
随机推荐
- Linux下搭建mysql
[准备环境] Linux centos7 [mysql安装步骤] 1.首先确定centos版本 cat /etc/redhat-release 2.yum安装 yum -y install mar ...
- WeChair项目Beta冲刺(2/10)
团队项目进行情况 1.昨日进展 Beta冲刺第二天 昨日进展: 昨天由于组内成员课程繁重,但是大家还是花时间一起开会谈论了开发的一些细节和交流了一些问题 2.今日安排 前端:扫码占座功能和预约功 ...
- 多语言工作者の十日冲刺<10/10>
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 团队进行Alpha冲刺--第十天(05.09) 作业正文 ...
- 同步/异步/阻塞/非阻塞/BIO/NIO/AIO各种情况介绍
常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTTP接口拿到详情数据,然后使用适合的视图展示详情数据. 如果网速很慢,代码发起一个HTTP请求后,就卡住不动了,直到十几秒后才拿到HTT ...
- Angular 从入坑到挖坑 - 模块简介
一.Overview Angular 入坑记录的笔记第七篇,介绍 Angular 中的模块的相关概念,了解相关的使用场景,以及知晓如何通过特性模块来组织我们的 Angular 应用 对应官方文档地址: ...
- python在循环中追加字典
1. 在循环中用append增加key值,最终生成全是key值的list: 2. 在循环中用append增加value值,最终生成全是value值的list: 3. 最后用zip将两个list合并成一 ...
- 如何去除List集合中重复的元素
1.通过循环进行删除 public static void removeDuplicate(List list) { for ( int i = 0 ; i < list.size() - 1 ...
- JavaScript手写new方法
1.看一下正常使用的new方法 function father(name){ this.name=name; this.sayname=function(){ console.log(this.nam ...
- Dubbo 负载均衡的实现
前言 负载均衡是指在集群中,将多个数据请求分散在不同单元上进行执行,主要为了提高系统容错能力和加强系统对数据的处理能力. 在 Dubbo 中,一次服务的调用就是对所有实体域 Invoker 的一次筛选 ...
- 初步了解JVM
先看一眼JVM虚拟机运行时的内存模型: 1.方法区 Perm(永久代.非堆) 2.虚拟机栈 3.本地方法栈 (Native方法) 4.堆 5.程序计数器 1 首先的问题是:jvm如何知道那些对象需要回 ...