Android Camera capture

想了下還是擠擠時間,把相機這基礎流程寫完吧,前面每篇寫的都還是挺耗時的(就是累了,想偷偷懶,哈哈哈哈),那接著前面的幾篇文章,給這一些列寫上一個中規中矩的結局吧~

APP層

以下是相機openCamera,configStream,startPreview,capture全過程的示例代碼,不用再分段演示了,注釋也寫的比較詳細了。

package com.example.simplecamera;import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceTexture;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.FrameLayout;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;public class MainActivity extends AppCompatActivity {// 權限請求碼private static final int REQUEST_CAMERA_PERMISSION = 100;// 相機相關變量private String mCameraId;private CameraDevice mCameraDevice;private CameraCaptureSession mCaptureSession;private CaptureRequest.Builder mPreviewRequestBuilder;private Size mPreviewSize;private ImageReader mImageReader;// UI組件private TextureView mTextureView;private Button mCaptureButton;private TextView mStatusText;// 后臺線程private HandlerThread mBackgroundThread;private Handler mBackgroundHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化UImTextureView = new TextureView(this);FrameLayout previewContainer = findViewById(R.id.preview_container);previewContainer.addView(mTextureView);mCaptureButton = findViewById(R.id.capture_button);mStatusText = findViewById(R.id.status_text);// 設置拍照按鈕點擊事件mCaptureButton.setOnClickListener(v -> takePicture());mCaptureButton.setEnabled(false);// 設置TextureView監聽mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}// SurfaceTexture監聽,用于處理預覽準備就緒private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {// 檢查權限if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},REQUEST_CAMERA_PERMISSION);return;}// 權限已授予,打開相機openCamera(width, height);}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return true;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};// 打開相機private void openCamera(int width, int height) {startBackgroundThread();// 獲取相機管理器CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);try {// 遍歷所有相機,選擇后置攝像頭for (String cameraId : manager.getCameraIdList()) {CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);// 只使用后置攝像頭Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {continue;}// 獲取可用的輸出尺寸StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {continue;}// 選擇合適的預覽尺寸mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),width, height);// 初始化ImageReader用于拍照mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);mCameraId = cameraId;break;}// 打開相機if (mCameraId != null) {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {return;}manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);} else {showStatus("未找到可用相機");}} catch (CameraAccessException e) {showStatus("打開相機失敗: " + e.getMessage());e.printStackTrace();}}// 相機狀態回調private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;createCameraPreviewSession();runOnUiThread(() -> {mCaptureButton.setEnabled(true);showStatus("相機已就緒");});}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {camera.close();mCameraDevice = null;runOnUiThread(() -> {mCaptureButton.setEnabled(false);showStatus("相機已斷開");});}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {camera.close();mCameraDevice = null;runOnUiThread(() -> {mCaptureButton.setEnabled(false);showStatus("相機錯誤: " + error);});}};// 創建相機預覽會話private void createCameraPreviewSession() {try {SurfaceTexture texture = mTextureView.getSurfaceTexture();if (texture == null) {throw new NullPointerException("TextureView為null");}// 設置預覽尺寸texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());// 創建預覽的SurfaceSurface surface = new Surface(texture);// 創建預覽請求mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewRequestBuilder.addTarget(surface);// 創建相機捕獲會話mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCaptureSession = session;try {// 設置自動對焦和自動曝光mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 開始預覽mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),null, mBackgroundHandler);} catch (CameraAccessException e) {showStatus("預覽失敗: " + e.getMessage());e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {showStatus("預覽配置失敗");}}, null);} catch (CameraAccessException e) {showStatus("創建會話失敗: " + e.getMessage());e.printStackTrace();}}// 拍照private void takePicture() {if (mCameraDevice == null || mCaptureSession == null) {Log.w(TAG, "Cannot take picture - camera not ready");return;}try {// 創建拍照請求final CaptureRequest.Builder captureBuilder =mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mImageReader.getSurface());// 設置自動對焦和曝光captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_START);captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 設置照片方向int rotation = getWindowManager().getDefaultDisplay().getRotation();captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);} catch (CameraAccessException e) {showStatus("拍照失敗: " + e.getMessage());e.printStackTrace();}}// 處理拍攝的圖片private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {// 在后臺處理圖片mBackgroundHandler.post(() -> {Image image = reader.acquireNextImage();if (image != null) {File imageFile = saveImageToFile(image);String message = imageFile != null ? "照片已保存: " + imageFile.getAbsolutePath() : "保存照片失敗";runOnUiThread(() -> showStatus(message));image.close();}});}};// 保存圖片到文件private File saveImageToFile(Image image) {ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);// 創建圖片文件String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());String fileName = "IMG_" + timeStamp + ".jpg";File storageDir = getExternalFilesDir(null);File imageFile = new File(storageDir, fileName);FileOutputStream output = null;try {output = new FileOutputStream(imageFile);output.write(bytes);return imageFile;} catch (IOException e) {e.printStackTrace();return null;} finally {if (output != null) {try {output.close();} catch (IOException e) {e.printStackTrace();}}}}// 選擇最佳的預覽尺寸private Size chooseOptimalSize(Size[] choices, int width, int height) {List<Size> bigEnough = new ArrayList<>();for (Size option : choices) {if (option.getHeight() == option.getWidth() * height / width &&option.getWidth() >= width && option.getHeight() >= height) {bigEnough.add(option);}}if (bigEnough.size() > 0) {return Collections.min(bigEnough, new CompareSizesByArea());} else {return choices[0];}}// 按面積比較尺寸的比較器private static class CompareSizesByArea implements Comparator<Size> {@Overridepublic int compare(Size lhs, Size rhs) {return Long.signum((long) lhs.getWidth() * lhs.getHeight() -(long) rhs.getWidth() * rhs.getHeight());}}// 獲取照片方向private int getOrientation(int rotation) {switch (rotation) {case Surface.ROTATION_0: return 90;case Surface.ROTATION_90: return 0;case Surface.ROTATION_180: return 270;case Surface.ROTATION_270: return 180;default: return 90;}}// 啟動后臺線程private void startBackgroundThread() {mBackgroundThread = new HandlerThread("CameraBackground");mBackgroundThread.start();mBackgroundHandler = new Handler(mBackgroundThread.getLooper());}// 停止后臺線程private void stopBackgroundThread() {mBackgroundThread.quitSafely();try {mBackgroundThread.join();mBackgroundThread = null;mBackgroundHandler = null;} catch (InterruptedException e) {e.printStackTrace();}}// 顯示狀態信息private void showStatus(String message) {runOnUiThread(() -> {mStatusText.setText(message);Toast.makeText(this, message, Toast.LENGTH_SHORT).show();});}// 權限請求結果處理@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUEST_CAMERA_PERMISSION) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 權限已授予,重新嘗試打開相機if (mTextureView.isAvailable()) {openCamera(mTextureView.getWidth(), mTextureView.getHeight());}} else {showStatus("需要相機權限才能使用應用");}}}@Overrideprotected void onResume() {super.onResume();startBackgroundThread();if (mTextureView.isAvailable()) {openCamera(mTextureView.getWidth(), mTextureView.getHeight());} else {mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}}@Overrideprotected void onPause() {closeCamera();stopBackgroundThread();super.onPause();}// 關閉相機private void closeCamera() {if (mCaptureSession != null) {mCaptureSession.close();mCaptureSession = null;}if (mCameraDevice != null) {mCameraDevice.close();mCameraDevice = null;}if (mImageReader != null) {mImageReader.close();mImageReader = null;}}
}

