【Android】基于SurfaceControlViewHost实现跨进程渲染

news2025/5/29 9:44:18

1 前言

        本文将介绍基于 SurfaceControlViewHost 实现跨进程渲染普通 View 和 GlSurfaceView,力求用最简单的 Demo,介绍 SurfaceControlViewHost 的应用,方便读者轻松扣出核心代码应用到自己的业务中。

        核心代码片段如下。

        1)服务端

public SurfaceControlViewHost.SurfacePackage getSurfacePackage(int displayId, IBinder hostToken, int width, int height) {
    // 创建SurfaceControlViewHost
    Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
    mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, display, hostToken);
    // 创建要渲染的View
    mView = new CustomView(mContext);
    // 将View附加到SurfaceControlViewHost
    mSurfaceControlViewHost.setView(mView, width, height);
    SurfacePackage surfacePackage = mSurfaceControlViewHost.getSurfacePackage();
    return surfacePackage;
}

        2)客户端

IBinder hostToken = mSurfaceView.getHostToken();
SurfaceControlViewHost.SurfacePackage surfacePackage = mRemoteRender.getSurfacePackage(0, hostToken, 1000, 2000);
mSurfaceView.setChildSurfacePackage(surfacePackage);

        本文案例项目结构如下,完整资源见 → 基于SurfaceControlViewHost实现跨进程渲染。

2 AIDL 配置

        Android 跨进程通信可以使用 AIDL 或 messenger,它们本质都是 Binder,本文使用 AIDL 实现跨进程通信。

        1)aidl 文件

// IRemoteRender.aidl
package com.zhyan8.remoterender;

import android.view.SurfaceControlViewHost.SurfacePackage;
import android.os.IBinder;

interface IRemoteRender {
    SurfacePackage getSurfacePackage(int displayId, IBinder hostToken, int width, int height);
}

        2)gradle 配置

sourceSets {
    main {
        aidl.srcDirs = ['src/main/aidl']
    }
}

buildFeatures.aidl true

        3)manifest 配置

        客户端配置如下。

<queries>
    <package android:name="com.zhyan8.service" />
    <package android:name="com.zhyan8.glservice" />
</queries>

        服务端配置如下。

<service
    android:name=".RemoteRenderService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.zhyan8.remoterender.IRemoteRender"/>
    </intent-filter>
</service>

<service
    android:name=".RemoteGLRenderService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.zhyan8.remoterender.IRemoteRender"/>
    </intent-filter>
</service>

3 客户端

        MainActivity.java

package com.zhyan8.client;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControlViewHost.SurfacePackage;
import android.view.SurfaceView;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

