四大组件 - ContentProvider

news2024/11/25 12:59:41

参考:Android 这 13 道 ContentProvider 面试题,你都会了吗?

参考:《Android 开发艺术探索》 第 9.5 节(ContentProvider 的工作过程)

参考:内容提供者程序

参考:<provider>>

1. ContentProvider 概述

ContentProvider 是一种内容共享型组件,实现了不同 App 进程之间的数据共享。

Messenger 一样,基于 ContentProvider 的进程间通信的底层实现同样也是 Binder

ContentProvider 所在的进程启动时,ContentProvider 组件会同时启动并被发布到 AMS 进程中。

1.1 ContentProvider 组件的优势

相比于直接访问数据库,ContentProvider 具有如下优势:

  1. ContentProvider 对外提供统一的 CRUD 接口,使得外界可以按照统一的方式访问不同来源提供的数据,而不用关心这些数据是怎么来的。(数据可以来源于数据库、xml 文件、网络请求等)

  2. ContentProvider 提供一种可跨进程的数据共享方式。

  3. ContentProvider 提供了数据更新时的通知机制。即:当 ContentProvider 所在进程中的数据更新时,访问这些数据的其他进程可以通过 ContentProvider 收到相关的通知。

1.2 ContentProvider & ContentResolver

ContentProvider 组件主要是用于进程间的数据共享的。其中:

  1. 提供数据的 App 进程中通过 ContentProvider 向其他 App 进程提供访问数据的 API 方法;

  2. 访问数据的 App 进程中通过 ContentResolver 访问提供数据的 App 进程中的 ContentProvider

1.3 ContentProvider 对外提供的数据形式 & 内部的数据存储方式

1.3.1 ContentProvider 以表格的形式对外提供数据

ContentProvider 主要以表格的形式来组织数据,并且可以包含多个表,这点和数据库很类似。

ContentProvider 可以对外提供文件数据,通过将文件的句柄保存在表格中提供给外界。从而让外界可以根据查询到的文件句柄来访问 ContentProvider 所提供的文件。

Android 系统所提供的 MediaStore 功能就是包含文件类型数据的 ContentProvider,详细实现可以参考 MediaStore

1.3.2 ContentProvider 对内部的数据存储方式没有任何要求

虽然 ContentProvider 对外提供的数据看起来像是来自数据库中的数据,但是 ContentProvider 对内部的数据存储方式没有任何要求:我们既可以使用 SQLite 数据库;也可以使用普通的文件;甚至可以采用内存中的一个对象来进行数据的存储。

也就是说,ContentProvider 对外提供的数据,可以是数据库数据、文件数据、内存数据等。

2. ContentProvider 的使用方式

2.1 自定义 ContentProvider 的子类

/* ContentProvider.java */
/*
    onCreate 代表 ContentProvider 的创建,
    一般来说我们需要在 onCreate 中做一些初始化工作,如获取数据库实例。
    注意:
        1. onCreate 方法在主线程中执行,不允许执行耗时操作,否则会导致 App 进程的启动时间延长。
        2. ContentProvider 的 onCreate 方法的调用时机先于 Application 的 onCreate 方法。
*/
public abstract boolean onCreate();

// 返回一个 Uri 请求所对应的 MIME 类型(媒体类型)。比如:图片("image/png")、视频("video/mp4")等。
// 如果 ContentProvider 提供的数据不需要配置 MIME 类型,那么可以返回 null 或者 "*/*" 
public abstract String getType(Uri uri);

public abstract Uri insert(Uri uri, ContentValues values);

public abstract int delete(Uri uri, String selection, String[] selectionArgs);

public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);

public abstract Cursor query(Uri uri, String[] projection,
            String selection, String[] selectionArgs,
            String sortOrder);

自定义 ContentProvider 子类时需要重写如上的抽象方法,其中 insert/delete/update/query 方法对应于 CRUD 操作,即实现对数据表的增删改查功能。

2.2 注册 ContentProvider 子类 & 相关属性解读

