在操作系统中,进程与进程间的内存和数据都是不共享的。这样做的目的,是为了避免进程间相互操作数据的现象发生,从而引起各自的安全问题。为了实现进程隔离,采用了虚拟地址空间,两个进程各自的虚拟地址不同,从逻辑上来实现彼此间的隔离。两个进程之间要进行通信,就需要采用特殊的通信机制:进程间通信(IPC:Inter-Process Communication,即进程间通信或跨进程通信,简称 IPC)。
一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来。二者的区别:
进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:
跨进程通信的基本原理:
我们知道,Android系统就是基于Linux内核实现的,咱们先简单了解一下Linux系统的IPC方式。虽然不同的资料对各种方式的名称和种类说法不完全相同,但是主要来说有如下6种方式:(1)管道 Pipe;(2)信号Signal;(3)信号量Semaphore;(4)消息队列Message Queue;(5)共享内存Shared Memmory;(6)套接字Socket。读者若想深入了解这些方式,可以自行查阅,这里不展开介绍,只需要知道有这六种方式即可。
Android IPC的方式在不同的资料中有不同的说法,但从大方向上看,可以归纳为如下四种(这里仅对各种方式做简单介绍和优劣对比,对具体如何使用,不做讲解):
1、Activity方式
Activity是四大组件中使用最频繁的,咱们先从它说起。使用Activity方式实现,就是使用startActivity()来启动另外一个进程的Activity。我们在使用App的使用,往往会遇到如下几种情形:(1)浏览器中看到一篇比较不错的文章,分享到微信朋友圈或者微博;(2)在某个App中点击某个网址,然后界面跳转到浏览器中进行阅读;(3)使用美团外卖app,看到店家的电话,点击联系商家时,跳转到了电话拨打界面…这样的操作再频繁不过了。这些就是通过startActivity的方式从一个App,跳转到了另外一个App的Activity,从而实现了跨进程通信。
我们知道,在调用startActivity(Intent intent)的时候,intent有两个类型:显式Intent和隐式Intent。显式Intent的使用方式如下,用于进程内组件间通信:
Intent intent = new Intent(this,OtherActivity.class);
startActivity(intent);
隐式intent的使用方式如下,用于IPC:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
startActivity(intent); //startActivityForResult()同样,这里不赘述
Intent.ACTION_CALL就是字符串常量“android.intent.action.CALL”,这种方式通过setAction的方式来启动目标app的Activity,上述代码就是启动电话app的拨号界面,有时候还可以带上电话号码等参数。由上可知,Activity实现跨进程通信的方式,适合于不同App之间功能界面的跳转。
2、Content provider(后面简称CP)方式
当我们开发App需要用到联系人,多媒体信息等数据的时候,往往会通过系统提供Uri,采用CP的方式去获取。Android系统中,数据主要存储在自带的SqlLite数据库中。应用要共享SqlLite中的数据给其他App操作(增、删、改、查),就要用到CP,也就是说,CP主要用于跨进程数据库共享。Android系统提供了很多的CP来供其它App使用,如多媒体信息、联系人、日历等。如下图显示了Android系统提供的CP,包名都是以"com.android.providers“开头的:
这些用于共享的数据其实都是存储在系统数据库中的,如下显示了meida CP中的数据库:
App开发者也可以自定义CP,把自己的数据提供给其它app使用,也可以自己定义操作权限,如只允许其它app读取自己的数据,而不允许修改等。CP的使用场景,是提供数据共享。CP本质上还是在操作数据库,数据存储在sdcard中,所以建立连接和操作数据都是耗时操作,所以注意开辟子线程去操作。当数据库中数据有变化时,Content Observer监听到数据库变化也是有一定的滞后。
3、Broadcase方式
Broadcast使用非常简单,注册好广播,添加上action,就可以等着接收其他进程发出的广播。发送和接收广播时,还可以借助Intent来携带数据。但是广播的使用存在很多问题,被很多程序员吐槽,甚至鄙夷,所以选择用广播进行跨进程通信,是下下策。下面盘点一下Broadcast的槽点:
总而言之,言而总之,使用Broadcast来实现跨进程通信,是下下之策!
4、Service方式
启动Service的方式有多种,有的用于跨进程通信,有的用于进程内部模块之间的通信,下面仅简单介绍一下跨进程通信的方式。
(1)startService()方式
Intent startIntent = new Intent ();
ComponentName componentName = new ComponentName(string packageName,string serviceClassName);
startIntent.setComponent(componentName );
startService( startIntent);
该方式启动远程Service实现跨进程通信,耦合度比较低,功能及代码结构清晰,但是存在以下缺点:
没有好的机制立即返回执行结果,往往Service完成任务后,还需要其他方式向Client端反馈。
Service端无法识别Client端是谁,只知道有启动命令,但无法知道是谁下的命令。
在已经创建好Service情况下,每次调用startService,就会执行onStartCommand()生命周期方法,相比于bindService,效率低下。
如果Client端忘记调用stopService()了,那么该Service会一直运行下去,这也是一个隐患。
所以,针对上述缺点,往往建议startService()方式用于同一个App内,跨进程的方式用后面将要讲到的AIDL来实现。
(2)bindService、Handler、Messager结合
这种方式也可以实现跨进程通信,据说和AIDL具有相同的功效,但比AIDL使用简单,详细可以阅读博文【Android总结篇系列:Android Service】。
(3)AIDL
这种方式也是 bindService() 启动方式的一种使用情况,也是广受程序员们推崇的方式。前面说 startService() 和 Broadcast 如何不好,就是为了衬托 AIDL 如何的好!这是本文的主角,本文后面会专门详细讲解,这里不赘述。
【总结】从如上的介绍来看,其实 Android 中跨进程通信的实现,就是利用四大组件来实现的。对方式的选择,我们总结一下:
传统的跨进程通信需拷贝数据2次,但 Binder 机制只需1次,主要是使用到了内存映射,具体下面会详细说明。
跨进程通信的核心原理:内存映射,具体请看文章:操作系统:图文详解 内存映射
对比 Linux (Android基于Linux)上的其他进程通信方式(管道、消息队列、共享内存、信号量、Socket),Binder 机制的优点有:
Binder 跨进程通信机制 模型 基于 Client - Server 模式:
此处重点讲解 Binder 驱动的作用和 原理:
Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互。具体原因:
所以,原理图可表示为以下(虚线表示并非直接交互—):
Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)。所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程并显式使用上述3个步骤,最终借助 Android的基本架构功能就可完成进程间通信:
前面我们讲到,为了克服 Linux 中 IPC 各种方式的缺点,在Android中引入了Binder机制。但是当说起Binder在Android中的使用时,几乎所有的资料都是在说 AIDL 的使用。AIDL 的全称是 Android Interface Definition Language,即Android接口定义语言,是 Binder 机制实现 Android IPC 时使用比较广泛的工具。
本节将以一个 Demo 演示一下 AIDL 的基本实现步骤。源码地址:https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A 提取码:4auk 。如下两个图展示了该 Demo 的结构图和 AIDL 关键文件:
建立两个App,分别为Client端和Server端。
这个比较 好理解,Server端就是包含了Service真正干活的那一端;Client端就是通过远程操控指挥的那一端,分别在不同的App中。如下图所示:
1、在Server端main目录下建立aidl文件夹以及.aidl文件,并copy一份到Client端,如图6.1中②处所示结构。注意,Client端和Server端②处是一模一样的。另外,AS中提供了快捷方式创建aidl文件,在main处点击右键 > New > AIDL > AIDL File文件,按照提示给aidl文件命名即可自动创建完成,可以看到文件路径也是该项目的包名。
这里给aidl命名为IDemoService.aidl,这里需要注意的是命名规范,一般都是以“I”开头,表示是一个接口,其内容如下:
//========== IDemoService.aidl========
package com.songwei.aidldemoserver;
// Declare any non-default types here with import statements
interface IDemoService {
void setName(String name);
String getName();
}
2、Server端创建Service文件 AidlService.java,如图6.1中③处所示,代码如下:
package com.songwei.aidldemoserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class AidlService extends Service {
private final static String TAG = "aidlDemo";
public AidlService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "server:[onCreate]");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
Log.i(TAG, "server:[onBind]");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "server:[onUnbind]");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "server:[onDestroy]");
}
class MyBinder extends IDemoService.Stub {
private String mName = "";
public void setName(String name) throws RemoteException{
Log.i(TAG, "server:[setName]");
mName = name;
}
@Override
public String getName() throws RemoteException {
Log.i(TAG, "server:[getName]");
return mName;
}
}
}
为了下文分析流程及生命周期,在其中各个方法中都添加了Log。同时,在Server端的AndroidManifest.xml文件中添加该Service的注册信息(注意exported属性值,如果有“intent-filter”,则默认值为true,否则为false。所以这里其实可以去掉,因为有“intent-filter”,其默认值就是true):
<service
android:name=".AidlService"
android:exported="true">
<intent-filter>
<action android:name="com.songwei.aidl" />
</intent-filter>
</service>
3、编译Sever端和Client端App,生成IDemoService.java文件。
当编译的时候,AS会自动为我们生成IDemoService.java文件,如图6.1和图6.2中④处所示。当你打开该文件的时候,是不是看到了如下场景?
惊不惊喜?意不意外?是不是一脸懵逼,大惊,卧槽,这尼玛啥玩意啊?
AIDL 是 Android 接口定义语言,IDemoService.java 是一个java中的interface(接口),现在是不是若有所思了呢?AIDL 正是定义了 IDemoService.java 这个接口!!! 这个接口文件就是 AIDL 帮助咱们生成的 Binder 相关代码,这些代码就是用来帮助实现 Client 端和 Server 端通信的。前面第2步中提到的IDemoService.aidl文件,其作用就是作为原料通过AIDL来生成这些你貌似看不懂的代码的,第3步中的 AidlService.java 和后续在 Client 端App连接Server端App的时候,其实这个aidl文件就从来没有出现过,也就是说,它已经没有什么价值了。所以说,AIDL 的作用就是用来自动生成 Binder 相关接口代码的,而不需要开发者手动编写。有些教程中说,可以不使用AIDL而手动编写这份Binder代码,AIDL不是Binder实现通信所必需的,笔者也没有尝试过手动编写,如果读者您想挑战,可以尝试一下!
咱们继续!打开IDemoService.java文件后,点击主菜单兰Code > Reformat Code (或 Ctrl + Alt +L快捷健),你会发现画面变成了下面这个样子:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\ASWorkspace\\testDemo\\aidldemoclient\\src\\main\\aidl\\com\\songwei\\aidldemoserver\\IDemoService.aidl
*/
package com.songwei.aidldemoserver;
public interface IDemoService extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.songwei.aidldemoserver.IDemoService {
private static final java.lang.String DESCRIPTOR = "com.songwei.aidldemoserver.IDemoService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.songwei.aidldemoserver.IDemoService interface,
* generating a proxy if needed.
*/
public static com.songwei.aidldemoserver.IDemoService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.songwei.aidldemoserver.IDemoService))) {
return ((com.songwei.aidldemoserver.IDemoService) iin);
}
return new com.songwei.aidldemoserver.IDemoService.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_setName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.setName(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.songwei.aidldemoserver.IDemoService {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void setName(java.lang.String name) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.lang.String getName() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void setName(java.lang.String name) throws android.os.RemoteException;
public java.lang.String getName() throws android.os.RemoteException;
}
惊不惊喜?意不意外?这下是不是有种似曾相识的赶脚?这就是一个很普通的java中的接口文件而已,结构也非常简单。
神秘的面纱揭开一层了吧!后面在讲完Client端和Server端的连接及通信后,还会继续深入剖析这个文件。
Client端通过ClientActivity活动类来连接Server端AidlService并通信。ClientActivity.java 的内容如下,布局文件在此省略,比较简单,就两个按钮,一个用于绑定,一个用于解绑,看Button命名也很容易分辨:
package com.songwei.aidldemoclient;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.songwei.aidldemoserver.IDemoService;
public class ClientActivity extends AppCompatActivity {
private final static String TAG = "aidlDemo";
private Button mBindBtn, mUnBindBtn;
private IDemoService mDemoService;
private boolean mIsBinded = false;
private ServiceConnection mConn = new ServiceConnection() {
//当与远程Service绑定后,会回调该方法。
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
Log.i(TAG, "client:[onServiceConnected]componentName=" + componentName);
mIsBinded = true;
//得到一个远程Service中的Binder代理,而不是该Binder实例
mDemoService = IDemoService.Stub.asInterface(binder);
try {
//远程控制设置name值
mDemoService.setName("Andy Song");
//远程获取设置的name值
String myName = mDemoService.getName();
Log.i(TAG, "client:[onServiceConnected]myName=" + myName);
} catch (RemoteException e) {
e.printStackTrace();
}
}
//该回调方法一般不会调用,如果在解绑的时候,发现该方法没有调用,不要惊慌,因为该方法的调用时机是Service被意外销毁时,比如内存不足时。
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "client:[onServiceDisconnected]");
mIsBinded = false;
mDemoService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBindBtn = (Button) findViewById(R.id.btn_bind);
mBindBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Android5.0及以后,出于对安全的考虑,Android系统对隐式启动Service做了限制,需要带上包名或者类名,这一点需要注意。
Intent intent = new Intent();
intent.setAction("com.songwei.aidl");
intent.setPackage("com.songwei.aidldemoserver");
bindService(intent, mConn, BIND_AUTO_CREATE);
}
});
mUnBindBtn = (Button) findViewById(R.id.btn_unbind);
mUnBindBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//解除绑定,当调用unbindService时,一定要判断当前service是否是binded的,如果没有,就会报错。
if (mIsBinded) {
unbindService(mConn);
mDemoService = null;
mIsBinded = false;
}
}
});
}
}
代码中对一些关键和容易忽略的地方做了注释,可以结合起来进行理解。
End 运行
运行的时候,需要先启动Service端进程,才能在Client端中点击“绑定”的时候绑定成功。完成一次“绑定”和“解绑”,得到的log如下所示:
01-08 15:29:43.109 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate]
01-08 15:29:43.110 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onBind]
01-08 15:29:43.113 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]componentName=ComponentInfo{
com.songwei.aidldemoserver/com.songwei.aidldemoserver.AidlService}
01-08 15:29:43.114 13532-13547/com.songwei.aidldemoserver I/aidlDemo: server:[setName]
01-08 15:29:43.114 13532-13546/com.songwei.aidldemoserver I/aidlDemo: server:[getName]
01-08 15:29:43.114 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song
01-08 15:36:07.570 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceDisconnected]
可以结合前面的 ClientActivity.java 和 AidlService.java 代码中的添加的log,来理解一下这个流程。当然,最好是能够按照上面的步骤,亲自动手实现一遍,比看10遍更有效果。
参考文章:
文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态
文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境
文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn
文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker
文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机
文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk
文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入
文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。 Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。
文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动
文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计
文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;gt;Jni-&amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图
文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法