Android 大版本升级变更截图方法总结

news2024/9/28 9:30:29

Android 大版本升级变更截图方法总结

  • 一、Android R (11) 平台
  • 二、Android S (12) 平台
  • 三、Android U (14) 平台

Android 原生的截屏功能是集成在 SystemUI 中,因此我们普通应用想要获取截图方法,就需要研读下 SystemUI 截屏部分的功能实现。

在这里插入图片描述

一、Android R (11) 平台

SystemUI中截屏时获取当前的屏幕截图方法如下

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotMoreAction.java

private Bitmap captureScreenshotBitmap() {
    mDisplay.getRealMetrics(mDisplayMetrics);
    float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
    int rot = mDisplay.getRotation();
    Rect sourceCrop = new Rect(0, 0, (int) dims[0], (int) dims[1]);
    // Take the screenshot
    Bitmap bitmap = SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot);
    Log.d(TAG, "capture screenshot bitmap");
    if (bitmap == null) {
        Log.d(TAG, "capture screenshot bitmap is null!");
    }
    return bitmap;
}

核心代码:SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot)

关于 SurfaceControl.screenshot 的具体实现查看源码如下

frameworks/base/core/java/android/view/SurfaceControl.java

/**
 * @see SurfaceControl#screenshot(Rect, int, int, boolean, int)}
 * @hide
 */
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {
    return screenshot(sourceCrop, width, height, false, rotation);

/**
 * Copy the current screen contents into a hardware bitmap and return it.
 * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap into
 * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
 *
 * CAVEAT: Versions of screenshot that return a {@link Bitmap} can be extremely slow; avoid use
 * unless absolutely necessary; prefer the versions that use a {@link Surface} such as
 * {@link SurfaceControl#screenshot(IBinder, Surface)} or {@link GraphicBuffer} such as
 * {@link SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}.
 *
 * @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}
 * @hide
 */
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height,
        boolean useIdentityTransform, int rotation) {
    // TODO: should take the display as a parameter
    final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
    if (displayToken == null) {
        Log.w(TAG, "Failed to take screenshot because internal display is disconnected");
        return null;
    }

    if (rotation == ROTATION_90 || rotation == ROTATION_270) {
        rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
    }

    SurfaceControl.rotateCropForSF(sourceCrop, rotation);
    final ScreenshotGraphicBuffer buffer = screenshotToBuffer(displayToken, sourceCrop, width,
            height, useIdentityTransform, rotation);

    if (buffer == null) {
        Log.w(TAG, "Failed to take screenshot");
        return null;
    }
    return Bitmap.wrapHardwareBuffer(buffer.getGraphicBuffer(), buffer.getColorSpace());
}

看到注解@UnsupportedAppUsage,这个注解的存在旨在提醒开发者,某些 API 或代码元素可能在未来版本中发生变化,可能会有风险或不稳定。

@UnsupportedAppUsage 注解,用于标记不建议应用程序使用的 API。它通常用于标记已被弃用或将在未来版本中删除的 API。

作为普通应用,我们需要兼容多版本,所以在使用高targetSdkVersion时,此方法在SDK中就会找不到,因此我们需要使用反射来完成。在 Android R (11) 上可使用的截图工具方法如下:

private static Bitmap screenshotR(int width, int height, Display defaultDisplay) {
    Bitmap bmp = null;
    Rect sourceCrop = new Rect(0, 0, width, height);
    try {
        Class<?> demo = Class.forName("android.view.SurfaceControl");
        Method method = demo.getMethod("screenshot", Rect.class, int.class, int.class, int.class);
        bmp = (Bitmap) method.invoke(null, sourceCrop, (int) width, (int) height,
                defaultDisplay.getRotation());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bmp;
}

二、Android S (12) 平台

Android S (12)Android T (13) 平台截图方法无变化,SystemUI 中相比较于 R 平台,代码有变化,梳理下代码找到截屏时获取当前的屏幕截图方法如下

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java

private Bitmap captureScreenshot(Rect crop) {
    int width = crop.width();
    int height = crop.height();
    Bitmap screenshot = null;
    final Display display = getDefaultDisplay();
    final DisplayAddress address = display.getAddress();
    if (!(address instanceof DisplayAddress.Physical)) {
        Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "
                + display);
    } else {
        final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
        final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(
                physicalAddress.getPhysicalDisplayId());
        final SurfaceControl.DisplayCaptureArgs captureArgs =
                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
                        .setSourceCrop(crop)
                        .setSize(width, height)
                        .build();
        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                SurfaceControl.captureDisplay(captureArgs);
        screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
    }
    return screenshot;
}