需要明確拍照過程是基于打開相機,配流之后的一個流程,所以點擊拍照按鈕進行拍照流程的時候需要判斷相機連接,配流過程是否正常,即需要判斷

    if (mCameraDevice == null || mCaptureSession == null) {Log.w(TAG, "Cannot take picture - camera not ready");return;}

如果都正常了,那就開始本文的關鍵流程分析,如下:

    mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);

Framework

由前面的文章介紹,我們就知道mCaptureSession其實就是CameraCaptureSessionImpl實例化對象
,所以繼續看
frameworks/base/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java

    @Overridepublic int capture(CaptureRequest request, CaptureCallback callback,Handler handler) throws CameraAccessException {checkCaptureRequest(request);synchronized (mDeviceImpl.mInterfaceLock) {checkNotClosed();handler = checkHandler(handler, callback);if (DEBUG) {Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback +" handler " + handler);}//看到這里是不是很熟悉,是不是跟上一篇講解setRepeatingRequest過程很像//即主要看參數的函數調用mDeviceImpl.capturereturn addPendingSequence(mDeviceImpl.capture(request,createCaptureCallbackProxy(handler, callback), mDeviceExecutor));}}

這個時候就接著跳轉到CameraDeviceImpl類看capture()實現了
frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java

    public int capture(CaptureRequest request, CaptureCallback callback, Executor executor)throws CameraAccessException {if (DEBUG) {Log.d(TAG, "calling capture");}List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();requestList.add(request);return submitCaptureRequest(requestList, callback, executor, /*streaming*/false);}

然后直接看下一步,關鍵流程已加注釋

    private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,Executor executor, boolean repeating) throws CameraAccessException {//這里帶的參數是false,說明了拍照的時候是不需要停預覽的,上一篇起預覽的時候//這里是true,是必定要停止上一次預覽連接的if (repeating) {stopRepeating();}//以下是關鍵函數requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);return requestInfo.getRequestId();}

通過binder調用到這里了
frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp

binder::Status CameraDeviceClient::submitRequestList(const std::vector<hardware::camera2::CaptureRequest>& requests,bool streaming,/*out*/hardware::camera2::utils::SubmitInfo *submitInfo) {if (streaming) {//預覽環節,上一章跟蹤的這個流程err = mDevice->setStreamingRequestList(metadataRequestList, surfaceMapList,&(submitInfo->mLastFrameNumber));} else {//拍照環節,這章跟蹤這一個流程err = mDevice->captureList(metadataRequestList, surfaceMapList,&(submitInfo->mLastFrameNumber));}return res;
}

繼續
frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp

status_t Camera3Device::captureList(const List<const PhysicalCameraSettingsList> &requestsList,const std::list<const SurfaceMap> &surfaceMaps,int64_t *lastFrameNumber) {ATRACE_CALL();return submitRequestsHelper(requestsList, surfaceMaps, /*repeating*/false, lastFrameNumber);
}

注意這個時候傳的repeating是false了

status_t Camera3Device::submitRequestsHelper(const List<const PhysicalCameraSettingsList> &requests,const std::list<const SurfaceMap> &surfaceMaps,bool repeating,/*out*/int64_t *lastFrameNumber) {if (repeating) {//預覽流程res = mRequestThread->setRepeatingRequests(requestList, lastFrameNumber);} else {//拍照流程res = mRequestThread->queueRequestList(requestList, lastFrameNumber);}return res;}
status_t Camera3Device::RequestThread::queueRequestList(List<sp<CaptureRequest> > &requests,/*out*/int64_t *lastFrameNumber) {ATRACE_CALL();Mutex::Autolock l(mRequestLock);for (List<sp<CaptureRequest> >::iterator it = requests.begin(); it != requests.end();++it) {//添加到請求隊列mRequestQueue.push_back(*it);}if (lastFrameNumber != NULL) {*lastFrameNumber = mFrameNumber + mRequestQueue.size() - 1;ALOGV("%s: requestId %d, mFrameNumber %" PRId32 ", lastFrameNumber %" PRId64 ".",__FUNCTION__, (*(requests.begin()))->mResultExtras.requestId, mFrameNumber,*lastFrameNumber);}unpauseForNewRequests();return OK;
}

然后回到RequestThread

bool Camera3Device::RequestThread::threadLoop() //省略n行代碼,以下流程跟預覽流程一致,只提出來關鍵函數waitForNextRequestBatch();if (mNextRequests.size() == 0) {return true;}res = prepareHalRequests();submitRequestSuccess = sendRequestsBatch();return submitRequestSuccess;      
}

hardware/qcom/camera/msm8998/QCamera2/HAL3/QCamera3HWI.cpp

int QCamera3HardwareInterface::processCaptureRequest(camera3_capture_request_t *request,List<InternalRequest> &internallyRequestedStreams)
{
//省略內容
}

這咋跟預覽一樣一樣的,很明顯,我!跟!丟!了!
回去!

frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp

status_t Camera3Device::submitRequestsHelper(const List<const PhysicalCameraSettingsList> &requests,const std::list<const SurfaceMap> &surfaceMaps,bool repeating,/*out*/int64_t *lastFrameNumber) {if (repeating) {//預覽流程res = mRequestThread->setRepeatingRequests(requestList, lastFrameNumber);} else {//拍照流程res = mRequestThread->queueRequestList(requestList, lastFrameNumber);}return res;}

仔細觀察一下這里的實現有什么不一樣呢
從上面代碼可以發現capture流程會被放在mRequestQueue里;repeating 流程會放在mRepeatingRequests,在threadLoop() 的waitForNextRequestBatch() 可以知道,capture流程會先塞進請求隊列里面

但好像只知道這些也不足以hal層區分拍照和預覽啊,其實主要還有一個點漏了,就是APP請求拍照時候,還設置了請求的參數,其實這個就是區分的關鍵
拍照:

// 創建拍照請求
final CaptureRequest.Builder captureBuilder =mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)

預覽:

// 創建預覽請求
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

下發鏈路可概括為:
應用層設置意圖 → Framework 層(CameraService)校驗、打包為 camera3_capture_request_t → 通過 HIDL 接口跨進程傳遞到 HAL → HAL 層解析元數據識別預覽,拍照意圖。

整個過程中,Framework 層負責元數據的序列化和跨進程傳遞,HAL 層通過解析元數據中的具體值(1 對應 PREVIEW)來區分請求類型,最終路由到對應的處理邏輯,本章就說這么多了,其他的如驅動控制,數據編碼,數據回傳等后面有空再搞了,困了。

大概總結一下拍照全流程:
應用層(發起拍照請求)→ 框架層(校驗/封裝請求)→ HAL層(解析/配置硬件)→ 驅動層(控制傳感器)→ 硬件(采集/處理數據)
→ 驅動層(傳輸數據)→ HAL層(寫入緩沖區)→ 框架層(通知應用)→ 應用層(讀取/保存數據)

簡單看了下應該沒啥大問題,睡覺!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93505.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93505.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93505.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

落霞歸雁思維框架應用(十) ——在職考研 199 管綜 + 英語二 30 周「順水行舟」上岸指南

落霞歸雁思維框架應用&#xff08;十&#xff09; ——在職考研 199 管綜 英語二 30 周「順水行舟」上岸指南 CSDN 首發 | 作者&#xff1a;落霞歸雁 | 2025-08-01 開場&#xff1a;把 199英二 從“兩座大山”變成“兩條順流” 在職黨最怕兩句話&#xff1a; “管綜題量太大…

自動調優 vLLM 服務器參數(實戰指南)

1. 操作步驟 該腳本能夠自動搜索最優的vLLM服務器參數組合&#xff08;包括max-num-seqs和max-num-batched-tokens&#xff09;&#xff0c;在滿足端到端延遲和前綴緩存命中率等要求的同時&#xff0c;實現吞吐量最大化。 1.1 前提條件 克隆 vLLM 并切到目標分支git clone http…

【騰訊云】EdgeOne免費版實現網站加速與安全防護

作為一名個人站長&#xff0c;我深知在有限的預算下如何為網站提供穩定、快速的服務是關鍵。騰訊云推出了 EdgeOne免費版 https://edgeone.ai/zh/redemption已經有一段時間。結合我的實際體驗&#xff0c;我想從國內備案域名的角度&#xff0c;分享一下如何利用這個工具提升網站…

修改gitlab默認的語言

文章目錄網上的方法1. 采用數據庫觸發器的方法2. 登錄pg庫2.1 查看表2.2 創建function2.3 創建觸發器2.4 修改歷史數據網上的方法 網上修改/opt/gitlab/embedded/service/gitlab-rails/config/application.rb的方法&#xff0c;我試了&#xff0c;沒生效&#xff0c;沒進一步研…

ndk { setAbiFilters([‘armeabi-v7a‘, “arm64-v8a“]) }

deepseek回答&#xff1a; 好的&#xff0c;我現在需要解釋build.gradle中這段ndk配置代碼的含義。首先&#xff0c;用戶提供的代碼片段是&#xff1a; ndk { setAbiFilters([‘armeabi-v7a’, “arm64-v8a”]) } 我的第一步是理解每個部分的作用。ndk塊在Android構建文件中用于…

k8s 1.30 通過helm部署ingress-controller-4.12.1

一、基本概念k8s集群中&#xff0c;ingress作為集群內服務對外暴漏的訪問接入點&#xff0c;幾乎承載著集群內服務訪問的所有流量。ingress是k8s中的一個資源對象&#xff0c;用來管理集群外部訪問集群內部服務的方式。可以通過ingress資源來配置不同的轉發規則&#xff0c;從而…

Gitee:本土開發者生態的崛起與數字化轉型新引擎

Gitee&#xff1a;本土開發者生態的崛起與數字化轉型新引擎 在數字化轉型浪潮席卷全球的當下&#xff0c;代碼托管平臺已從單純的代碼存儲工具演變為企業技術創新的基礎設施。作為中國領先的一站式DevOps平臺&#xff0c;Gitee以其本地化優勢和創新功能矩陣&#xff0c;正重新定…

Servlet HTTP 狀態碼詳解

Servlet HTTP 狀態碼詳解 引言 在Web開發中,HTTP狀態碼是服務器響應客戶端請求時返回的狀態信息。這些狀態碼有助于開發者了解請求處理的結果,并針對不同的狀態碼進行相應的處理。Servlet作為Java Web開發的重要技術之一,理解HTTP狀態碼對于開發高質量的Web應用至關重要。…

ubuntu qt環境下出現No suitable kits found解決方案

1. 清理 Qt Creator 緩存Qt Creator 會緩存項目配置、索引等數據&#xff0c;可能導致某些異常。清理方法&#xff1a;(1) 刪除 Qt Creator 配置目錄bashrm -rf ~/.config/QtProject/&#xff08;Ubuntu/Linux&#xff09; 或 Windows&#xff1a;cmdrmdir /s /q "%APPDAT…

【保姆級喂飯教程】Python依賴管理工具大全:Virtualenv、venv、Pipenv、Poetry、pdm、Rye、UV、Conda、Pixi等

目錄前言1前言2一、包管理工具1. pip&#xff08;Python官方&#xff0c;2008&#xff09;二、虛擬環境工具1. virtualenv&#xff08;Ian Bicking&#xff0c;2007&#xff09;2. venv&#xff08;Python3.3&#xff0c;2012&#xff09;三、版本管理工具1. pyenv&#xff08;…

Linux進程概念(五)進程地址空間

地址空間排布這段空間中自上而下&#xff0c;地址是增長的&#xff0c;棧是向地址減小方向增長&#xff0c;里面存放函數中的臨時變量&#xff0c;而堆是向地址增長方向增長&#xff0c;malloc開辟的地址空間存放在堆區&#xff0c;堆棧之間的共享區域&#xff0c;主要用來加載…

Go語言實戰案例-判斷二叉樹是否對稱

給定一棵二叉樹&#xff0c;判斷這棵樹是否是對稱的。對稱的含義是&#xff1a;這棵樹的左子樹和右子樹在結構上是鏡像對稱的&#xff0c;且對應節點的值相等。示例 1&#xff1a;1/ \2 2/ \ / \ 3 4 4 3輸出&#xff1a;true示例 2&#xff1a;1/ \2 2\ \3 3輸出&a…

【機器學習深度學習】為什么需要分布式訓練?

目錄 前言 一、模型規模爆炸&#xff1a;單卡GPU已難以承載 1.1 問題描述 1.2 面臨挑戰 1.3 解決方案&#xff1a;模型并行 (Model Parallelism) 1.4 類比理解&#xff1a;模型并行 1.5 模型并行的關鍵點 1.6 模型并行&#xff08;Model Parallelism&#xff09;的流程…

二十八、【Linux系統域名解析】DNS安裝、子域授權、緩存DNS、分離解析、多域名解析

DNS服務深度解析&#xff1a;緩存、分離與多域名管理一、DNS服務架構全景DNS核心組件關系DNS服務器類型對比二、基礎DNS服務配置1. Bind9核心配置文件2. 區域文件結構解析區域文件記錄類型表三、子域授權與分層解析子域授權原理子域配置流程1. 父域配置2. 子域配置遞歸與迭代查…

【LeetCode】前綴表相關算法

目錄1、介紹2、核心概念【1】前綴和后綴【2】最長公共前后綴&#xff08;LPS&#xff09;3、相關算法題【1】找出字符串中第一個匹配項的下標【2】重復的子字符串1、介紹 前綴表是一種在字符串匹配算法&#xff08;特別是KMP算法&#xff09;中使用的數據結構&#xff0c;用于…

(六) Spring AI 1.0版本 + 千問大模型+RAG

上篇文章我們大概講了一下向量模型的知識&#xff0c;本篇文章&#xff0c;我們將會通過RAG實戰的形式&#xff0c;來感受一下RAG。 項目準備 pom.xml 這里我們需要引入向量庫和pdf相關的包<dependency><groupId>org.springframework.ai</groupId><artifa…

Spring Boot與Mybatis-Plus集成SQLServer的完整指南

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;本項目旨在演示如何將SQLServer與Spring Boot以及Mybatis-Plus框架進行整合&#xff0c;打造一個高效穩定的后端服務。詳細介紹涉及了數據庫連接、實體類定義、Mapper接口創建、Service層業務邏輯編寫、Control…

【工作筆記】判斷一條方法需不需要事務/AOP

① 看注解方法/類上有 Transactional → 需要事務&#xff0c;必須走代理方法/類上有自定義 AOP 注解&#xff08;如 Log、Retry、Cacheable 等&#xff09;→ 需要代理什么都沒有 → 幾乎肯定不需要示例需求Transactional public void generateDailyTask(...)? 需要事務publi…

Unity 的UI動畫調節

在游戲開發中&#xff0c;精美的UI動畫能極大提升用戶體驗。Unity提供了強大的動畫系統&#xff0c;讓開發者可以輕松創建流暢的界面動效。本文將介紹UI動畫的核心概念、制作流程和實用技巧。一、核心動畫組件Animation窗口 - 可視化創建關鍵幀動畫Animator組件 - 控制動畫狀態…

26考研11408數據結構

數據結構 1.緒論1.1.1數據結構的基本概念 數據數據元素&#xff1a;數據的基本單位&#xff0c;一個數據元素由多個數據項組成&#xff0c;數據項是組成數據元素不可分割的最小單位數據對象&#xff1a;具有相同性質的數據元素的集合&#xff0c;是數據的一個子集數據類型&…