<provider android:authorities="list"
          android:directBootAware="true|false"
          android:enabled="true|false"
          android:exported="true|false"
          android:grantUriPermissions="true|false"
          android:icon="drawable resource"
          android:initOrder="integer"
          android:label="string resource"
          android:multiprocess="true|false"
          android:name="string"
          android:permission="string"
          android:process="string"
          android:readPermission="string"
          android:syncable="true|false"
          android:writePermission="string">

    <grant-uri-permission android:path="string"
                      android:pathPattern="string"
                      android:pathPrefix="string" />

</provider>

其中:

  1. <provider>

    包含于 <application> 标签中,用来注册自定义的 ContentProvider。
    
  2. android:authorities

    是 ContentProvider 的唯一标识,提供给外界用来访问当前注册的 ContentProvider。
    建议命名时加上包名前缀。
    当声明了多个唯一标识时,用分号 ";" 分隔开
    
  3. android:directBootAware

    ContentProvider 是否可感知直接启动(direct-boot);即,它是否可以在用户解锁设备之前运行。
    
  4. android:enabled

    系统是否可以实例化 ContentProvider。true 可以;false 不可以。默认 true。
    
  5. android:permission

    其他 App 需要申请该属性指定的权限后才能读写 ContentProvider 提供的数据
    
  6. android:readPermission

    其他 App 需要申请该属性指定的权限后才能读 ContentProvider 提供的数据
    当 permission 和 readPermission 同时存在时,读权限取决于 readPermission;写权限取决于 permission
    
  7. android:writePermission

    其他 App 需要申请该属性指定的权限后才能写 ContentProvider 提供的数据
    当 permission 和 writePermission 同时存在时,写权限取决于 writePermission;读权限取决于 permission
    
  8. android:exported

    true 表示允许其他 App 在申请了 permission/readPermission/writePermission 指定权限的情况下访问 ContentProvider
    
    false 表示不允许其他 App 访问 ContentProvider。只能在如下情况下访问 ContentProvider:
        1. 同一个 App 进程中;
        2. 其他具有相同 userId 的进程中;
        3. 注册 ContentProvider 时设置了属性 android:grantUriPermissions = true,
            使得其他 App 进程可以获取一次性访问 ContentProvider 的临时权限。
    
  9. android:grantUriPermissions

    true 表示允许其他 App 获取一次性访问此 ContentProvider 所提供的所有数据的临时权限。
        所谓临时权限,就如打开刚安装的 App 时,会弹框向用户询问是否允许某个权限,并为用户提供了几种选项:
                     1. 始终允许;2. 使用期间允许;3. 仅本次使用时允许;3. 禁止。(不同手机可能存在区别)
            而临时权限就相当于选择了 "2. 仅本次使用时允许",即在本次访问 ContentProvider 之后,就不再拥有权限了,
            当下次再访问时,仍然会询问用户是否允许权限。即:临时权限就是允许用户一次性地访问。
    
    false 不允许其他 App  获取一次性访问此 ContentProvider 所提供的所有数据的临时权限。(默认值)
        但是,此情况下,我们还可以通过 <provider> 下的子标签 <grant-uri-permission> 声明
        指定路径下的数据是可以让其他 App 在临时权限下一次性访问的。
    
  10. <grant-uri-permission>

    在 android:grantUriPermissions = false 的情况下,
    可以通过此标签来声明指定路径下的某些或某一数据仍然可以让其他 App 在临时权限下一次性访问的。
    此标签下的三个属性都是用来指定数据的路径的,但一个 <grant-uri-permission> 标签只能指定一个路径,
    也就是说,一个标签中只能使用以下三个属性中的一个:
    1. android:path:某一数据的完整路径
    2. android:pathPrefix:某些数据的公共路径
    3. pathPattern:包含通配符的完整路径
    

2.3 其他进程中通过 ContentResolver 来访问 ContentProvider

/* Context.java */
public abstract ContentResolver getContentResolver();

ContentResolver 提供的用于访问 ContentProvider 的方法,与 ContentProvider 子类重写的方法基本一样。事实上,ContentResolver 内部就是通过基于 binder 机制的跨进程通信来调用 ContentProvider 对应的方法的。

/* ContentResolver.java */
String getType(Uri url)

Uri insert(Uri url, ContentValues values)

int delete(Uri url, String where, String[] selectionArgs)

int update(Uri uri, ContentValues values, String where, String[] selectionArgs)