核心代码:SurfaceControl.captureDisplay(captureArgs)

关于 SurfaceControl.captureDisplay 的具体实现需要查看源码,这里多了几个新类DisplayCaptureArgsScreenshotHardwareBuffer

frameworks/base/core/java/android/view/SurfaceControl.java

/**
 * @param captureArgs Arguments about how to take the screenshot
 * @param captureListener A listener to receive the screenshot callback
 * @hide
 */
public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
        @NonNull ScreenCaptureListener captureListener) {
    return nativeCaptureDisplay(captureArgs, captureListener);
}

/**
 * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with
 * the content.
 *
 * @hide
 */
public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) {
    SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();

    int status = captureDisplay(captureArgs, screenCaptureListener);
    if (status != 0) {
        return null;
    }

    return screenCaptureListener.waitForScreenshot();
}

在 Android S 和 Android T 上可使用的截图工具方法如下:

private static Bitmap screenshotS(int width, int height, Display defaultDisplay) {
    Bitmap bmp;
    Rect sourceCrop = new Rect(0, 0, width, height);
    final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) defaultDisplay.getAddress();
    final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(
            physicalAddress.getPhysicalDisplayId());
    final SurfaceControl.DisplayCaptureArgs captureArgs =
            new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
                    .setSourceCrop(sourceCrop)
                    .setSize(width, height)
                    .build();
    final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
            SurfaceControl.captureDisplay(captureArgs);
    bmp = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
    return bmp;
}

上诉工具类中DisplayAddress.Physical和SurfaceControl.DisplayCaptureArgs为hide类,SurfaceControl.getPhysicalDisplayTokenSurfaceControl.captureDisplayhide方法,所以需要使用反射方法来实现,下述代码为反射的实现:

public static Bitmap screenshotS(int width, int height, Display defaultDisplay) {
    Bitmap bmp = null;
    Rect sourceCrop = new Rect(0, 0, width, height);
    try {
        Class<?> displayAddressClass = Class.forName("android.view.DisplayAddress$Physical");
        Object physicalAddress =  displayAddressClass.getMethod("getPhysicalDisplayId").invoke(defaultDisplay.getAddress());

        Class<?> surfaceControlClass = Class.forName("android.view.SurfaceControl");
        Method getPhysicalDisplayTokenMethod = surfaceControlClass.getMethod("getPhysicalDisplayToken", long.class);
        Object displayToken = getPhysicalDisplayTokenMethod.invoke(null,physicalAddress);

        Class<?> displayCaptureArgsBuilderClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs$Builder");
        Constructor<?> displayCaptureArgsBuilderConstructor = displayCaptureArgsBuilderClass.getDeclaredConstructor(IBinder.class);
        Object displayCaptureArgsBuilder = displayCaptureArgsBuilderConstructor.newInstance(displayToken);
        Method setSourceCropMethod = displayCaptureArgsBuilderClass.getMethod("setSourceCrop", Rect.class);
        Method setSizeMethod = displayCaptureArgsBuilderClass.getMethod("setSize", int.class, int.class);
        Method buildMethod = displayCaptureArgsBuilderClass.getMethod("build");
        setSourceCropMethod.invoke(displayCaptureArgsBuilder, sourceCrop);
        setSizeMethod.invoke(displayCaptureArgsBuilder, width, height);
        Object captureArgs = buildMethod.invoke(displayCaptureArgsBuilder);

        Method captureDisplayMethod = surfaceControlClass.getMethod("captureDisplay", captureArgs.getClass());
        Object screenshotBuffer = captureDisplayMethod.invoke(null, captureArgs);

        if (screenshotBuffer != null) {
            Class<?> screenshotHardwareBufferClass = Class.forName("android.view.SurfaceControl$ScreenshotHardwareBuffer");
            Method asBitmapMethod = screenshotHardwareBufferClass.getMethod("asBitmap");
            bmp = (Bitmap) asBitmapMethod.invoke(screenshotBuffer);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bmp;
}

三、Android U (14) 平台

Android 14 平台上的 SystemUI 中的截图方法类是使用Kotlin编写

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt

override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
    val captureArgs = CaptureArgs.Builder()
        .setSourceCrop(crop)
        .build()
    val syncScreenCapture = ScreenCapture.createSyncCaptureListener()
    windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture)
    val buffer = syncScreenCapture.getBuffer()
    return buffer?.asBitmap()
}

