【Android】基于SurfaceControlViewHost实现跨进程渲染
1 前言
本文将介绍基于 SurfaceControlViewHost 实现跨进程渲染普通 View 和 GlSurfaceView,力求用最简单的 Demo,介绍 SurfaceControlViewHost 的应用,方便读者轻松扣出核心代码应用到自己的业务中。
核心代码片段如下。
1)服务端
public SurfaceControlViewHost.SurfacePackage getSurfacePackage(int displayId, IBinder hostToken, int width, int height) {// 创建SurfaceControlViewHostDisplay display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, display, hostToken);// 创建要渲染的ViewmView = new CustomView(mContext);// 将View附加到SurfaceControlViewHostmSurfaceControlViewHost.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>
服务端配置如下。
<serviceandroid:name=".RemoteRenderService"android:exported="true"><intent-filter><action android:name="com.zhyan8.remoterender.IRemoteRender"/></intent-filter>
</service><serviceandroid: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;@Overrideprotected 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();}}@Overrideprotected 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() {@Overridepublic 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());}}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.d(TAG, "onServiceDisconnected");clearBind();}};private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {@Overridepublic 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"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="draw"android:onClick="onClickDraw"/><android.view.SurfaceViewandroid: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());@Overridepublic void onCreate() {super.onCreate();Log.i(TAG, "onCreate");}@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG, "onBind");return mBinder;}@Overridepublic void onDestroy() {super.onDestroy();Log.i(TAG, "onDestroy");if (mSurfaceControlViewHost != null) {mSurfaceControlViewHost.release();}}private final IRemoteRender.Stub mBinder = new IRemoteRender.Stub() {@Overridepublic 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( () -> {// 创建SurfaceControlViewHostContext 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);// 将视图附加到SurfaceControlViewHostmSurfaceControlViewHost.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());@Overridepublic void onCreate() {super.onCreate();Log.i(TAG, "onCreate");}@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG, "onBind");return mBinder;}@Overridepublic void onDestroy() {Log.i(TAG, "onDestroy");super.onDestroy();if (mSurfaceControlViewHost != null) {mSurfaceControlViewHost.release();}}private final IRemoteRender.Stub mBinder = new IRemoteRender.Stub() {@Overridepublic 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( () -> {// 创建SurfaceControlViewHostContext 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));// 将视图附加到SurfaceControlViewHostmSurfaceControlViewHost.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();}@Overridepublic 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");}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {//设置视图窗口GLES30.glViewport(0, 0, width, height);}@Overridepublic 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, //V21f, -1f, 0f //V3};float[] texture = {1f, 0f, //V00f, 0f, //V10f, 1.0f, //V21f, 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];// 生成纹理idGLES30.glGenTextures(1, textureIds, 0);// 绑定纹理到OpenGLGLES30.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);
}
运行效果如下。
相关文章:

【Android】基于SurfaceControlViewHost实现跨进程渲染
1 前言 本文将介绍基于 SurfaceControlViewHost 实现跨进程渲染普通 View 和 GlSurfaceView,力求用最简单的 Demo,介绍 SurfaceControlViewHost 的应用,方便读者轻松扣出核心代码应用到自己的业务中。 核心代码片段如下。 1)服务端…...

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

