ChatGPT安卓集成实战:从SDK接入到性能优化全指南

news2026/3/16 18:52:10
ChatGPT安卓集成实战从SDK接入到性能优化全指南最近在做一个需要集成AI对话功能的安卓应用目标是把类似ChatGPT的智能对话能力塞进手机里。想法很美好但真动手了才发现从SDK接入到最终流畅运行中间全是“坑”。网络延迟、响应卡顿、数据安全、离线体验……每一个环节都够喝一壶的。经过一番折腾总算把流程跑通并做了一些优化。今天就把这套从实战中总结出来的集成方案整理出来希望能帮到同样在摸索的开发者朋友们。1. 集成路上的那些“坑”背景与痛点分析在安卓端集成大语言模型API远不止是发个HTTP请求那么简单。我遇到的典型问题主要有这么几个API版本与兼容性OpenAI的API迭代不算慢官方SDK的更新有时会滞后。直接使用REST API虽然灵活但需要自己处理认证、参数序列化、错误码映射等一堆琐事稍有不慎就会因为版本变化导致请求失败。长文本响应与UI卡顿这是最影响用户体验的一点。模型生成一段较长的回复可能需要好几秒甚至十几秒。如果在主线程同步等待应用必然卡死。如何优雅地处理流式响应并实时、平滑地更新UI是个技术活。网络不稳定与重试策略移动网络环境复杂请求超时、中断是家常便饭。简单的重试可能会加重服务器负担或造成用户等待过久需要更智能的重试退避机制。多轮对话上下文管理ChatGPT的魅力在于上下文连贯性。在App中我们需要在本地维护一个结构化的对话历史每次请求都要携带正确的上下文并在应用重启后能恢复这对本地存储设计提出了要求。敏感数据的安全存储API Key是最高权限的凭证绝不能硬编码或明文存储。如何在安卓设备上安全地保管这类秘密需要遵循平台的最佳安全实践。2. 技术选型官方SDK vs 自定义封装面对集成第一个决策就是用OpenAI官方提供的Java/Kotlin SDK还是自己用RetrofitOkHttp封装我画了一个简单的决策树来帮助选择是否需要最新、最全的API功能 ├── 是 → 优先评估官方SDK的更新频率和版本 └── 否 → 项目对包体积是否敏感 ├── 是希望最小化依赖→ 选择自定义封装Retrofit └── 否 → 项目是否需要快速验证原型 ├── 是 → 选择官方SDK开箱即用 └── 否 → 团队是否希望更精细地控制网络层如加密、拦截、缓存 ├── 是 → 选择自定义封装 └── 否 → 选择官方SDK官方SDK的优点开箱即用功能全面通常跟随API更新社区遇到问题可能已有解决方案。官方SDK的缺点可能会引入不必要的依赖增加包体积对网络层的控制粒度较粗如果API有定制化需求修改起来可能不如自己的代码方便。自定义封装的优点轻量依赖可控可以深度集成到现有的网络框架中能实现高度定制化的逻辑如特定的重试、加密、日志。自定义封装的缺点需要自己实现所有API接口的序列化/反序列化需要紧跟官方API的变化前期开发成本较高。对于我的项目由于已经有一套成熟的网络层架构且需要对请求过程进行非常细致的监控和改造我最终选择了基于Retrofit Kotlin协程的自定义封装方案。这样既能复用现有基础设施又能获得最大的灵活性。3. 核心实现构建健壮的通信层3.1 使用Kotlin Flow处理流式响应ChatGPT的API支持流式输出streaming这能极大提升长文本响应的感知速度。在安卓上用Kotlin Flow来处理这种数据流非常合适。interface OpenAIApiService { Headers(Content-Type: application/json) POST(v1/chat/completions) suspend fun createChatCompletionStreaming( Header(Authorization) authHeader: String, Body request: ChatCompletionRequest ): ResponseBody // 注意这里返回的是ResponseBody用于手动处理流 } class OpenAIRepository(private val apiService: OpenAIApiService) { fun streamChatCompletion(messages: ListMessage): FlowString flow { val request ChatCompletionRequest( model gpt-3.5-turbo, messages messages, stream true // 开启流式 ) val responseBody apiService.createChatCompletionStreaming( authHeader Bearer ${getSecureApiKey()}, request request ) responseBody.use { body - body.source().use { source - val buffer source.buffer() while (true) { val line buffer.readUtf8Line() ?: break if (line.startsWith(data: )) { val data line.removePrefix(data: ) if (data [DONE]) break try { val json Json.parseToJsonElement(data) val choices json.jsonObject[choices]?.jsonArray val delta choices?.firstOrNull() ?.jsonObject?.get(delta)?.jsonObject val content delta?.get(content)?.jsonPrimitive?.contentOrNull content?.let { emit(it) } } catch (e: Exception) { // 处理单条数据解析错误不中断整个流 Log.e(OpenAI, Parse streaming data error, e) } } } } } }.catch { e - // 统一处理流中发生的异常 throw IOException(Stream reading failed, e) }.flowOn(Dispatchers.IO) // 确保在IO线程执行 }在ViewModel中收集这个Flowclass ChatViewModel : ViewModel() { private val _uiState MutableStateFlow(ChatUiState()) val uiState: StateFlowChatUiState _uiState.asStateFlow() fun sendMessage(userInput: String) { viewModelScope.launch { _uiState.update { it.copy(isLoading true, currentAnswer ) } // 更新本地消息列表 val userMessage Message(role user, content userInput) val updatedMessages _uiState.value.history userMessage try { repository.streamChatCompletion(updatedMessages) .collect { chunk - // 收到一个流片段追加到当前回答中 _uiState.update { state - state.copy(currentAnswer state.currentAnswer chunk) } } // 流式接收完毕将最终答案存入历史 val assistantMessage Message(role assistant, content _uiState.value.currentAnswer) _uiState.update { state - state.copy( history state.history userMessage assistantMessage, currentAnswer , isLoading false ) } // 触发本地缓存保存 saveHistoryToLocal() } catch (e: Exception) { _uiState.update { it.copy(error e.message, isLoading false) } } } } }3.2 带指数退避和Token刷新的重试拦截器网络请求必须要有重试机制。一个优秀的重试策略应该包含指数退避避免雪崩和针对认证失败的特殊处理如刷新JWT Token。class RetryAndAuthInterceptor( private val tokenManager: TokenManager ) : Interceptor { companion object { private const val MAX_RETRY_COUNT 3 private val RETRYABLE_STATUS_CODES setOf(408, 429, 500, 502, 503, 504) } Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val originalRequest chain.request() var currentRequest originalRequest var retryCount 0 while (true) { val response try { chain.proceed(currentRequest) } catch (e: IOException) { // 网络IO异常判断是否重试 if (retryCount MAX_RETRY_COUNT isRetryableException(e)) { retryCount val waitTime calculateBackoffDelay(retryCount) Thread.sleep(waitTime) continue // 重试当前请求 } else { throw e // 达到最大重试次数或不可重试异常抛出 } } // 检查HTTP状态码 when (response.code) { 401 - { // 认证失败尝试刷新Token response.close() if (retryCount 0) { // 仅在第一轮401时尝试刷新 val newToken tokenManager.refreshTokenBlocking() if (newToken ! null) { // 用新Token构建新请求 currentRequest originalRequest.newBuilder() .header(Authorization, Bearer $newToken) .build() retryCount continue // 用新Token重试请求 } } // 刷新失败或已刷新过仍失败返回原响应 return response } in RETRYABLE_STATUS_CODES - { // 可重试的服务端错误 response.close() if (retryCount MAX_RETRY_COUNT) { retryCount val waitTime calculateBackoffDelay(retryCount) Thread.sleep(waitTime) continue } return response.newBuilder() .code(response.code) .message(Service unavailable after $MAX_RETRY_COUNT retries) .build() } else - { // 成功或其他不可重试错误直接返回 return response } } } } private fun calculateBackoffDelay(retryCount: Int): Long { // 指数退避公式delay baseDelay * (2 ^ (retryCount - 1)) jitter val baseDelay 1000L // 1秒 val exponential 1L shl (retryCount - 1) // 2^(retryCount-1) val jitter (0..500).random().toLong() // 0-500ms的随机抖动避免惊群 return baseDelay * exponential jitter } private fun isRetryableException(e: IOException): Boolean { // 判断是否为网络超时、中断等可重试异常 return e is SocketTimeoutException || e is ConnectException || e is UnknownHostException // 谨慎重试DNS失败 } }4. 性能优化流畅体验与离线支持4.1 使用Room缓存对话历史为了提升体验和实现有限的离线查看我用Room来持久化对话记录。// 定义消息实体 Entity(tableName chat_messages) data class ChatMessageEntity( PrimaryKey(autoGenerate true) val id: Long 0, val conversationId: String, // 会话ID用于分组 val role: String, // user 或 assistant val content: String, val timestamp: Long System.currentTimeMillis(), val modelUsed: String? null // 记录使用的模型便于追溯 ) // 定义数据访问对象 Dao interface ChatMessageDao { Query(SELECT * FROM chat_messages WHERE conversationId :conversationId ORDER BY timestamp ASC) fun getMessagesByConversation(conversationId: String): FlowListChatMessageEntity Insert suspend fun insertMessage(message: ChatMessageEntity) Insert suspend fun insertAll(messages: ListChatMessageEntity) Query(DELETE FROM chat_messages WHERE conversationId :conversationId) suspend fun deleteConversation(conversationId: String) Query(SELECT DISTINCT conversationId FROM chat_messages ORDER BY timestamp DESC) fun getAllConversationIds(): FlowListString } // 在Repository层整合网络与本地数据 class ChatRepository( private val apiService: OpenAIApiService, private val messageDao: ChatMessageDao, private val dispatcher: CoroutineDispatcher Dispatchers.IO ) { suspend fun sendMessageAndSave( conversationId: String, userMessage: String ): FlowString flow { // 1. 立即保存用户消息到本地 val userEntity ChatMessageEntity( conversationId conversationId, role user, content userMessage ) withContext(dispatcher) { messageDao.insertMessage(userEntity) } // 2. 获取当前会话历史用于构建API请求上下文 val history withContext(dispatcher) { messageDao.getMessagesByConversation(conversationId) .first() // 取第一个最新快照 } val apiMessages history.map { Message(it.role, it.content) } // 3. 调用流式API并收集响应 val fullResponse StringBuilder() repository.streamChatCompletion(apiMessages) .collect { chunk - fullResponse.append(chunk) emit(chunk) // 向上游发射片段 } // 4. 流式接收完毕保存AI回复到本地 val assistantEntity ChatMessageEntity( conversationId conversationId, role assistant, content fullResponse.toString() ) withContext(dispatcher) { messageDao.insertMessage(assistantEntity) } }.flowOn(dispatcher) }4.2 通过WorkManager调度后台同步任务如果应用有跨设备同步需求可以使用WorkManager在合适的时机如连接Wi-Fi、充电时在后台同步对话历史到云端。class SyncConversationWorker( appContext: Context, workerParams: WorkerParameters ) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { return try { // 1. 获取需要同步的本地新消息 val unsyncedMessages getUnsyncedMessagesFromLocal() if (unsyncedMessages.isEmpty()) { return Result.success() // 没有需要同步的数据 } // 2. 同步到云端服务器这里假设有自己的后端 val syncSuccess syncToCloud(unsyncedMessages) if (syncSuccess) { // 3. 标记本地消息为已同步 markMessagesAsSynced(unsyncedMessages.map { it.id }) Result.success() } else { // 同步失败根据重试策略决定是否重试 if (runAttemptCount MAX_SYNC_ATTEMPTS) { Result.retry() } else { Result.failure() } } } catch (e: Exception) { Log.e(SyncWorker, Sync failed, e) Result.failure() } } // 配置周期性同步任务 fun schedulePeriodicSync(context: Context) { val constraints Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) // 仅在Wi-Fi下 .setRequiresBatteryNotLow(true) // 电量不低时 .build() val syncRequest PeriodicWorkRequestBuilderSyncConversationWorker( 4, TimeUnit.HOURS, // 每4小时一次 15, TimeUnit.MINUTES // 允许15分钟弹性执行窗口 ).setConstraints(constraints) .build() WorkManager.getInstance(context) .enqueueUniquePeriodicWork( conversation_sync, ExistingPeriodicWorkPolicy.KEEP, // 如果已有任务保持原有 syncRequest ) } }5. 安全合规保护用户数据与API密钥5.1 使用AndroidKeyStore加密敏感数据API Key绝对不能硬编码在代码中或明文存储在SharedPreferences里。AndroidKeyStore提供了硬件级别的密钥保护。class SecureTokenManager(context: Context) { private val sharedPrefs context.getSharedPreferences(secure_prefs, Context.MODE_PRIVATE) private val keyStore KeyStore.getInstance(AndroidKeyStore).apply { load(null) } private val cipher Cipher.getInstance(AES/GCM/NoPadding) private val keyAlias app_openai_key init { createKeyIfNeeded() } private fun createKeyIfNeeded() { if (!keyStore.containsAlias(keyAlias)) { val keyGenParams KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ).apply { setBlockModes(KeyProperties.BLOCK_MODE_GCM) setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) setKeySize(256) setUserAuthenticationRequired(false) // 根据需求设置是否需生物认证 setRandomizedEncryptionRequired(true) }.build() val keyGenerator KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore ) keyGenerator.init(keyGenParams) keyGenerator.generateKey() } } fun saveApiKey(apiKey: String) { try { val secretKey keyStore.getKey(keyAlias, null) as SecretKey cipher.init(Cipher.ENCRYPT_MODE, secretKey) val iv cipher.iv // GCM需要IV val encrypted cipher.doFinal(apiKey.toByteArray(Charsets.UTF_8)) // 保存加密数据和IV sharedPrefs.edit() .putString(encrypted_key, Base64.encodeToString(encrypted, Base64.DEFAULT)) .putString(encryption_iv, Base64.encodeToString(iv, Base64.DEFAULT)) .apply() } catch (e: Exception) { throw SecurityException(Failed to encrypt API key, e) } } fun getApiKey(): String? { return try { val encryptedBase64 sharedPrefs.getString(encrypted_key, null) val ivBase64 sharedPrefs.getString(encryption_iv, null) if (encryptedBase64 null || ivBase64 null) { return null } val secretKey keyStore.getKey(keyAlias, null) as SecretKey val iv Base64.decode(ivBase64, Base64.DEFAULT) val encrypted Base64.decode(encryptedBase64, Base64.DEFAULT) cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv)) val decrypted cipher.doFinal(encrypted) String(decrypted, Charsets.UTF_8) } catch (e: Exception) { Log.e(SecureTokenManager, Failed to decrypt API key, e) null } } fun clearApiKey() { sharedPrefs.edit() .remove(encrypted_key) .remove(encryption_iv) .apply() } }5.2 ProGuard/R8混淆规则合理的混淆能增加反编译难度保护业务逻辑和API端点。# 保留Retrofit相关的类和方法 -keepattributes Signature, InnerClasses, EnclosingMethod -keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations # 保留Retrofit接口 -keep interface com.yourpackage.api.** { *; } # 保留JSON序列化/反序列化相关的类如使用Moshi/Gson -keep class com.yourpackage.model.** { *; } # 保留Room相关的类 -keep class * extends androidx.room.RoomDatabase -keep class * extends androidx.room.Entity -keepclassmembers class * { androidx.room.* *; } # 保留WorkManager Worker类 -keep class * extends androidx.work.Worker { public init(android.content.Context,androidx.work.WorkerParameters); public doWork(); } # 保留ViewModel和LiveData/Flow相关类 -keep class * extends androidx.lifecycle.ViewModel -keepclassmembers class * extends androidx.lifecycle.ViewModel { init(...); } # 如果使用了反射保留相关类 -keepclassmembers class **.BuildConfig { public static *; }6. 避坑指南三个常见崩溃场景与解决方案在实际开发中我遇到了不少导致应用崩溃或行为异常的场景以下是三个典型的例子和解决方法场景一大响应导致OOM内存溢出问题当AI返回极长的文本如生成一篇千字文章时如果一次性加载到内存中可能引发OutOfMemoryError。解决方案使用流式响应如前文所示流式接收并逐步显示避免一次性持有完整字符串。分页加载历史对于本地存储的对话历史在UI上实现分页加载不要一次性查询并渲染全部。限制上下文长度在发送给API的请求中主动截断或总结过长的历史对话确保请求体不会过大。场景二DNS解析超时或失败问题在某些网络环境下初始化请求时DNS解析api.openai.com可能超时导致连接失败。解决方案配置OkHttp的Dns使用自定义Dns实现可以集成HTTPDNS或设置备用IP。class CustomDns : Dns { override fun lookup(hostname: String): ListInetAddress { return try { Dns.SYSTEM.lookup(hostname) } catch (e: Exception) { // 系统DNS失败尝试备用方案 if (hostname api.openai.com) { // 注意直接使用IP需要处理SSL证书验证问题且IP可能变化不推荐生产环境使用 // listOf(InetAddress.getByName(备用IP)) throw e // 暂时直接抛出实际可记录日志并降级 } else { throw e } } } }增加连接超时时间适当调整OkHttpClient的连接和读取超时设置。优雅降级在多次DNS失败后提示用户检查网络或切换网络环境。场景三后台进程被杀死导致数据丢失问题用户正在输入或AI正在流式回复时应用退到后台可能被系统回收导致当前状态丢失。解决方案即时持久化用户发送消息后立即保存到Room数据库。流式接收过程中可以定期或每收到一定量数据就更新一次本地缓存注意性能平衡。使用SavedStateHandle在ViewModel中利用SavedStateHandle来保存关键的UI状态如当前输入框内容、是否正在加载以便在配置变更如旋转和轻量级进程回收时恢复。class ChatViewModel( private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val KEY_CURRENT_INPUT current_input var currentInput: String get() savedStateHandle[KEY_CURRENT_INPUT] ?: set(value) { savedStateHandle[KEY_CURRENT_INPUT] value } }处理协程生命周期使用viewModelScope启动协程它会在ViewModel清除时自动取消避免内存泄漏。对于重要的后台同步任务使用WorkManager它由系统调度进程被杀后仍能继续。7. 代码规范遵循Jetpack组件化与KDoc保持代码清晰和可维护性至关重要。我遵循了以下规范架构分层严格区分ViewUI、ViewModel状态管理、Repository数据聚合、DataSource本地/远程数据源。各层之间单向依赖。单一数据源UI数据始终来源于ViewModel暴露的StateFlow/LiveData避免在View中直接操作或持有数据。使用依赖注入使用Hilt或Koin管理依赖提高可测试性。编写有意义的KDoc关键公共类、方法、复杂逻辑处添加KDoc注释。/** * 负责管理聊天会话的核心仓库。 * * 该类聚合了网络API调用和本地数据库操作为ViewModel提供统一的数据访问接口。 * 它处理对话历史的持久化、流式响应的解析以及错误处理。 * * property apiService 用于调用OpenAI ChatCompletion API的服务接口 * property messageDao 用于访问本地对话消息数据库的DAO * property tokenManager 用于安全获取和刷新API认证令牌的管理器 * property dispatcher 协程调度器默认为[Dispatchers.IO] */ class ChatRepository Inject constructor( private val apiService: OpenAIApiService, private val messageDao: ChatMessageDao, private val tokenManager: TokenManager, private val dispatcher: CoroutineDispatcher Dispatchers.IO ) { /** * 发送用户消息并获取AI的流式回复。 * * 该方法执行以下步骤 * 1. 将用户消息立即保存至本地数据库。 * 2. 从数据库加载当前会话的完整历史。 * 3. 调用OpenAI流式API并返回一个[Flow]持续发射回复文本片段。 * 4. 流式传输完成后将完整的AI回复保存至本地数据库。 * * param conversationId 当前对话的唯一标识符 * param userMessage 用户输入的文本消息 * return 一个[Flow]持续发射AI回复的字符串片段。在流完成或出错时结束。 * throws [IOException] 当网络请求失败时抛出。 * throws [SecurityException] 当API密钥无效或缺失时抛出。 */ fun sendMessageAndSave( conversationId: String, userMessage: String ): FlowString flow { // ... 方法实现 }.flowOn(dispatcher) }8. 延伸思考从文本到语音交互将ChatGPT集成到安卓应用后一个很自然的延伸就是加入语音交互能力。想象一下用户可以直接说话应用将其转为文字发送给AI再将AI的文字回复用语音读出来这体验就完全不一样了。安卓原生提供了SpeechRecognizer类来实现语音识别。你可以这样规划语音输入利用SpeechRecognizer监听用户语音实时或结束后将识别结果送入你的ChatRepository。上下文处理将识别出的文本作为用户消息调用已有的对话流程。语音输出收到AI的文本回复后使用TextToSpeech引擎将其朗读出来。你可以选择系统TTS引擎或集成更高质量的第三方语音合成SDK。这相当于为你的AI应用装上了“耳朵”和“嘴巴”。不过这又会引入新的挑战比如语音识别的准确率、环境噪音处理、TTS的延迟和音质以及更复杂的交互状态管理监听中、思考中、播放中。如果你对构建这样一个能听会说的AI应用感兴趣觉得从零开始整合语音识别、大语言模型和语音合成很有挑战性那么可以了解一下火山引擎提供的现成解决方案。他们有一个从0打造个人豆包实时通话AI的动手实验这个实验不是简单的API调用演示而是带你完整地走一遍构建实时语音对话应用的流程从语音识别ASR接入到调用大模型LLM生成回复再到语音合成TTS播放形成一个完整的闭环。对于想快速实现语音交互功能或者希望学习如何将多种AI能力有机组合起来的开发者来说是个非常不错的实践入口。我体验后发现它把很多底层的复杂工作比如音频编解码、实时传输、多模块协同都封装好了让你能更专注于核心交互逻辑和体验优化上上手速度比自己从头折腾快多了。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…