当前位置: 首页 > news >正文

进程间通信-Binder

Binder

  • Binder框架概述
    • 服务端
    • Binder驱动
    • 客户端
  • 设计服务端和客户端
    • 设计服务端
    • 客户端设计
  • Binder与Service
    • Service
    • AIDL 保证包裹内参数顺序
      • IMusicPlayerService
      • Proxy
      • Stub
  • 系统服务中的Binder对象
    • ServiceManger管理的服务
    • 理解Manger
    • 功能快捷键
    • 合理的创建标题,有助于目录的生成
    • 如何改变文本的样式
    • 插入链接与图片
    • 如何插入一段漂亮的代码片
    • 生成一个适合你的列表
    • 创建一个表格
      • 设定内容居中、居左、居右
      • SmartyPants
    • 创建一个自定义列表
    • 如何创建一个注脚
    • 注释也是必不可少的
    • KaTeX数学公式
    • 新的甘特图功能,丰富你的文章
    • UML 图表
    • FLowchart流程图
    • 导出与导入
      • 导出
      • 导入

Binder框架概述

Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块,如图所示。
在这里插入图片描述

服务端

首先来看服务端。一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程

该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务函数。因此,要实现一个Binder服务,就必须重载onTransact()方法。

重载onTransact()函数的主要内容是把onTransact()函数的参数转换为服务函数的参数,而onTransact()函数的参数来源是客户端调用transact()函数时输入的,因此,如果transact()有固定格式的输入,那么onTransact()就会有固定格式的输出。

Binder驱动

任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。

客户端

客户端要想访问远程服务,必须获取远程服务在Binder驱动中对应的mRemote引用。获得该mRemote对象后,就可以调用其transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容主要包括以下几项。

  • 以线程间消息通信的模式,向服务端发送客户端传递过来的参数。

  • 挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知(notify)。

  • 接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。

从这里可以看出,对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的Binder,而事实上则是通过Binder驱动进行了中转。即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,所不同的是Binder驱动中的对象不会再额外产生一个线程。

客户端如何获取到Binder驱动中对应的mRemote引用?
Binder驱动如何发送消息到服务端?

设计服务端和客户端

设计服务端

服务端是一个Binder类对象,只要基于Binder类新建一个Server类即可。以下以设计一个MusicPlayerService类为例。

假设该Service仅提供两个方法:start(String filePath)和stop(),那么该类的代码可以如下:

public class MusicPlayerService extends Binder {@Overrideprotected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {switch (code) {case 1000:data.enforceInterface("MusicPlayerService");String filePath = data.readString();start(filePath);// replay.writeXXX();break;}return super.onTransact(code, data, reply, flags);}public void start(String filePath) {}public void stop() {}
}

code变量是客户端和服务端约定好的,用于标识客户端期望调用服务端的哪个函数,

这里假定1000是双方约定要调用start()函数的值。

enforceInterface()是为了某种校验,它与客户端的writeInterfaceToken()对应。

readString()用于从包裹中取出一个字符串。取出filePath变量后,就可以调用服务端的start()函数了。

如果该客户端期望服务端返回一些结果,则可以在返回包裹reply中调用Parcel提供的相关函数写入相应的结果。

当要启动该服务时,只需要初始化一个MusicPlayerService对象即可。比如可以在主Activity里面初始化一个MusicPlayerService,然后运行,此时可以发现多运行了一个线程,Binder-Thread3。

如果不创建MusicPlayerService,则只有2个Binder对象对应的线程。即Binder-Thread1 和 Binder-Thread2。

这两个进程是怎么来的?

客户端设计

要想使用服务端,首先要获取服务端在Binder驱动中对应的mRemote变量的引用。获得该变量的引用后,就可以调用该变量的transact()方法。该方法的函数原型如下:

public final boolean transact(int code, Parcel data, Parcel reply,int flags) 

其中data表示的是要传递给远程Binder服务的包裹(Parcel),远程服务函数所需要的参数必须放入这个包裹中。包裹中只能放入特定类型的变量,这些类型包括常用的原子类型,比如String、int、long等。除了一般的原子变量外,Parcel还提供了一个writeParcel()方法,可以在包裹中包含一个小包裹。因此,要进行Binder远程服务调用时,服务函数的参数要么是一个原子类,要么必须继承于Parcel类,否则,是不能传递的。

因此,对于MusicPlayerService的客户端而言,可以如下调用transact()方法。