能说一下JVM的内存区域吗
根据Java虚拟机的规范,JVM的内存区域可以细分为程序计数器、虚拟机栈、本地方法栈、堆和方法区。 其中方法区和线程是共享的,虚拟机栈、本地方法区和程序计数器是线程私有的。 介绍一下程序计数器? 程序计数器也被称为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("请输入一串字符:\n"); while ((ch getchar())! \n) { if ((ch > a && ch < z) || (ch > A && ch < Z)) { count; } } printf("字母的个数为&a…...

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

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

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

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

黑马点评Reids重点详解(Reids使用重点)
目录 一、短信登录(redisseesion) 基于Session实现登录流程 🔄 图中关键模块解释: 利用seesion登录的问题 设计key的具体细节 整体访问流程 二、商户查询缓存 reids与数据库主动更新的三种方案 缓存穿透 缓存雪崩问题及…...

小米2025年校招笔试真题手撕(一)
一、题目 小A每天都要吃a,b两种面包各一个。而他有n个不同的面包机,不同面包机制作面包的时间各不相同。第i台面包机制作a面包 需要花费ai的时间,制作b面包则需要花费bi的时间。 为能尽快吃到这两种面包,小A可以选择两个不同的面包机x&…...

《软件工程》第 11 章 - 结构化软件开发
结构化软件开发是一种传统且经典的软件开发方法,它强调将软件系统分解为多个独立的模块,通过数据流和控制流来描述系统的行为。本章将结合 Java 代码示例、可视化图表,深入讲解面向数据流的分析与设计方法以及实时系统设计的相关内容。 11.1 …...
MongoDB基础知识(浅显)
一、MongoDB 核心概念 MongoDB 是一个 面向文档的 NoSQL 数据库,与传统的关系型数据库(如 MySQL)相比,最大的区别是它以 文档(Document)为存储单元,而不是表和行。 1. 数据库(Data…...

Neo4j(三) - 使用Java操作Neo4j详解
文章目录 前言一、创建项目二、导入依赖三、节点和关系数据打印四、创建节点与关系五、查询数据方法六、更新数据方法七、删除节点与关系方法八、合并数据方法九、完整代码1. 完整代码2. 项目下载 前言 本文介绍通过 Java 操作 Neo4j 图数据库的完整流程。主要涵盖开发环境搭建…...
MPI实现大数据Ring Broadcast逻辑
文章目录 MPI实现大数据Ring Broadcast逻辑Ring Broadcast基本原理MPI实现代码优化建议性能考虑 MPI实现大数据Ring Broadcast逻辑 Ring Broadcast是一种在并行计算中高效传播大数据的技术,特别适合在MPI环境中使用。下面我将介绍如何用MPI实现这种广播逻辑。 Rin…...

蓝桥杯3503 更小的数
问题描述 小蓝有一个长度均为 n 且仅由数字字符 0∼9 组成的字符串,下标从 0 到 n−1,你可以将其视作是一个具有 n 位的十进制数字 num,小蓝可以从 num 中选出一段连续的子串并将子串进行反转,最多反转一次。 小蓝想要将选出的子…...
高并发下使用防重表做防重案例
工作中遇到的重复数据产生的问题: 之前提供的一个批量复制商品的接口,产生了重复的商品数据。 针对于这个问题我想到了可以加一张防重表,在防重表中增加商品表的name和model字段作为唯一索引。 例如: CREATE TABLE product_uniq…...

算法-全排列
1、全排列函数的使用 举例:{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主题,适合用于产品展示型的外贸网站。 https://www.jianzhanpress.com/?p8556 Machine机器wordpress模板 宽屏简洁实用的wordpress外贸建站模板,适合工业机器生产、加工、制造的外贸…...

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

mongodb语法$vlookup性能分析
1 场景描述 mongodb有两个表department和user表, department表有_id,name,level,表有记录169w条 user表有_id,name,department_id,表有记录169w条,department_id没有创建索引,department_id是department的_id。 现…...

晶圆隐裂检测提高半导体行业效率
半导体行业是现代制造业的核心基石,被誉为“工业的粮食”,而晶圆是半导体制造的核心基板,其质量直接决定芯片的性能、良率和可靠性。晶圆隐裂检测是保障半导体良率和可靠性的关键环节。 晶圆检测 通过合理搭配工业相机与光学系统,…...
临床试验中的独立数据监查委员会
1. IDMC会议概况 1.1 核心职责 1.1.1 安全性监查 IDMC需评估不良事件(AE)和严重不良事件(SAE),确保受试者风险可控。这是其核心职责之一,通过严格的安全性监查,保障受试者的健康和安全,避免因试验带来的不可控风险。 1.1.2 有效性评估 在预先设定的期中分析中,IDMC要…...

在 LangChain 中集成 Mem0 记忆系统教程
目录 简介环境准备基础配置核心组件说明1. 提示模板设计2. 上下文检索3. 响应生成4. 记忆存储 工作流程解析使用示例关键特性完整代码与效果 简介 Mem0 是一个强大的记忆系统,可以帮助 AI 应用存储和检索历史对话信息。本教程将介绍如何在 LangChain 应用中集成 Me…...
PTA练习题
文章目录 L1-101 别再来这么多猫娘了!(字符串查找-替换)L2-049 鱼与熊掌(set/暴力/vector)L2-050 懂蛇语(字符串匹配)L2-051 满树的遍历(前序)L2-001 紧急救援(最短路) L…...

华润电力招聘认知能力测评及性格测评真题题库考什么?
华润电力招聘测评包含逻辑推理、数字推理、语言理解三大类型的问卷。共计58题。测评限时60分钟。其中逻辑推理、数字推理、语言推理分别限时20分钟,如逾时未完成相关测试,测试将自动终止,请注意测评时间。为了确保测评的连贯性,建…...

Maven Profile在插件与依赖中的深度集成
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...

手机平板等设备租赁行业MDM方案解析
目录 引言:MDM 在租赁行业的重要性日益凸显 用户场景:租赁公司面临的主要挑战 1. 设备丢失、逾期未还 2. 手动配置和恢复效率低 3. 非授权使用频繁 4. 时区设置混乱影响运维 5. 缺乏实时监管能力 EasyControl MDM:租赁设备的远程管控…...
【前端】使用HTTPS
在前端本地开发环境中使用 HTTPS 主要取决于你用的是哪个构建工具(如 Vite、Webpack、Vue CLI 等)。 目录 ViteWebpack本地生产环境 npx serve浏览器提示“不安全”解决方法上传github注意不要把key传上去 Vite npm install --save-dev types/node #安…...
Python应用“面向对象”小练习
大家好!面向对象编程是一种以 “对象” 为核心的编程思想。对象可以看作是具有特定属性和行为的实体。例如,一个学生可以是一个对象,他的属性包括姓名和年龄,行为可以是打招呼。 代码呈现: # 定义类和对象 class Student:def __init__(sel…...