Cursor query( // query(uri, projection, selection, selectionArgs, sortOrder, null);
        Uri uri, 
        String[] projection, 
        String selection, String[] selectionArgs, 
        String sortOrder)

Cursor query(
        Uri uri, 
        String[] projection, 
        String selection, String[] selectionArgs, 
        String sortOrder, 
        CancellationSignal cancellationSignal) {

    Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
    return query(uri, projection, queryArgs, cancellationSignal);
}

Cursor query(
        Uri uri, 
        String[] projection, 
        Bundle queryArgs, 
        CancellationSignal cancellationSignal)

2.4 ContentProvider 的数据更新通知机制

2.4.1 在其他 App 中通过 ContentResolver 监听数据的更新
/* ContentResolver.java */
/*
    当参数 Uri 标识的 ContentProvider 中的与 Uri 的 path 部分匹配的数据更新时,
    参数 observer 的 onChange 方法就会回调。

    参数 notifyForDescendants 为 false 表示监听与 Uri 的 path 路径完全匹配的数据的更新,
        或者与 path 路径的父路径匹配的数据的更新。
    参数 notifyForDescendants 为 true 表示还会监听与 Uri 的 path 下的子路径匹配的数据的更新。
*/
void registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer)

void unregisterContentObserver(ContentObserver observer)

ContentProvider 中的数据发生变更时(如调用了 ContentProviderinsert/delete/update),需要在 ContentProvider 中调用 ContentResolvernotifyChange 方法。于是,其他 App 中通过 ContentResolverregisterContentObserver 方法注册的 ContentObserver 才会收到通知(即才会回调 onChange 方法)。

/* ContentObserver.java */
/*
    回调方法 onChange 会在 Handler 线程中执行,
    如果传入 null,不指定 Handler,那么回调方法 onChange 会在 Binder 线程中执行。
*/
public ContentObserver(Handler handler)

/*
    当在 ContentProvider 中调用 ContentResolver.nofityChange(Uri, null) 方法后,
    其他 App 中的注册了的对 Uri 进行监听的 ContentObserver 的 onChange 方法就会回调。

    ContentObserver 提供了如下三个重载的 onChange 方法:
    1. 首先回调的是 onChange(selfChange, uri, userId) 
    2. 在 onChange(selfChange, uri, userId) 中只是调用了 onChange(selfChange, uri) 
    3. 在 onChange(selfChange, uri) 中只是调用了 onChange(selfChange) 
    需要用到哪些参数,就重写带哪些参数的 onChange 方法。
*/

public void onChange(boolean selfChange, Uri uri, int userId)

public void onChange(boolean selfChange, Uri uri)

public void onChange(boolean selfChange)
2.4.2 在 ContentProvider 中通过 ContentResolver 发送数据更新的通知

在调用 ContentProviderupdateinsertdelete 方法后,一般会引起数据源的改变。
此时,可以在 ContentProvider 中调用 ContentResolvernotifyChange 方法向其他 App 进程发出数据更新的通知。

可以通过 ContentProvider 组件中的上下文 Context 对象获取 ContentResolver 实例。

即在 ContentProvider 中调用 getContext().getContentResolver() 方法获取 ContentResolver 实例。

/* ContentResolver.java */
/*
    此方法一般在 ContentProvider 中的 insert、update、delete 方法内操作完数据后调用,
    用于向其他 App 进程发出数据更新的通知。
	
    参数 observer 一般传 null,表示其他 App 进程中只要调用 ContentResolver.registerContentObserver 方法,
    注册了的监听此 Uri 的 ContentObserver 都会收到通知,即都会回调 onChange 方法。
*/
public void notifyChange(Uri uri, ContentObserver observer)

3. ContentProviderUri 结构 & 通过 UriMatcher 管理多个 path

3.1 Uri 结构 & 操作 UriAPI 方法

通常的 Uri 的结构如下所示:

在这里插入图片描述