        IBinder mRemote = null;String filePath = "/sdcard/music/heal_the_world.mp3";int code = 1000;Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();data.writeInterfaceToken("MusicPlayerService");data.writeString(filePath);mRemote.transact(code, data, reply, 0);IBinder binder = reply.readStrongBinder();reply.recycle();data.recycle();

首先,包裹不是客户端自己创建的,而是调用Parcel.obtain()申请的。
data和reply变量都由客户端提供,reply变量用户服务端把返回的结果放入其中。

writeInterfaceToken()方法标注远程服务名称,理论上讲,这个名称不是必需的,因为客户端既然已经获取指定远程服务的Binder引用,那么就不会调用到其他远程服务。该名称将作为Binder驱动确保客户端的确想调用指定的服务端。

writeString()方法用于向包裹中添加一个String变量。注意,包裹中添加的内容是有序的,这个顺序必须是客户端和服务端事先约定好的,在服务端的onTransact()方法中会按照约定的顺序取出变量。

接着调用transact()方法。调用该方法后,

客户端线程进入Binder驱动,Binder驱动就会挂起当前线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹。服务端拿到包裹后,会对包裹进行拆解,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的reply包裹中。然后服务端向Binder驱动发送一个notify的消息,从而使得客户端线程从Binder驱动代码区返回到客户端代码区。

transact()的最后一个参数的含义是执行IPC调用的模式,分为两种:一种是双向,用常量0表示,其含义是服务端执行完指定服务后会返回一定的数据;另一种是单向,用常量1表示,其含义是不返回任何数据。

最后,客户端就可以从reply中解析返回的数据了,同样,返回包裹中包含的数据也必须是有序的,而且这个顺序也必须是服务端和客户端事先约定好的。

Binder与Service

以上手工编写Binder服务端和客户端的过程存在两个重要问题。
第一,客户端如何获得服务端的Binder对象引用。

第二,客户端和服务端必须事先约定好两件事情:

  • 服务端函数的参数在包裹中的顺序。

  • 服务端不同函数的int型标识。也就是transact方法中的code参数的值。

Service

那么,Service类是如何解决本节开头所提出的两个重要问题的呢?

首先,AmS提供了startService()函数用于启动客户服务,而对于客户端来讲,可以使用以下两个函数来和一个服务建立连接,其原型在android.app. ContextImpl类中。

    @Overridepublic ComponentName startService(Intent service) {try {ComponentName cn = ActivityManagerNative.getDefault().startService(mMainThread.getApplicationThread(), service,service.resolveTypeIfNeeded(getContentResolver()));if (cn != null && cn.getPackageName().equals("!")) {throw new SecurityException("Not allowed to start service " + service+ " without permission " + cn.getClassName());}return cn;} catch (RemoteException e) {return null;}}

该函数用于启动intent指定的服务,而启动后,客户端暂时还没有服务端的Binder引用,因此,暂时还不能调用任何服务功能。

    @Overridepublic boolean bindService(Intent service, ServiceConnection conn,int flags) {IServiceConnection sd;if (mPackageInfo != null) {sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),mMainThread.getHandler(), flags);} else {throw new RuntimeException("Not supported in system context");}try {int res = ActivityManagerNative.getDefault().bindService(mMainThread.getApplicationThread(), getActivityToken(),service, service.resolveTypeIfNeeded(getContentResolver()),sd, flags);if (res < 0) {throw new SecurityException("Not allowed to bind to service " + service);}return res != 0;} catch (RemoteException e) {return false;}}

该函数用于绑定一个服务,这就是第一个重要问题的关键所在。其中第二个参数是一个interface类,该interface的定义如以下代码所示:

/*** Interface for monitoring the state of an application service.  See* {@link android.app.Service} and* {@link Context#bindService Context.bindService()} for more information.* <p>Like many callbacks from the system, the methods on this class are called* from the main thread of your process.*/
public interface ServiceConnection {/*** Called when a connection to the Service has been established, with* the {@link android.os.IBinder} of the communication channel to the* Service.** @param name The concrete component name of the service that has* been connected.** @param service The IBinder of the Service's communication channel,* which you can now make calls on.*/public void onServiceConnected(ComponentName name, IBinder service);public void onServiceDisconnected(ComponentName name);
}