import com.zhyan8.remoterender.IRemoteRender;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private IRemoteRender mRemoteRender;
    private IBinder mService;
    private SurfaceView mSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSurfaceView = findViewById(R.id.surface_view);
        startService();
    }

    public void onClickDraw(View view) {
        try {
            IBinder hostToken = mSurfaceView.getHostToken();
            SurfacePackage surfacePackage = mRemoteRender.getSurfacePackage(0, hostToken, 1000, 2000);
            mSurfaceView.setChildSurfacePackage(surfacePackage);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }

    private void startService() {
        Log.d(TAG, "startService");
        Intent intent = new Intent("com.zhyan8.remoterender.IRemoteRender");
        //intent.setPackage("com.zhyan8.service"); // 渲染普通View的服务
        intent.setPackage("com.zhyan8.glservice"); // 基于OpenGL ES渲染的服务
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    private void clearBind() {
        Log.d(TAG, "clearBind");
        if (mService != null) {
            mService.unlinkToDeath(mDeathRecipient, 0);
        }
        mRemoteRender = null;
        mService = null;
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected");
            mRemoteRender = IRemoteRender.Stub.asInterface(service);
            mService = service;
            try {
                mService.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                Log.e(TAG, "e=" + e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected");
            clearBind();
        }
    };

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {

        @Override
        public void binderDied() {
            Log.d(TAG, "binderDied");
            clearBind();
        }
    };
}

        activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="draw"
        android:onClick="onClickDraw"/>

    <android.view.SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="1000px"
        android:layout_height="2000px"
        android:layout_gravity="center"/>
</LinearLayout>

4 跨进程渲染普通 View

        RemoteRenderService.java

package com.zhyan8.service;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceControlViewHost.SurfacePackage;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.zhyan8.remoterender.IRemoteRender;

import java.util.concurrent.CountDownLatch;

public class RemoteRenderService extends Service {
    private static final String TAG = "RemoteRenderService";

    private SurfaceControlViewHost mSurfaceControlViewHost;
    private ImageView mImageView;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        return mBinder;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy");
        if (mSurfaceControlViewHost != null) {
            mSurfaceControlViewHost.release();
        }
    }

    private final IRemoteRender.Stub mBinder = new IRemoteRender.Stub() {

        @Override
        public SurfacePackage getSurfacePackage(int displayId, IBinder hostToken, int width, int height) {
            Log.i(TAG, "getSurfacePackage, displayId=" + displayId + ", hostToken=" + hostToken + ", width=" + width + ", height=" + height);
            final SurfacePackage[] result = new SurfaceControlViewHost.SurfacePackage[1];
            final CountDownLatch latch = new CountDownLatch(1);
            mHandler.post( () -> {
                // 创建SurfaceControlViewHost
                Context context = getBaseContext();
                Display display = context.getSystemService(DisplayManager.class).getDisplay(displayId);
                mSurfaceControlViewHost = new SurfaceControlViewHost(context, display, hostToken);
                // 创建要渲染的内容
                mImageView = new ImageView(RemoteRenderService.this);
                mImageView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
                mImageView.setScaleType(ImageView.ScaleType.FIT_XY);
                mImageView.setImageResource(R.drawable.girl);
                // 将视图附加到SurfaceControlViewHost
                mSurfaceControlViewHost.setView(mImageView, width, height);
                result[0] = mSurfaceControlViewHost.getSurfacePackage();
                latch.countDown();
            });

            try {
                latch.await(); // 等待主线程完成操作
                return result[0];
            } catch (InterruptedException e) {
                Log.i(TAG, "getSurfacePackage, e=" + e.getMessage());
            }
            return null;
        }
    };
}

        运行效果如下。

5 跨进程渲染 GLSurfaceView

        RemoteGLRenderService.java

package com.zhyan8.glservice;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceControlViewHost.SurfacePackage;
import android.view.ViewGroup;

import com.zhyan8.remoterender.IRemoteRender;

import java.util.concurrent.CountDownLatch;

public class RemoteGLRenderService extends Service {
    private static final String TAG = "RemoteGLRenderService";

    private SurfaceControlViewHost mSurfaceControlViewHost;
    private GLSurfaceView mGLSurfaceView;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        return mBinder;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy");
        super.onDestroy();
        if (mSurfaceControlViewHost != null) {
            mSurfaceControlViewHost.release();
        }
    }

    private final IRemoteRender.Stub mBinder = new IRemoteRender.Stub() {

        @Override
        public SurfacePackage getSurfacePackage(int displayId, IBinder hostToken, int width, int height) {
            Log.i(TAG, "getSurfacePackage, displayId=" + displayId + ", hostToken=" + hostToken + ", width=" + width + ", height=" + height);
            final SurfacePackage[] result = new SurfaceControlViewHost.SurfacePackage[1];
            final CountDownLatch latch = new CountDownLatch(1);
            mHandler.post( () -> {
                // 创建SurfaceControlViewHost
                Context context = getBaseContext();
                Display display = context.getSystemService(DisplayManager.class).getDisplay(displayId);
                mSurfaceControlViewHost = new SurfaceControlViewHost(context, display, hostToken);
                // 创建要渲染的内容
                mGLSurfaceView = new GLSurfaceView(RemoteGLRenderService.this);
                mGLSurfaceView.setEGLContextClientVersion(3);
                mGLSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
                mGLSurfaceView.setRenderer(new MyGLRenderer(RemoteGLRenderService.this));
                // 将视图附加到SurfaceControlViewHost
                mSurfaceControlViewHost.setView(mGLSurfaceView, width, height);
                result[0] = mSurfaceControlViewHost.getSurfacePackage();
                latch.countDown();
            });

            try {
                latch.await(); // 等待主线程完成操作
                return result[0];
            } catch (InterruptedException e) {
                Log.i(TAG, "getSurfacePackage, e=" + e.getMessage());
            }
            return null;
        }
    };
}

        MyGLRenderer.java

package com.zhyan8.glservice;

import android.opengl.GLES30;
import android.opengl.GLSurfaceView;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;

import java.nio.FloatBuffer;