我这里将 Kotlin 转化为 Java 编写的代码

// 导包为隐藏方法,请使用反射重写此代码
import android.view.IWindowManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.CaptureArgs;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;

IWindowManager windowManager = IWindowManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.WINDOW_SERVICE));
CaptureArgs captureArgs = new CaptureArgs.Builder()
        .setSourceCrop(sourceCrop)
        .build();
SynchronousScreenCaptureListener syncScreenCapture = ScreenCapture.createSyncCaptureListener();
windowManager.captureDisplay(defaultDisplay.getDisplayId(), captureArgs, syncScreenCapture);
ScreenshotHardwareBuffer buffer = syncScreenCapture.getBuffer();
if (buffer != null) {
    bitmap = buffer.asBitmap();
}

核心代码:windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture)

之前的方法都封装在了SurfaceControl中,最新的U平台将逻辑挪到了IWindowManager

frameworks/base/core/java/android/view/IWindowManager.aidl

/**
  * Captures the entire display specified by the displayId using the args provided. If the args
  * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
  */
 oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
         in ScreenCapture.ScreenCaptureListener listener);

// aidl 是接口,相关实现在 WindowManagerService 中
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
        ScreenCapture.ScreenCaptureListener listener) {
    Slog.d(TAG, "captureDisplay");
    if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
        throw new SecurityException("Requires READ_FRAME_BUFFER permission");
    }

    ScreenCapture.LayerCaptureArgs layerCaptureArgs = getCaptureArgs(displayId, captureArgs);
    ScreenCapture.captureLayers(layerCaptureArgs, listener);

    if (Binder.getCallingUid() != SYSTEM_UID) {
        // Release the SurfaceControl objects only if the caller is not in system server as no
        // parcelling occurs in this case.
        layerCaptureArgs.release();
    }
}

IWindowManagerWindowManager 是 Android 系统中的两个不同的类,它们有以下区别:

  1. 接口 vs 类:IWindowManager 是一个接口,定义了窗口管理器的方法和功能,而 WindowManager 是一个具体的实现类,用于实际管理窗口的显示和操作。

  2. 系统服务 vs 上下文获取:IWindowManager 通常是通过系统服务机制获取的,可以通过 ServiceManager.getService("window") 来获取 IWindowManager 的实例。而 WindowManager 是通过上下文(Context)的 getSystemService() 方法获取的,例如 context.getSystemService(Context.WINDOW_SERVICE)

  3. 系统级权限 vs 应用级权限:IWindowManager 通常被用于系统级别的窗口管理,例如修改窗口属性、调整窗口的位置和大小等,因此访问 IWindowManager 需要特定的系统级权限。相比之下,应用程序可以通过 WindowManager 类来管理自己的窗口,但受到应用程序权限的限制。

总的来说,IWindowManager 是用于系统级窗口管理的接口,而 WindowManager 是用于应用程序级窗口管理的类。在大多数情况下,应用程序开发者更常使用 WindowManager 类来管理应用程序的窗口。

在 Android U (14) 上可使用的截图工具方法如下:

private static Bitmap screenshotU(int width, int height, Display defaultDisplay) {
    Bitmap bmp = null;
    Rect sourceCrop = new Rect(0, 0, width, height);
    try {
        IWindowManager windowManager = IWindowManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.WINDOW_SERVICE));
        Class<?> screenCaptureClass = Class.forName("android.window.ScreenCapture");
        Class<?> captureArgsClass = Class.forName("android.window.ScreenCapture$CaptureArgs");
        Class<?> captureArgsBuilderClass = Class.forName("android.window.ScreenCapture$CaptureArgs$Builder");
        Class<?> screenCaptureListenerClass = Class.forName("android.window.ScreenCapture$ScreenCaptureListener");
        Class<?> synchronousScreenCaptureListenerClass = Class.forName("android.window.ScreenCapture$SynchronousScreenCaptureListener");
        Class<?> screenshotHardwareBufferClass = Class.forName("android.window.ScreenCapture$ScreenshotHardwareBuffer");
        Method setSourceCropMethod = captureArgsBuilderClass.getDeclaredMethod("setSourceCrop", Rect.class);
        Object captureArgsBuilder = captureArgsBuilderClass.newInstance();
        setSourceCropMethod.invoke(captureArgsBuilder, sourceCrop);
        Method buildMethod = captureArgsBuilderClass.getDeclaredMethod("build");
        Object captureArgs = buildMethod.invoke(captureArgsBuilder);
        Method createSyncCaptureListenerMethod = screenCaptureClass.getMethod("createSyncCaptureListener");
        Object syncScreenCapture = createSyncCaptureListenerMethod.invoke(null);
        Method captureDisplayMethod = windowManager.getClass().getMethod("captureDisplay", int.class, captureArgsClass, screenCaptureListenerClass);
        captureDisplayMethod.invoke(windowManager, defaultDisplay.getDisplayId(), captureArgs, syncScreenCapture);
        Method getBufferMethod = synchronousScreenCaptureListenerClass.getMethod("getBuffer");
        Object buffer = getBufferMethod.invoke(syncScreenCapture);
        if (buffer != null) {
            Method asBitmapMethod = screenshotHardwareBufferClass.getMethod("asBitmap");
            bmp = (Bitmap) asBitmapMethod.invoke(buffer);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bmp;
}

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

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

相关文章

RocketMQ系统性学习-SpringCloud Alibaba集成RocketMQ以及消费收发实战

文章目录 Spring Cloud Alibaba 集成 RocketMQ 最佳实践集成依赖DashBoard消息收发实战 Spring Cloud Alibaba 集成 RocketMQ 最佳实践 SpringBoot 相对于 SSM 来说已经很大程度上简化了开发&#xff0c;但是使用 SpringBoot 集成一些第三方的框架&#xff0c;还是需要花费一些…

Nginx快速入门:安装目录结构详解及核心配置解读(二)

0. 引言 上节我们讲解了nginx的应用场景和安装&#xff0c;本节继续针对nginx的各个目录文件进行讲解&#xff0c;让大家更加深入的认识nginx。并通过一个实操案例&#xff0c;带大家来实际认知nginx的核心配置 1. nginx安装目录结构 首先nginx的默认安装目录为&#xff1a;…

【一】FPGA实现SPI协议之SPI协议介绍

【一】FPGA实现SPI协议之SPI协议介绍 一、spi协议解析 spi协议有4根线&#xff0c;主机输出从机输入MOSI、主机输入从机输出MISO、时钟信号SCLK、片选信号SS\CS 。 一般用于主机和从机之间通信。由主机发起读请求和写请求&#xff0c;主机的权限是主动的&#xff0c;从机是被…

web打印技术方案

在B/S应用系统开发中常常遇到表单打印需求&#xff0c;尤其是OA、ERP类的企业运营管理系统&#xff0c;打印的需求很常见&#xff0c;但WEB应用的打印一直以来是一个难题&#xff0c;特别是在应用中完成标签打印&#xff08;如包裹面单、货运标签等&#xff09;、票据打印&…

AI降重工具

WEB版 体验一下 from docx import Document import requestsdef call_api_and_get_content(content, prompt):api_url "http://XXXXXXXX/api?content" content promptresponse requests.get(api_url)if response.status_code 200:api_result response.text.re…

搭建知识付费平台?明理信息科技为你提供全程解决方案

明理信息科技saas知识付费平台 在当今数字化时代&#xff0c;知识付费已经成为一种趋势&#xff0c;越来越多的人愿意为有价值的知识付费。然而&#xff0c;公共知识付费平台虽然内容丰富&#xff0c;但难以满足个人或企业个性化的需求和品牌打造。同时&#xff0c;开发和维护…

安装centos 7及配置网卡、连接Xshell

1.点击新建虚拟机 2.默认自定义 3.默认选择17版本的 4.选择稍后安装操作系统 5.选择Linux 6.选择安装目录和自定义名称&#xff0c;安装时尽量新建一个文件夹 7.根据电脑配置&#xff0c;自定义处理器大小&#xff0c;这里是建议配置 8.最少2个G 9.使用NAT 注&#xff1a; VMn…

智能优化算法应用:基于闪电连接过程算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于闪电连接过程算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于闪电连接过程算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.闪电连接过程算法4.实验参数设定…