请注意该interface中的onServiceConnected()方法的第二个变量Service。当客户端请求AmS启动某个Service后,该Service如果正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationThread对象,调用的参数中会包含Service的Binder引用,然后在ApplicationThread中会回调bindService中的conn接口。因此,在客户端中,可以在onServiceConnected()方法中将其参数Service保存为一个全局变量,从而在客户端的任何地方都可以随时调用该远程服务。这就解决了第一个重要问题,即客户端如何获取远程服务的Binder引用。

在这里插入图片描述

AIDL 保证包裹内参数顺序

关于第二个问题,Android的SDK中提供了一个aidl工具,该工具可以吧一个aidl文件转换为一个Java类文件,在该Java类文件,同时重载了transact和onTransact()方法,统一了存入包裹和读取包裹参数,从而使设计者可以把注意力放到服务代码本身上。

接下来看aidl工具都做了什么。如本章第一节示例,此处依然假设要编写一个MusicPlayerService服务,服务中包含两个服务函数,分别是start()和stop()。那么,可以首先编写一个IMusicPlayerService.aidl文件。如以下代码所示:

    package com.haiii.android.client;  interface IMusicPlayerService{  boolean start(String filePath);  void stop();  }  

该文件的名称必须遵循一定的规范,第一个字母"I"不是必需的,但是,为了程序风格的统一,"I"的含义是IInterface类,即这是一个可以提供访问远程服务的类。后面的命名–MusicPlayerService对应的是服务的类名,可以是任意的,但是,aidl工具会以该名称命名输出的Java类。

aidl文件的语法基本类似于Java,package指定输出后的Java文件对应的包名。如果该文件需要引用其他Java类,则可以使用import关键字,但需要注意的是,包裹内只能写入以下三个类型的内容:

  • Java原子类型,如int、long、String等变量。
  • Binder引用。
  • 实现了Parcelable的对象。

因此,基本上来讲,import所引用的Java类也只能是以上三个类型。

interface为关键字,有时会在interface前面加一个oneway,代表该service提供的方法都是没有返回值的,即都是void类型。

下面看看该aidl生成的IMusicPlayerService.java文件的代码。如下所示:

package com.haiii.client;public interface IMusicPlayerService extends android.os.IInterface {/*** Local-side IPC implementation stub class.*/public static abstract class Stub extends android.os.Binderimplements com.haiii.client.IMusicPlayerService {private static final java.lang.String DESCRIPTOR ="com.haiii.client.IMusicPlayerService";/*** Construct the stub at attach it to the interface.*/public Stub() {this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.haiii.client.IMusicPlayerService interface,* generating a proxy if needed.*/public static com.haiii.client.IMusicPlayerServiceasInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}android.os.IInterface iin =(android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);if (((iin != null) && (iin instanceof com.haiii.client.IMusicPlayerService))) {return ((com.haiii.client.IMusicPlayerService) iin);}return new com.haiii.client.IMusicPlayerService.Stub.Proxy(obj);}public android.os.IBinder asBinder() {return this;}@Overridepublic 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_start: {data.enforceInterface(DESCRIPTOR);java.lang.String _arg0;_arg0 = data.readString();boolean _result = this.start(_arg0);reply.writeNoException();reply.writeInt(((_result) ? (1) : (0)));return true;}case TRANSACTION_stop: {data.enforceInterface(DESCRIPTOR);this.stop();reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements com.haiii.client.IMusicPlayerService {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}public android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}public boolean start(java.lang.String filePath) throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();boolean _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(filePath);mRemote.transact(Stub.TRANSACTION_start, _data, _reply, 0);_reply.readException();_result = (0 != _reply.readInt());} finally {_reply.recycle();_data.recycle();}return _result;}public void stop() throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);_reply.readException();} finally {_reply.recycle();_data.recycle();}}}static final int TRANSACTION_start = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);}public boolean start(java.lang.String filePath) throws android.os.RemoteException;public void stop() throws android.os.RemoteException;
}  