public class MyGLRenderer implements GLSurfaceView.Renderer {
    private FloatBuffer vertexBuffer;
    private FloatBuffer textureBuffer;
    private MyGLUtils mGLUtils;
    private int mTextureId;
    private int mTimeLocation;
    private long mStartTime = 0L;
    private long mRunTime = 0L;

    public MyGLRenderer(Context context) {
        mGLUtils = new MyGLUtils(context);
        getFloatBuffer();
        mStartTime = System.currentTimeMillis();
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
        //设置背景颜色
        GLES30.glClearColor(0.1f, 0.2f, 0.3f, 0.4f);
        //编译着色器
        final int vertexShaderId = mGLUtils.compileShader(GLES30.GL_VERTEX_SHADER, R.raw.vertex_shader);
        final int fragmentShaderId = mGLUtils.compileShader(GLES30.GL_FRAGMENT_SHADER, R.raw.fragment_shader);
        //链接程序片段
        int programId = mGLUtils.linkProgram(vertexShaderId, fragmentShaderId);
        GLES30.glUseProgram(programId);
        mTextureId = mGLUtils.loadTexture(R.drawable.girl);
        mTimeLocation = GLES30.glGetUniformLocation(programId, "u_time");
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置视图窗口
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        mRunTime = System.currentTimeMillis() - mStartTime;
        //将颜色缓冲区设置为预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        GLES30.glUniform1f(mTimeLocation, mRunTime / 1000f);
        //启用顶点的数组句柄
        GLES30.glEnableVertexAttribArray(0);
        GLES30.glEnableVertexAttribArray(1);
        //准备顶点坐标和纹理坐标
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 0, textureBuffer);
        //激活纹理
        GLES30.glActiveTexture(GLES30.GL_TEXTURE);
        //绑定纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId);
        //绘制贴图
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, 4);
        //禁止顶点数组句柄
        GLES30.glDisableVertexAttribArray(0);
        GLES30.glDisableVertexAttribArray(1);
    }

    private void getFloatBuffer() {
        float[] vertex = new float[] {
                1f, 1f, 0f,     //V0
                -1f, 1f, 0f,    //V1
                -1f, -1f, 0f,   //V2
                1f, -1f, 0f     //V3
        };
        float[] texture = {
                1f, 0f,     //V0
                0f, 0f,     //V1
                0f, 1.0f,   //V2
                1f, 1.0f    //V3
        };
        vertexBuffer = mGLUtils.getFloatBuffer(vertex);
        textureBuffer = mGLUtils.getFloatBuffer(texture);
    }
}

        MyGLUtils.java

package com.zhyan8.glservice;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES30;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class MyGLUtils {
    private Context mContext;
    private Bitmap mBitmap;

    public MyGLUtils(Context context) {
        mContext = context;
    }

    public FloatBuffer getFloatBuffer(float[] floatArr) {
        FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        fb.put(floatArr);
        fb.position(0);
        return fb;
    }

    //通过代码片段编译着色器
    public int compileShader(int type, String shaderCode){
        int shader = GLES30.glCreateShader(type);
        GLES30.glShaderSource(shader, shaderCode);
        GLES30.glCompileShader(shader);
        return shader;
    }

    //通过外部资源编译着色器
    public int compileShader(int type, int shaderId){
        String shaderCode = readShaderFromResource(shaderId);
        return compileShader(type, shaderCode);
    }

    //链接到着色器
    public int linkProgram(int vertexShaderId, int fragmentShaderId) {
        final int programId = GLES30.glCreateProgram();
        //将顶点着色器加入到程序
        GLES30.glAttachShader(programId, vertexShaderId);
        //将片元着色器加入到程序
        GLES30.glAttachShader(programId, fragmentShaderId);
        //链接着色器程序
        GLES30.glLinkProgram(programId);
        return programId;
    }

    //从shader文件读出字符串
    private String readShaderFromResource(int shaderId) {
        InputStream is = mContext.getResources().openRawResource(shaderId);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line;
        StringBuilder sb = new StringBuilder();
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    //加载纹理贴图
    public int loadTexture(int resourceId) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        mBitmap = BitmapFactory.decodeResource(mContext.getResources(), resourceId, options);
        final int[] textureIds = new int[1];
        // 生成纹理id
        GLES30.glGenTextures(1, textureIds, 0);
        // 绑定纹理到OpenGL
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]);
        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR_MIPMAP_LINEAR);
        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
        // 加载bitmap到纹理中
        GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, mBitmap, 0);
        // 生成MIP贴图
        GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);
        // 取消绑定纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
        return textureIds[0];
    }
}

        vertex_shader.glsl