对于唯一标识 ContentProviderUri,由上图中的三部分组成:

  1. scheme

    ContentProvider 的 Uri 的 scheme 固定为 content。即:ContentProvider 的 Uri 的开头部分固定为 "content://"
    
  2. authority

    ContentProvider 的 Uri 的 authority 在注册 ContentProvider 时通过属性 android:authorities 声明
    
  3. path

    ContentProvider 的 Uri 的 path 部分,用来指定所要访问的数据具体是哪些数据。
    
    path 部分可以指定访问一张表中的所有数据,如访问 user 表的 Uri 为:
        "content://authority/user" 
    
    path 部分也可以指定访问一张表中的一条数据,如访问 user 表中 id 为 1 的那条数据的 Uri 为:
        "content://authority/user/1" 
    
    总之,path 部分是完全自定义的,从 ContentResolver 传过来的 Uri 中将 path 解析出来后,
    与我们在 ContentProvider 中已自定义好的 path 常量进行匹配,即可知道外界需要访问的是什么数据。
    

Uri.java 提供了如下的 API 来对 Uri 进行操作:

/* Uri.java */
/*
    将符合 Uri 结构的字符串转换成 Uri 对象
*/
public static Uri parse(String uriString)

/*
    返回表示一个文件的 Uri 对象
*/
public static Uri fromFile(File file)

/* 
    获取 Uri 中的 scheme 部分
*/
public abstract String getScheme();

/* 
    获取 Uri 中的 authority 部分
*/
public abstract String getAuthority();

/* 
    获取 Uri 中的 path 部分
*/
public abstract String getPath();

3.2 通过 UriMatcher 管理多个 path

UriMatcher 的作用就是对 Uri 中的 path 部分进行标识,用标识码(code)来表示不同的 path

使用 UriMatcher 可以简化对 Uri 的解析,直接将 Uri 传给 UriMatcher 就能获取到对应不同 path 的标识码(code)。

于是,根据自定义的标识码(code)在 ContentProvider 内部查询对应的数据即可。

UriMatcher.java 提供的 API 方法如下:

/* UriMatcher.java */
/*
    传入 UriMatcher.NO_MATCH 即可。(NO_MATCH = -1)
    参数 code 表示当调用 match(Uri) 方法时,如果 Uri 中不存在 authority 和 path 部分,则返回此 code
*/
public UriMatcher(int code)

/*
    将参数 authority 和 path 注册到 UriMatcher 对象中,并用参数 code 标记这条注册记录。

    当调用 match(Uri) 方法时,
        如果传入的 Uri 的 authority 和 path 与已注册的 authority 和 path 匹配,则返回匹配的注册记录的标记 code
        如果传入的 Uri 的 authority 和 path 不与任何一条注册记录匹配,则返回 -1(即返回 NO_MATCH)
*/
public void addURI(String authority, String path, int code)

public int match(Uri uri)

4. ContentProvider 的生命周期方法:onCreate

ContentProvider 只有一个 onCreate() 生命周期方法。

ContentProvider.onCreate 方法会在 ContentProvider 所在的进程启动时调用一次。且调用时机优先于 Application.onCreate 方法。

注意:ContentProvideronCreate 方法在整个进程的生命周期中只会被回调一次。

5. ContentProvider 的工作线程

5.1 onCreate 方法在主线程中执行

ContentProvideronCreate 在主线程中执行,且优先于 ApplicationonCreate 方法执行,所以在 ContentProvideronCreate 方法中不能做耗时操作,否则会使 App 的启动变慢。

5.2 query/update/insert/delete/getTypeBinder 线程中执行

由于 query/update/insert/delete/getType 是在 Binder 线程中执行的,所以调用这些方法并不会阻塞 ContentProvider 所在进程的主线程。

但是,如果其他 App 进程中通过 ContentResolverquery/update/insert/delete/getType 方法访问 ContentProvider 时,是在主线程中执行的,那么其他 App 进程的主线程就可能会阻塞。

因此,其他 App 进程中通过 ContentResolver 访问 ContentProvider 时仍然需要在子线程中进行访问。

也就是说,使用 ContentResolver 发起的 query/update/insert/delete/getType 这些 binder 调用,是会阻塞线程的。

5.3 ContentProvider 的线程安全问题

queryupdateinsertdelete 这些方法是在 Binder 线程池中提供的 Binder 线程中执行的,因此会存在多线程并发访问的情况,所以这些方法内部需要做好线程同步。