pytorch中nn.Sequential详解

1 nn.Sequential概述 1.1 nn.Sequential介绍 nn.Sequential是一个序列容器&#xff0c;用于搭建神经网络的模块被按照被传入构造器的顺序添加到容器中。除此之外&#xff0c;一个包含神经网络模块的OrderedDict也可以被传入nn.Sequential()容器中。利用nn.Sequential()搭建好…

音视频技术开发周刊 | 324

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 467亿参数MoE追平GPT-3.5&#xff01;爆火开源Mixtral模型细节首公开&#xff0c;中杯逼近GPT-4 今天&#xff0c;Mistral AI公布了Mixtral 8x7B的技术细节&#xff0c;不…

Java精品项目源码新基于协同过滤算法的旅游推荐系统(编号V69)

Java精品项目源码新基于协同过滤算法的旅游推荐系统(编号V69) 大家好&#xff0c;小辰今天给大家介绍一个基于协同过滤算法的旅游推荐系统

java参数校验

引入依赖 <!--参数效验--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--Length参数效验--><dependency><groupId>org.hib…

Golang(壹)

爱情不需要华丽的言语&#xff0c;只需要默默的行动。 简介 应用领域&#xff1a; 下载vscode 使用vscode Go下载 - Go语言中文网 - Golang中文社区 下载sdk 解压到文件中&#xff0c;打开sdk解压文件 穿插dos操作系统知识点&#xff1a; 测试go语言环境 看到vscode 的目录结…

[Win10系统] Win10 任务栏软件图标显示为空白 | 解决方案

文章目录 [Win10系统] Win10 任务栏软件图标显示为空白 | 解决方案前言产生错误的原因解决方案方法一&#xff1a;手动操作方法二&#xff1a;自动操作 总结 [Win10系统] Win10 任务栏软件图标显示为空白 | 解决方案 前言 有时候&#xff0c;我们在使用 Windows 10 系统时&…

深度学习环境配置------windows系统(GPU)------Pytorch

深度学习环境配置------windows系统&#xff08;GPU&#xff09;------Pytorch 准备工作明确操作系统明确显卡系列 CUDA和Cudnn下载与安装1.下载2.安装 环境配置过程1.安装Anacoda2.配置环境1&#xff09;创建一个新的虚拟环境2&#xff09;pytorch相关库的安装 2.安装VScode1&…

图片去除背景,无水印下载的六大免费平台!

随着人工智能技术的不断进步&#xff0c;越来越多的应用场景开始利用人工智能技术来提升用户体验。其中&#xff0c;AI去除图片背景是一项非常实用的功能。AIGCer尝试了多个平台&#xff0c;排除了很多有水印&#xff0c;需要付费&#xff0c;去除效果差等平台&#xff0c;为大…

[Verilog] 设计方法和设计流程

主页&#xff1a; 元存储博客 文章目录 1. 设计方法2. 设计流程 3 Vivado软件设计流程总结 1. 设计方法 Verilog 的设计多采用自上而下的设计方法&#xff08;top-down&#xff09;。设计流程是指从一个项目开始从项目需求分析&#xff0c;架构设计&#xff0c;功能验证&#…

Re解析(正则表达式解析)

正则表达式基础 元字符 B站教学视频&#xff1a; 正则表达式元字符基本使用 量词 贪婪匹配和惰性匹配 惰性匹配如下两张图&#xff0c;而 .* 就表示贪婪匹配&#xff0c;即尽可能多的匹配到符合的字符串&#xff0c;如果使用贪婪匹配&#xff0c;那么结果就是图中的情况三 p…

vue-springboot+java导师选择分配双选管理系统 0spy6

.2.3功能需求 本导师选择管理系统是为了提高用户查阅信息的效率和管理人员管理信息的工作效率&#xff0c;可以快速存储大量数据&#xff0c;还有信息检索功能&#xff0c;这大大的满足了学生、导师和管理员这三者的需求。操作简单易懂&#xff0c;合理分析各个模块的功能&…

凤凰架构之事务处理

目录 本地事务全局事务共享事务分布式事务可靠消息队列TCC事务SAGA事务 本地事务 本地事务是最基础的一种事务解决方案&#xff0c;只适用于单个服务使用单个数据源的场景。从应用角度看&#xff0c;它是直接依赖于数据源本身提供的事务能力来工作的&#xff0c;在程序代码层面…