attribute vec4 aPosition;
attribute vec2 aTextureCoord;
varying vec2 vTexCoord;

void main() {
     gl_Position = aPosition;
     vTexCoord = aTextureCoord;
}

        fragment_shader.glsl

precision mediump float;
uniform sampler2D uTextureUnit;
varying vec2 vTexCoord;
uniform float u_time;

void main() {
     vec3 color = texture2D(uTextureUnit, vTexCoord).rgb;
     color.x += sin(u_time * 1.3 + 0.4) * 0.2;
     color.y += cos(u_time * 1.7 + 7.1) * 0.2;
     color.z += (sin(u_time) + cos(u_time)) * 0.2;
     gl_FragColor = vec4(color, 1.0);
}

        运行效果如下。

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

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

相关文章

vue+ThreeJs 创造自动选择的甜甜圈(圆环)

嗨&#xff0c;我是小路。今天主要和大家分享的主题是“vueThreeJs 创造自动选择的甜甜圈”。 一个漂浮在页面中央的 3D 圆环&#xff0c;多个图标/文本/图片均匀分布在圆周上。它会自动缓慢旋转&#xff0c;形成动态视觉焦点。这就是今天要搭建的项目&#xff0c;并对…

能说一下JVM的内存区域吗

根据Java虚拟机的规范&#xff0c;JVM的内存区域可以细分为程序计数器、虚拟机栈、本地方法栈、堆和方法区。 其中方法区和线程是共享的&#xff0c;虚拟机栈、本地方法区和程序计数器是线程私有的。 介绍一下程序计数器&#xff1f; 程序计数器也被称为PC寄存器。是一块较小…

东方仙盟_灵颜妙手——表单样式——仙盟创梦IDE