ContentProvider 中的数据都保存在一个 SQLiteDatabase 中时,因为 SQLiteDatabase 内部对数据库的操作是有同步处理的,所以可以正确应对多线程的情况。

但是如果通过多个 SQLiteDatabase 对象来操作数据库就无法保证线程同步,因为 SQLiteDatabase 对象之间无法进行线程同步。

如果 ContentProvider 中的数据是保存在内存中的,如保存在同一个 List 容器中,那么这种情况下对 List 容器的遍历、插入、删除操作就需要进行线程同步,否则就会引起多线程并发问题。

6. 其他 API

6.1 ContentValues

6.2 Cursor

7. 示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1426549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Request Response 基础篇

Request & Response 在之前的博客中&#xff0c;初最初见到Request和Response对象&#xff0c;是在Servlet的Service方法的参数中&#xff0c;之前隐性地介绍过Request的作用是获取请求数据。通过获取的数据来进行进一步的逻辑处理&#xff0c;然后通过对Response来进行数…

隧道穿透:内网探测协议出网

目录 1、TCP/UDP 使用nc探测 使用Telnet探测 使用UDP探测 2、HTTP/HTTPS Windows &#xff08;1&#xff09;Bitsadmin &#xff08;2&#xff09;Certuil Linux &#xff08;1&#xff09;Curl &#xff08;2&#xff09;Wget 3、ICMP Windows &#xff08;1&am…

认识 SYN Flood 攻击

文章目录 1.什么是 SYN Flood 攻击&#xff1f;2.半连接与全连接队列3.如何防范 SYN Flood 攻击&#xff1f;参考文献 1.什么是 SYN Flood 攻击&#xff1f; SYN Flood 是互联网上最原始、最经典的 DDoS&#xff08;Distributed Denial of Service&#xff09;攻击之一。 SYN…

知识图谱嵌入学习在推理方法中的应用与挑战

目录 前言1 关系推理的嵌入模型1.1 嵌入模型介绍1.2 模型的差异1.3 嵌入模型的发展趋势 2 符号推理与向量推理3 嵌入模型的多样性4 强化学习与挑战5 元关系学习结论 前言 在人工智能领域&#xff0c;推理一直是关键任务之一。然而&#xff0c;传统的符号推理受限于人工定义&am…

创建与删除数据库(四)

创建与删除数据库&#xff08;四&#xff09; 一、创建数据库 1.1 使用DDL语句创建数据库 CREATE DATABASE 数据库名 DEFAULT CHARACTER 示例&#xff1a; 创建一个test 的数据库&#xff0c;并查看该数据库&#xff0c;以及该数据库的编码。 创建数据库&#xff1a; cre…

Unity中开发程序打包发布

添加ESC脚本 使用Unity打包发布的过程中&#xff0c;考虑到打开的程序会处于全屏界面&#xff0c;而此时我们又会有退出全屏的需求&#xff0c;因此需要添加ESC脚本&#xff0c;当我们单击ESC脚本的过程中&#xff0c;退出全屏模式。 在Assets/Scenes下&#xff0c;创建esc.cs…

深度学习的新前沿:突破、应用与挑战

引言 深度学习的快速发展已经在人工智能领域引起了革命性的变化。作为模仿人脑结构和功能的强大工具&#xff0c;深度神经网络在图像识别、自然语言处理、医学诊断等多个领域取得了显著成就。但是&#xff0c;随着技术的不断推进&#xff0c;深度学习也在不断地进化和扩展其能…

【python】OpenCV—Tracking(10.1)

学习来自《Learning OpenCV 3 Computer Vision with Python》Second Edition by Joe Minichino and Joseph Howse 文章目录 检测移动的目标涉及到的 opencv 库cv2.GaussianBlurcv2.absdiffcv2.thresholdcv2.dilatecv2.getStructuringElementcv2.findContourscv2.contourAreacv2…

Jmeter性能测试: Jmeter 5.6.3 分布式部署

目录 一、实验 1.环境 2.jmeter 配置 slave 代理压测机 3.jmeter配置master控制器压测机 4.启动slave从节点检查 5.启动master主节点检查 6.运行jmeter 7.观察jmeter-server主从节点变化 二、问题 1.jmeter 中间请求和响应乱码 一、实验 1.环境 &#xff08;1&#…