这些代码主要完成以下三个任务。

IMusicPlayerService

定义一个Java interface,内部包含aidl文件所声明的服务函数,类名称为IMusicPlayerService,并且该类基于IInterface接口,即需要提供一个asBinder()函数。

Proxy

定义一个Proxy类,该类将作为客户端程序访问服务端的代理。所谓的代理主要就是为了前面所提到的第二个重要问题–统一包裹内写入参数的顺序。

Stub

定义一个Stub类,这是一个abstract类,基于Binder类,并且实现了IMusicPlayerService接口,主要由服务端来使用。该类之所以要定义为一个abstract类,是因为具体的服务函数必须由程序员实现,因此,IMusicPlayerService接口中定义的函数在Stub类中可以没有具体实现。同时,在Stub类中重载了onTransact()方法,由于transact()方法内部给包裹内写入参数的顺序是由aidl工具定义的,因此,在onTransact()方法中,aidl工具自然知道应该按照何种顺序从包裹中取出相应参数。

在Stub类中还定义了一些int常量,比如TRANSACTION_start,这些常量与服务函数对应,transact()和onTransact()方法的第一个参数code的值即来源于此。

在Stub类中,除了以上所述的任务外,Stub还提供了一个asInterface()函数。提供这个函数的作用是这样的:

提供这个函数的原因是服务端提供的服务除了其他进程可以使用外,在服务进程内部的其他类也可以使用该服务,对于后者,显然是不需要经过IPC调用,而可以直接在进程内部调用的,而Binder内部有一个queryLocalInterface(String description)函数,该函数是根据输入的字符串判断该Binder对象是否是一个本地的Binder引用。

在这里插入图片描述
创建服务时,服务端进程内部创建一个Binder对象,Binder驱动中也会创建一个Binder对象。如果从客户端进程获取服务端进程的Binder,则只会返回Binder驱动中的Binder对象,而如果从服务端进程内部获取Binder对象,则会获取服务端本身的Binder对象。

因此,asInterface()函数正是利用了queryLocalInterface()方法,提供了一个统一的接口。无论是远程客户端还是服务端内部进程,当获取Binder对象后,可以把获取的Binder对象作为asInterface()的参数,从而返回一个IMusicPlayerService接口,该接口要么使用Proxy类,要么直接使用Stub所实现的相应服务函数。

系统服务中的Binder对象

在应用程序中,经常使用getSystemService(String serviceName)方法获取一个系统服务,那么,这些系统服务的Binder引用是如何传递给客户端的呢?

须知系统服务并不是通过startService()启动的。getSystemService()函数的实现是在ContextImpl类中,该函数所返回的Service比较多,具体可参照源码。这些Service一般都由ServiceManager管理。

ServiceManger管理的服务

ServiceManager是一个独立进程,其作用如名称所示,管理各种系统服务,管理的逻辑如下:
在这里插入图片描述
ServiceManager本身也是一个Service,Framework提供了一个系统函数,可以获取该Service对应的Binder引用,那就是BinderInternal.getContextObject()。

该静态函数返回ServiceManager后,就可以通过ServiceManager提供的方法获取其他系统Service的Binder引用。

其他系统服务在启动时,首先把自己的Binder对象传递给ServiceManager,即所谓的注册(addService)。

下面查看获取一个Service{IMPUT_METHOD_SERVICE}:

if (INPUT_METHOD_SERVICE.equals(name)) {return InputMethodManager.getInstance(this);
static public InputMethodManager getInstance(Looper mainLooper) {synchronized (mInstanceSync) {if (mInstance != null) {return mInstance;}IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);mInstance = new InputMethodManager(service, mainLooper);}return mInstance;}

即通过ServiceManager获取InputMethod Service对应的Binder对象b,然后再将该Binder对象作为IInputMethodManager.Stub.asInterface()的参数,返回一个IInputMethodManager的统一接口。

ServiceManager.getService()的代码如下:

public static IBinder getService(String name) {try {IBinder service = sCache.get(name);if (service != null) {return service;} else {return getIServiceManager().getService(name);}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;}

即首先从sCache 缓存中查看是否有对应的Binder 对象,有则返回,没有则调用getIServiceManager().getService(name),函数getIServiceManager()即用于返回系统中唯一的ServiceManager对应的Binder,其代码如下:

private static IServiceManager getIServiceManager() {if (sServiceManager != null) {return sServiceManager;}// Find the service managersServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());return sServiceManager;}

BinderInternal.getContextObject()静态函数即用于返回ServiceManager对应的全局Binder对象,该函数不需要任何参数,因为它的作用是固定的。其他所有通过ServiceManager获取的系统服务的过程与以上基本类似,所不同的就是传递给ServiceManager的服务名称不同,因为ServiceManager正是按照服务的名称(String类型)来保存不同的Binder对象的。

使用addService()向ServiceManager中添加一个服务一般是在SystemService进程启动时完成的。

理解Manger

ServiceManager所管理的所有Service都是以相应的Manager返回给客户端,因此,这里简述一下Framework中关于Manager的语义。

在Android中,Manager的含义更应该翻译为经纪人,Manager所manage的对象是服务本身,因为每个具体的服务一般都会提供多个API接口 ,而Manager所manage的正是这些API。

客户端一般不能直接通过Binder引用去访问具体的服务,它需要先通过ServiceManager获取远程服务的Binder引用,然后使用这个Binder引用构造一个客户端本地可以访问的经纪人,比如前面的IInputMethodManager,然后客户端就可以通过该经纪人访问远程的服务。

通过本地Manger访问远程服务的模型图如下:
在这里插入图片描述

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

相关文章:

进程间通信-Binder

Binder Binder框架概述服务端Binder驱动客户端 设计服务端和客户端设计服务端客户端设计 Binder与ServiceServiceAIDL 保证包裹内参数顺序IMusicPlayerServiceProxyStub 系统服务中的Binder对象ServiceManger管理的服务理解Manger功能快捷键合理的创建标题&#xff0c;有助于目…...

一个简单的vim例子

一.欢迎来到我的酒馆 在本章节介绍vim工具。 目录 一.欢迎来到我的酒馆二.什么是vim三.开始使用vim 二.什么是vim 2.1什么是vim vim是一种Linux命令行类型的文本编辑器。vim指的是"vi improved"&#xff0c;意思是vi工具的升级版。vim是基于vi实现的&#x…...

sql server 备份到网络共享

场景&#xff1a;sql server服务器A将数据库备份文件备份到服务器B 1&#xff09;服务器B创建共享目录 这里我将 D:\ProDbBak 共享&#xff0c;并且Everyone完全控制 2&#xff09;sql server服务器A能够访问服务器B共享目录&#xff0c;并且能完全控制 3&#xff09;修改服务…...

程序与进程

一、程序是怎么被执行的 1.在程序中&#xff0c;由引导代码去调用程序中得main函数&#xff0c;而这个过程由链接器完成&#xff0c;链接器将引导代码链接到我们的应用程序构成可执行文件。 2.程序运行需要通过操作系统的加载器来实现&#xff0c;加载器是操作系统中的程序&a…...

大模型从入门到应用——LangChain:链(Chains)-[链与索引:图问答(Graph QA)和带来源的问答(QA with Sources)]

分类目录&#xff1a;《大模型从入门到应用》总目录 图问答&#xff08;Graph QA&#xff09; 创建图 在本节中&#xff0c;我们构建一个示例图。目前&#xff0c;这对于较小的文本片段效果最好&#xff0c;下面的示例中我们只使用一个小片段&#xff0c;因为提取知识三元组对…...

spark sql 数据倾斜--join 同时开窗去重的问题优化

spark sql 数据倾斜–join 同时开窗去重的问题优化 文章目录 spark sql 数据倾斜--join 同时开窗去重的问题优化结论1. 原方案&#xff1a;join步骤时&#xff0c;同时开窗去重数据倾斜 2. 优化2.1 参数调优2.2 SQL优化 背景&#xff1a; 需求&#xff1a;在一张查询日志表中&a…...

lv3 嵌入式开发-linux介绍及环境配置

目录 1 UNIX、Linux和GNU简介 2 环境介绍 3 VMwareTools配置 4 vim配置&#xff1a; 1 UNIX、Linux和GNU简介 什么是UNIX? unix是一个强大的多用户、多任务操作系统&#xff0c;支持多种处理器架构 中文名 尤尼斯 外文名 UNIX 本质 操作系统 类型 分时操作系统 开…...

RabbitMQ工作模式-路由模式

官方文档参考&#xff1a;https://www.rabbitmq.com/tutorials/tutorial-four-python.html 使用direct类型的Exchange,发N条消息并使用不同的routingKey,消费者定义队列并将队列routingKey、Exchange绑定。此时使用direct模式Exchange必须要routingKey完成匹配的情况下消息才…...

StringIO BytesIO

上一篇中我们介绍了文件的基本读写操作&#xff0c;但是很多时候数据的读写并不一定都是在文件中&#xff0c;我们也可以在内存中读写数据&#xff0c;因此引出我们今天的主要内容&#xff0c;即 StringIO 和 BytesIO&#xff0c;让你学会在内存中进行数据的基本读写操作。 1 …...

通讯录管理系统(个人学习笔记黑马学习)

1、系统需求 通讯录是一个可以记录亲人、好友信息的工具。 本教程主要利用C来实现一个通讯录管理系统系统中需要实现的功能如下: 添加联系人:向通讯录中添加新人&#xff0c;信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人显示联系人:显示通讯录中所有联系人信…...

[SpringBoot3]远程访问@HttpExchange

六、远程访问HttpExchange[SpringBoot3] 远程访问是开发的常用技术&#xff0c;一个应用能够访问其他应用的功能。SpringBoot提供了多种远程访问的技术。基于HTTP协议的远程访问是最广泛的。SpringBoot中定义接口提供HTTP服务。生成的代理对象实现此接口&#xff0c;代理对象实…...

Linux安装ntp并使用阿里云配置ntp服务器

安装 NTP 客户端&#xff1a; 打开终端&#xff0c;以 root 权限执行以下命令来安装 NTP 客户端&#xff1a; sudo zypper install ntp 编辑 NTP 配置文件&#xff1a; 使用文本编辑器打开 NTP 的配置文件 /etc/ntp.conf&#xff0c;例如使用 nano 编辑器&#xff1a; sudo v…...

js常用方法总结

1、slice 和 splice slice表示截取&#xff0c;slice(start,end)&#xff0c;不改变原数组&#xff0c;返回新数组。 splice表示删除&#xff0c;splice(start,length,item)&#xff0c;会改变原数组&#xff0c;从某个位置开始删除多个元素&#xff0c;并可以插入新的元素。…...

在PHP中安装Composer并管理Vue前端依赖包

系列文章目录 文章目录 系列文章目录前言一、安装Composer二、使用Composer管理PHP依赖包三、使用npm管理Vue前端依赖包总结 前言 在开发Web应用程序时&#xff0c;使用Composer来管理PHP的依赖包和Vue前端的依赖包是一种很常见的做法。Composer是PHP的包管理工具&#xff0c;…...

03-前端基础CSS-第一天

01-CSS层叠样式表导读 目标&#xff1a; 能够说出什么是CSS能够使用CSS基础选择器能够设置字体样式能够设置文本样式能够说出CSS的三种引入方式能够使用Chrome调试工具调试样式 目录&#xff1a; 1.CSS简介2.CSS基础选择器3.CSS字体属性4.CSS文本属性5.CSS引入方式6.综合案…...

多张图片转为pdf怎么弄?

多张图片转为pdf怎么弄&#xff1f;在网络传输过程中&#xff0c;为了避免图片格式文件出现差错&#xff0c;并确保图片的清晰度和色彩不因不同设备而有所改变&#xff0c;常见的做法是将图片转换为PDF格式。然而&#xff0c;当涉及到多张图片时&#xff0c;逐一转换将会变得相…...

jdk新版本特性

JDK8,JDK11,JDK17,JDK21及中间版本主要更新特性_jdk重要版本_ycsdn10的博客-CSDN博客 Java 20 新特性概览 | JavaGuide(Java面试 学习指南)...

进程Start

Linux中的命令解释器和Windows的程序管理器explorer.exe一样地位,都是在用户态下运行的进程 共享变量发生不同进程间的指令交错&#xff0c;就可能会数据出错 进程只作为除CPU之外系统资源的分配单位 CPU的分配单位是线程 每个进程都有自己的独立用户空间 内核空间是OS内核的…...

SpringCloud学习笔记(六)_Ribbon服务调用

Ribbon介绍 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具 Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时、重试等。简单的说&#xff0c;就是…...

系统架构设计师考试论文:论无服务器架构及其应用

近年来&#xff0c;随着信息技术的迅猛发展和应用需求的快速更迭&#xff0c;传统的多层企业应用系统架构面临越来越多的挑战&#xff0c;已经难以适应这种变化。在这一背景下&#xff0c;无服务器架构(ServliessArchitecture)逐渐流行&#xff0c;它强调业务逻辑由事件触发&am…...

linux下安装Mycat

1 官网下载mycat 官方网站&#xff1a; 上海云业网络科技有限公司http://www.mycat.org.cn/ github地址&#xff1a; MyCATApache GitHubMyCATApache has 34 repositories available. Follow their code on GitHub.https://github.com/MyCATApache 2 Mycat安装 1 把MyCat…...

OpenCV(八):图像二值化

目录 1.固定值二值化 2.自适应阈值二值化 3.Android JNI完整代码 1.固定值二值化 固定阈值二值化是OpenCV中一种简单而常用的图像处理技术&#xff0c;用于将图像转换为二值图像。在固定阈值二值化中&#xff0c;像素值根据一个预定义的阈值进行分类&#xff0c;大于阈值的…...

《Flink学习笔记》——第十一章 Flink Table API和 Flink SQL

Table API和SQL是最上层的API&#xff0c;在Flink中这两种API被集成在一起&#xff0c;SQL执行的对象也是Flink中的表&#xff08;Table&#xff09;&#xff0c;所以我们一般会认为它们是一体的。Flink是批流统一的处理框架&#xff0c;无论是批处理&#xff08;DataSet API&a…...

电脑提示缺少d3dx9_43.dll的问题及5个解决方法

大家好&#xff01;今天&#xff0c;我将和大家分享一个电脑提示缺少d3dx9_43.dll的问题及其解决方法。这个问题可能会影响到我们在使用电脑时的一些功能&#xff0c;所以掌握这个解决方法对我们来说是非常有帮助的。 首先&#xff0c;我们来了解一下什么是d3dx9_43.dll。d3dx9…...

Linux stat 命令及示例

介绍 该stat命令打印有关文件和文件系统的详细信息。该工具提供有关所有者是谁、修改日期、访问权限、大小、类型等信息。 该实用程序对于故障排除、在更改文件之前获取有关文件的信息以及例行文件和系统管理任务至关重要。 本文stat通过实际示例解释了有关 Linux 命令的所有…...

06-基础例程6

基础例程6 01、WIFI实验—WebServer 实验介绍 ​ 连接路由器上网是我们每天都做的事情&#xff0c;日常生活中只需要知道路由器的账号和密码&#xff0c;就可以使用手机或电脑连接到路由器&#xff0c;然后上网。 ​ 连接路由器&#xff0c;将ESP32的IP地址等信息通过shell…...

【附安装包】Eplan2022安装教程

软件下载 软件&#xff1a;Eplan版本&#xff1a;2022语言&#xff1a;简体中文大小&#xff1a;1.52G安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.5GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu.co…...

大数据-玩转数据-Flink窗口

一、Flink 窗口 理解 在流处理应用中&#xff0c;数据是连续不断的&#xff0c;因此我们不可能等到所有数据都到了才开始处理。当然我们可以每来一个消息就处理一次&#xff0c;但是有时我们需要做一些聚合类的处理&#xff0c;例如&#xff1a;在过去的1分钟内有多少用户点击…...

【python爬虫】—豆瓣电影Top250

豆瓣电影Top250 豆瓣榜单简介需求描述Python实现 豆瓣榜单简介 豆瓣电影 Top 250 榜单是豆瓣网站上列出的评分最高、受观众喜爱的电影作品。这个榜单包含了一系列优秀的影片&#xff0c;涵盖了各种类型、不同国家和时期的电影。 需求描述 使用python爬取top250电影&#xff…...

【跟小嘉学 Rust 编程】十五、智能指针

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...