代码 .东方仙盟_灵颜妙手 {background-color: #f0f8ff;padding: 10px;display: block;width:100%;height: 100%;}.东方仙盟_灵颜妙手 .表单 {max-width: 800px;margin: 0 auto;background-color: white;border-radius: 8px;box-shadow: 0 0 10px rgba(0, 123, 255, 0.1);paddin…

输入一串字符,统计其中字母的个数

#include <stdio.h> int main() { char ch; int count 0; printf("请输入一串字符&#xff1a;\n"); while ((ch getchar())! \n) { if ((ch > a && ch < z) || (ch > A && ch < Z)) { count; } } printf("字母的个数为&a…

进程IO之 进程

一、进程相关概念 1.什么是进程 程序&#xff1a;静态的&#xff0c;编译好的可执行文件&#xff0c;存放在磁盘中的指令和数据的集合 进程&#xff1a;动态的&#xff0c;是程序的一次执行过程&#xff0c;是独立的可调度的任务 2.进程的特点 &#xff08;1&#xff09;对…

OpenGL Chan视频学习-5 Vertex Attributes and Layouts in OpenGL

bilibili视频链接&#xff1a; 【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p5&vd_source44b77bde056381262ee55e448b9b1973 一、知识点整理 1.1.OpenGL管线工作流程 为显卡提供绘制的所有数据&#xff0c;并将数据存储在GPU内存使用着色器&…

ESP32学习笔记_Peripherals(3)——ADC

摘要 本博客介绍了ESP32-S3芯片内置SAR ADC的原理、参考电压、分辨率、信号衰减等基础知识&#xff0c;并讲解了如何使用ESP-IDF驱动库实现ADC的连续采样&#xff08;DMA&#xff09;功能&#xff0c;演示了多通道模拟信号&#xff08;如摇杆模块&#xff09;的采集与处理流程…

QT学习一

对于选择qmake还是cmake&#xff0c;现在写的暂时先用qmake 1.命名规范和快捷键 2.按钮控件常用API //创建第一个按钮QPushButton * btn new QPushButton;//让btn对象 依赖在mywidget窗口中btn->setParent(this);//显示文本btn->setText("第一个按钮");//创建…

黑马点评Reids重点详解(Reids使用重点)

目录 一、短信登录&#xff08;redisseesion&#xff09; 基于Session实现登录流程 &#x1f504; 图中关键模块解释&#xff1a; 利用seesion登录的问题 设计key的具体细节 整体访问流程 二、商户查询缓存 reids与数据库主动更新的三种方案 缓存穿透 缓存雪崩问题及…

小米2025年校招笔试真题手撕(一)

一、题目 小A每天都要吃a,b两种面包各一个。而他有n个不同的面包机&#xff0c;不同面包机制作面包的时间各不相同。第i台面包机制作a面包 需要花费ai的时间&#xff0c;制作b面包则需要花费bi的时间。 为能尽快吃到这两种面包&#xff0c;小A可以选择两个不同的面包机x&…

《软件工程》第 11 章 - 结构化软件开发

结构化软件开发是一种传统且经典的软件开发方法&#xff0c;它强调将软件系统分解为多个独立的模块&#xff0c;通过数据流和控制流来描述系统的行为。本章将结合 Java 代码示例、可视化图表&#xff0c;深入讲解面向数据流的分析与设计方法以及实时系统设计的相关内容。 11.1 …

Neo4j(三) - 使用Java操作Neo4j详解

文章目录 前言一、创建项目二、导入依赖三、节点和关系数据打印四、创建节点与关系五、查询数据方法六、更新数据方法七、删除节点与关系方法八、合并数据方法九、完整代码1. 完整代码2. 项目下载 前言 本文介绍通过 Java 操作 Neo4j 图数据库的完整流程。主要涵盖开发环境搭建…

蓝桥杯3503 更小的数

问题描述 小蓝有一个长度均为 n 且仅由数字字符 0∼9 组成的字符串&#xff0c;下标从 0 到 n−1&#xff0c;你可以将其视作是一个具有 n 位的十进制数字 num&#xff0c;小蓝可以从 num 中选出一段连续的子串并将子串进行反转&#xff0c;最多反转一次。 小蓝想要将选出的子…

算法-全排列

1、全排列函数的使用 举例&#xff1a;{1,2,3}的全排列 #include<iostream> #include<bits/stdc.h> using namespace std; typedef long long ll; int main(){ll a[3] {1, 2, 3};do{for (ll i 0; i < 3;i){cout << a[i] << " ";}cout…

最好用的wordpress外贸主题

产品展示独立站wordpress主题 橙色的首页大banner外贸英文wordpress主题&#xff0c;适合用于产品展示型的外贸网站。 https://www.jianzhanpress.com/?p8556 Machine机器wordpress模板 宽屏简洁实用的wordpress外贸建站模板&#xff0c;适合工业机器生产、加工、制造的外贸…

2025 河北ICPC( D. 金泰园(二分)-- C.年少的誓约(公式转化))

文章目录 2025 河北ICPCD. 金泰园&#xff08;二分&#xff09;C.年少的誓约(公式转化)总结 2025 河北ICPC 题目链接&#xff1a; Attachments - The 9th Hebei Collegiate Programming Contest - Codeforces sdccpc20250522 - Virtual Judge 赛时&#xff1a;5道 D. 金泰…

mongodb语法$vlookup性能分析

1 场景描述 mongodb有两个表department和user表&#xff0c; department表有_id,name,level&#xff0c;表有记录169w条 user表有_id,name,department_id&#xff0c;表有记录169w条&#xff0c;department_id没有创建索引&#xff0c;department_id是department的_id。 现…

晶圆隐裂检测提高半导体行业效率

半导体行业是现代制造业的核心基石&#xff0c;被誉为“工业的粮食”&#xff0c;而晶圆是半导体制造的核心基板&#xff0c;其质量直接决定芯片的性能、良率和可靠性。晶圆隐裂检测是保障半导体良率和可靠性的关键环节。 晶圆检测 通过合理搭配工业相机与光学系统&#xff0c…

在 LangChain 中集成 Mem0 记忆系统教程

目录 简介环境准备基础配置核心组件说明1. 提示模板设计2. 上下文检索3. 响应生成4. 记忆存储 工作流程解析使用示例关键特性完整代码与效果 简介 Mem0 是一个强大的记忆系统&#xff0c;可以帮助 AI 应用存储和检索历史对话信息。本教程将介绍如何在 LangChain 应用中集成 Me…

华润电力招聘认知能力测评及性格测评真题题库考什么?

华润电力招聘测评包含逻辑推理、数字推理、语言理解三大类型的问卷。共计58题。测评限时60分钟。其中逻辑推理、数字推理、语言推理分别限时20分钟&#xff0c;如逾时未完成相关测试&#xff0c;测试将自动终止&#xff0c;请注意测评时间。为了确保测评的连贯性&#xff0c;建…