CameraX 學習筆記

由於項目中自定義的相機經常報奇葩bug,決定學習下 CameraX。通過查看谷歌的官方 Demo ,學到了不少知識,做了這份筆記。
下面是相機的使用步驟及方法解釋等。

CameraX 使用


添加依賴

   // CameraX core library
    def camerax_version = '1.0.0-beta04'
    implementation "androidx.camera:camera-core:$camerax_version"
    // CameraX Camera2 extensions
    implementation "androidx.camera:camera-camera2:$camerax_version"
    // CameraX Lifecycle library
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    // CameraX View class
    implementation 'androidx.camera:camera-view:1.0.0-alpha11'

xml 佈局

<!-- 建議寬高都用 match -->
<androidx.camera.view.PreviewView
       android:id="@+id/view_finder"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
// 設置預覽界面的對齊方式,默認 FILL_CENTER 等比例鋪滿 previewView
viewFinder.scaleType = PreviewView.ScaleType.FIT_CENTER

用到的主要類解釋

  • 開始的地方:創建 Camera 對象
    Camera = ProcessCameraProvider.bindToLifecycle(LifecycleOwner, 
    								CameraSelector, Preview, ImageCapture, ImageAnalysis);
    

涉及對象解釋:

  • ProcessCameraProvider 一個單例,用於將相機的生命週期綁定到任何 {@link LifecycleOwner} 中。
  • Camera 綁定用例後返回的camera對象,這個對象基本沒用
  • LifecycleOwner 與 activity/fragment 生命週期綁定
  • CameraSelector 相機選擇器,用於選擇前後置相機
  • Preview 相機預覽,通過preview.setSurfaceProvider() 與 PreviewView 綁定實現預覽
  • ImageCapture 拍照,拍照由這個對象觸發和監聽
  • ImageAnalysis 數據解析,實時監聽相機圖像數據

各類的初始化

  • 初始化 ProcessCameraProvider

    // 要求指定compileOptions Java8, 否則這個方法會拋異常 
    val cameraProviderFuture: ListenableFuture<ProcessCameraProvider!> = ProcessCameraProvider.getInstance(context)
    // 當 ProcessCameraProvider 初始化完成會調用 listener
    cameraProviderFuture.addListener(Runnable {
    	val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
    	// 在綁定前先要解綁
    	cameraProvider.unbindAll()
    	// 這個方法建議try catch, 因爲cameraSelector配置不對或重複綁定等都會拋異常
    	camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalysis)
    }, ContextCompat.getMainExecutor(context))
    
  • 初始化 CameraSelector

    val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
    
  • 初始化 Preview

    val preview = Preview.Builder()
    			// 設置了寬高比率,不能再設置分辨率
    			.setTargetAspectRatio(AspectRatio.RATIO_16_9)
    			// .setTargetResolution(Size(1080, 1920))
    			// 設置
    			.setTargetRotation(viewFinder.getDisplay().getRotation())
    			.build()
    //設置 SurfaceProvider
    preview.setSurfaceProvider(viewFinder.createSurfaceProvider())
    
  • 初始化 ImageCapture

    val imageCapture = ImageCapture.Builder()
    				.setTargetAspect(AspectRatio.RATIO_16_9)
    				// .setTargetResolution(Size(1080, 1920))
    				.setRotation(viewFinder.getDisplay().getRataton())
    				// 閃光燈默認關閉 
    				.setFlashMode(ImageCapture.FLASH_MODE_AUTO)
    				.build()
    // 注意:這個方法可以做到預覽與照片的比率和顯示區域完全一致
    imageCapture.setCropAspectRatio(Rational(viewFinder.width, viewFinder.height))
    
    
  • 初始化 ImageAnalysis

    val imageAnalysis = ImageAnalysis.Builder()
    				.setTargetAspect(AspectRatio.RATIO_16_9)
    				// .setTargetResolution(Size(1080, 1920))
    				.setRotation(viewFinder.getDisplay().getRataton())
    				.build()
    // 第一個參數爲線程池,指定 Analyzer 接口回調的線程;第二個參數 Analyzer 接口對象
    imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), 實現 Analyzer 接口)
    

拍照功能 ImageCapure

拍照有兩種方式,一種直接將圖片保存到指定位置; 另一種返回 ImageProxy 對象,裏面包含圖片信息。看下面代碼演示:

  1. 直接將圖片保存到指定位置
    // 參數1: 指定輸出的圖片路徑 + metadata等;參數2: 線程池,指定拍照成功或失敗的回調線程; 參數3: 拍照成功或失敗的回調
    imageCapture.takePicture(ImageCapture.OutputFileOptions, Executor, ImageCapture.OnImageSavedCallback)
    