探索智慧文旅:科技如何提升游客体验

随着科技的迅猛发展&#xff0c;智慧文旅已成为旅游业的重要发展方向。通过运用先进的信息技术&#xff0c;智慧文旅不仅改变了传统旅游业的运营模式&#xff0c;更在提升游客体验方面取得了显著成效。本文将深入探讨科技如何助力智慧文旅提升游客体验。 一、智慧文旅的兴起与…

Android SELinux:保护您的移动设备安全的关键

Android SELinux&#xff1a;保护您的移动设备安全的关键 1 引言 移动设备在我们的生活中扮演着越来越重要的角色&#xff0c;我们几乎把所有重要的信息都存储在这些设备上。然而&#xff0c;随着移动应用程序的数量不断增加&#xff0c;安全性也变得越来越关键。这就是为什么…

基于Go-Kit的Golang整洁架构实践

如何用Golang实现简洁架构&#xff1f;本文介绍了基于Go-Kit实现简洁架构的尝试&#xff0c;通过示例介绍了简洁架构的具体实现。原文: Why is Go-Kit Perfect For Clean Architecture in Golang? 简介 Go是整洁架构(Clean Architecture)的完美选择。整洁架构本身只是一种方法…

RocketMQ—RocketMQ快速入门

RocketMQ—RocketMQ快速入门 RocketMQ提供了发送多种发送消息的模式&#xff0c;例如同步消息&#xff0c;异步消息&#xff0c;顺序消息&#xff0c;延迟消息&#xff0c;事务消息等。 消息发送和监听的流程 消息生产者 创建消息生产者producer&#xff0c;并制定生产者组…

Pymysql将爬取到的信息存储到数据库中

爬取平台为电影天堂 获取到的数据仅为测试学习而用 爬取内容为电影名和电影的下载地址 创建表时需要建立三个字段即可 import urllib.request import re import pymysqldef film_exists(film_name, film_link):"""判断插入的数据是否已经存在""&qu…

Hadoop3.x基础(3)- MapReduce

来源: B站尚硅谷 目录 MapReduce概述MapReduce定义MapReduce优缺点优点缺点 MapReduce核心思想MapReduce进程常用数据序列化类型MapReduce编程规范WordCount案例实操本地测试提交到集群测试 Hadoop序列化序列化概述自定义bean对象实现序列化接口&#xff08;Writable&#xff…

C++ 之LeetCode刷题记录(二十五)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 目标&#xff1a;执行用时击败90%以上使用 C 的用户。 121. 买卖股票的最佳时机 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[…

数学建模比赛中,chatgpt、文心一言如何提问更有效?

在数学建模的战场上&#xff0c;ChatGPT和文心一言可是我们的“神助攻”&#xff01;但想让它们发挥出最大威力&#xff0c;提问可是个技术活。问题问得好&#xff0c;答案跑不了&#xff1b;问题问得妙&#xff0c;解决没烦恼&#xff01; 下面我就一个实际的对话为例&#x…

2024美赛数学建模E题思路分析 - 财产保险的可持续性

1 赛题 问题E&#xff1a;财产保险的可持续性 极端天气事件正成为财产所有者和保险公司面临的危机。“近年来&#xff0c;世界已经遭受了1000多起极端天气事件造成的超过1万亿美元的损失”。[1]2022年&#xff0c;保险业的自然灾害索赔人数“比30年的平均水平增加了115%”。[…

AI 原生时代的云计算

本文整理自2023年 12 月 20 日举办的「2023 百度云智大会智算大会」主论坛&#xff0c;百度副总裁谢广军的主题演讲《AI 原生时代的云计算》。 &#xff08;视频回放链接&#xff1a;https://cloud.baidu.com/summit/aicomputing_2023/index.html&#xff09; 大模型的到来&…

elk之基本crud

写在前面 本文看下工作中用的最多的CRUD。让我们一起来做一个帅帅的CRUD BOY吧&#xff01;&#xff01;&#xff01; 1&#xff1a;基本操作 Create 格式1(指定ID)&#xff1a;PUT 索引名称/_create/文档ID {文档json} 格式2&#xff08;不指定ID&#xff09;:POST 索引名称…