對應參數的初始化:

  • 初始化 ImageCapture.OutputFileOption

    //創建存儲路徑
    val file = getPhotoFile();
    val metadata = ImageCature.Metadata().apply {
    	// 當爲前置攝像頭時鏡像;前置攝像頭預覽時默認就是鏡像
    	isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
    	// 還可設置 Location
    	// location = 
    }
    val outputOption = ImageCapture.OutputFileOption.Builder(file)
    		.setMetadata(metadata)
    		.build()
    
  • 初始化 ImageCapture.OnImageSavedCallback

    val callback = object : ImageCapture.OnImageSavedCallback {
           override fun onImageSaved(output: ImageCapture.OutputFileResults) {
           	// 通過 savedUri 獲取拍照結果
               val savedUri: Uri = output.savedUri ?: Uri.fromFile(file)
               //注意:這個回調方法在子線程,即上面傳入的 Executor 線程
           }
    
           override fun onError(exc: ImageCaptureException) {
               Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
               Toast.makeText(requireContext(), exc.message, Toast.LENGTH_LONG).show()
           }
    
       }
    
  1. 自己處理圖像數據 ImageCapture.OnImageCapturedCallback
    • 主方法

      // 參數1: 線程池, 拍照成功或失敗的回調在這個線程執行; 參數2: 拍照成功或失敗的回調
      imageCapture.takePicture(Executor, ImageCapture.OnImageCapturedCallback)
      
    • 初始化 ImageCapture.OnImageCapturedCallback

      val callback = object : ImageCapture.OnImageCapturedCallback() {
      	override fun onCaptureSuccess(image: ImageProxy) {
                    // 將 imageProxy 轉爲 byte數組
                    val buffer: ByteBuffer = image.planes[0].buffer
                    // 新建指定長度數組
                    val byteArray = ByteArray(buffer.remaining())
                    // 倒帶到起始位置 0
                    buffer.rewind()
                    // 數據複製到數組, 這個 byteArray 包含有 exif 相關信息,
                    // 由於 bitmap 對象不會包含 exif 信息,所以轉爲 bitmap 需要注意保存 exif 信息
                    buffer.get(byteArray)
      
      
                    val imageView = container.findViewById<ImageView>(R.id.image_view)
                    // 主線程更新 UI
                    imageView.post{
                        Glide.with(imageView)
                                .load(byteArray)
                                .into(imageView)
                    }
      
                    image.close()
                }
      
                override fun onError(exc: ImageCaptureException) {
                    Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                }
      }
      

額外知識


  • FragmentContainerView
    用於容納動態添加的 Fragment 的容器,可替代使用 FrameLayout 等,因爲它解決了動畫 z 排序問題以及分派給 Fragment 的窗口邊襯區的問題。

  • 取消activity旋轉動畫
    android:rotationAnimation=“seamless”

  • 各個方法對於的目錄:當目錄不存在時還會自動創建

    //外部存儲
    getExternalCacheDirs: /storage/emulated/0/Android/data/com.example.cameraxstudy/cache
    getObbDir: /storage/emulated/0/Android/obb/com.example.cameraxstudy
    getExternalMediaDirs: /storage/emulated/0/Android/media/com.example.cameraxstudy
    getExternalCacheDir: /storage/emulated/0/Android/data/com.example.cameraxstudy/cache
    // 內部存儲
    getCacheDir: /data/user/0/com.example.cameraxstudy/cache
    getFilesDir: /data/user/0/com.example.cameraxstudy/files
    getDataDir: /data/user/0/com.example.cameraxstudy
    getCodeCacheDir: /data/user/0/com.example.cameraxstudy/code_cache
    getNoBackupFilesDir: /data/user/0/com.example.cameraxstudy/no_backup
    
    
  • 分享圖片

    var mediaFile: File = getMediaFile()
    val intent = Intent().apply {
        // 從文件擴展名推斷媒體類型
        val mediaType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(mediaFile.extension)
        // 從我們的FileProvider實現中獲取URI
        val uri = FileProvider.getUriForFile(
                view.context, BuildConfig.APPLICATION_ID + ".provider", mediaFile)
        // 設置適當的 intent extra, type, action and flags
        putExtra(Intent.EXTRA_STREAM, uri)
        type = mediaType
        action = Intent.ACTION_SEND
        flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    }
    
    // 啓動意圖,讓用戶選擇要共享的應用程序
    startActivity(Intent.createChooser(intent, getString(R.string.share_hint)))
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章