主頁 > 移動端開發 > Android自定義Camera2相機

Android自定義Camera2相機

2021-04-17 11:25:37 移動端開發

在這里插入圖片描述

Android自定義Camera2相機

轉載請標明出處:https://blog.csdn.net/ZhijunHong/article/details/115730693,謝謝~

寫在前面

Google從Android 5.0 L(API 21) 版本,開始引入Camera2(android.hardware.camera2)以取代Camera1(android.hardware.Camera)相機框架,

Camera2相比于之前的Camera1架構完全不同,使用起來比較復雜,與此同時功能也變得非常強大,

此篇博客,能夠幫助你快速構建并理解自定義Camera2相機的關鍵步驟,

完整代碼,請移步:https://github.com/zhijunhong/custom_camera/tree/master/camera2

使用Camera2的優點

通過設計框架的改造和優化,Camera2具備了以下優點:

  1. 改進了新硬體的性能,使用更先進的API架構;
  2. 可以獲取更多的幀(預覽/拍照)資訊以及手動控制每一幀的引數;
  3. 對Camera的控制更加完全(比如支持調整focus distance, 剪裁預覽/拍照圖片);
  4. 支持更多圖片格式(yuv/raw)以及高速連拍;

自定義Camera2相機

一些概念

1. Pipeline

Camera2的API模型被設計成一個 Pipeline(管道),它按順序處理每一幀的請求并回傳請求結果給客戶端,下面這張來自官方的圖展示了Pipeline的作業流程,我們會通過一個簡單的例子詳細解釋這張圖,

在這里插入圖片描述

Pipeline示意圖

為了解釋上面的示意圖,假設我們想要同時拍攝兩張不同尺寸的圖片,并且在拍攝的程序中閃光燈必須亮起來,整個拍攝流程如下:

  1. 創建一個用于從Pipeline獲取圖片的CaptureRequest;
  2. 修改CaptureRequest的閃光燈配置,讓閃光燈在拍照程序中亮起來;
  3. 創建兩個不同尺寸的Surface用于接收圖片資料,并且將它們添加到CaptureRequest中;
  4. 發送配置好的CaptureRequest到Pipeline中等待它回傳拍照結果,

一個新的CaptureRequest會被放入一個被稱作Pending Request Queue的佇列中等待被執行,當In-Flight Capture Queue佇列空閑的時候就會從Pending Request Queue獲取若干個待處理的CaptureRequest,并且根據每一個CaptureRequest 的配置進行Capture操作,最后我們從不同尺寸的Surface中獲取圖片資料并且還會得到一個包含了很多與本次拍照相關的資訊的CaptureResult,流程結束,

2. Supported Hardware Level

相機功能的強大與否和硬體息息相關,不同廠商對 Camera2 的支持程度也不同,所以Camera2定義了一個叫做Supported Hardware Level的重要概念,其作用是將不同設備上的Camera2根據功能的支持情況劃分成多個不同級別以便開發者能夠大概了解當前設備上Camera2的支持情況,截止到Android P為止,從低到高一共有LEGACY、LIMITED、FULL 和 LEVEL_3四個級別:

  1. LEGACY:向后兼容的級別,處于該級別的設備意味著它只支持Camera1的功能,不具備任何Camera2高級特性;
  2. LIMITED:除了支持Camera1的基礎功能之外,還支持部分Camera2高級特性的級別;
  3. FULL:支持所有Camera2的高級特性;
  4. LEVEL_3:新增更多Camera2高級特性,例如YUV資料的后處理等,

3. Capture

相機的所有操作和引數配置最終都是服務于影像捕獲,例如對焦是為了讓某一個區域的影像更加清晰,調節曝光補償是為了調節影像的亮度等,因此,在Camera2 里面所有的相機操作和引數配置都被抽象成Capture(捕獲),所以不要簡單的把Capture直接理解成是拍照,因為Capture操作可能僅僅是為了讓預覽畫面更清晰而進行對焦而已,如果你熟悉Camera,那你可能會問 setFlashMode() 在哪?setFocusMode() 在哪?takePicture() 在哪?告訴你,它們都是通過Capture 來實作的,

Capture從執行方式上又被細分為【單次模式】、【多次模式】和【重復模式】三種,我們來一一解釋下:

  • 單次模式(One-shot):指的是只執行一次的Capture操作,例如設定閃光燈模式、對焦模式和拍一張照片等,多個一次性模式的Capture會進入佇列按順序執行,
  • 多次模式(Burst):指的是連續多次執行指定的Capture操作,該模式和多次執行單次模式的最大區別是連續多次Capture期間不允許插入其他任何Capture 操作,例如連續拍攝100張照片,在拍攝這100張照片期間任何新的Capture請求都會排隊等待,直到拍完100張照片,多組多次模式的Capture會進入佇列按順序執行,
  • 重復模式(Repeating):指的是不斷重復執行指定的Capture操作,當有其他模式的Capture提交時會暫停該模式,轉而執行其他被模式的Capture,當其他模式的 Capture 執行完畢后又會自動恢復繼續執行該模式的Capture,例如顯示預覽畫面就是不斷 Capture 獲取每一幀畫面,該模式的 Capture 是全域唯一的,也就是新提交的重復模式Capture會覆寫舊的重復模式Capture,

關鍵API

CameraManagerCameraManager是一個負責查詢和建立相機連接的系統服務,它的功能不多,這里列出幾個CameraManager的關鍵功能:
1.將相機資訊封裝到CameraCharacteristics中,并提獲取CameraCharacteristics實體的方式;
2.根據指定的相機ID連接相機設備;
3.提供將閃光燈設定成手電筒模式的快捷方式,
CameraCharacteristicsCameraCharacteristics 是一個只讀的相機資訊提供者,其內部攜帶大量的相機資訊,包括代表相機朝向的 LENS_FACING;判斷閃光燈是否可用的 FLASH_INFO_AVAILABLE;獲取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等,如果你對Camera1比較熟悉,那么CameraCharacteristics有點像Camera1的 Camera.CameraInfo 或者 Camera.Parameters
CameraDeviceCameraDevice 代表當前連接的相機設備,它的職責有以下四個:
1.根據指定的引數創建 CameraCaptureSession;
2.根據指定的模板創建 CaptureRequest;
3.關閉相機設備;
4.監聽相機設備的狀態,例如斷開連接、開啟成功和開啟失敗等,
熟悉Camera1的人可能會說CameraDevice就是Camera1的 Camera 類,實則不是,Camera 類幾乎負責了所有相機的操作,而 CameraDevice 的功能則十分的單一,就是只負責建立相機連接的事務,而更加細化的相機操作則交給了稍后會介紹的CameraCaptureSession,
SurfaceSurface 是一塊用于填充影像資料的記憶體空間,例如你可以使用 SurfaceView 的 Surface 接收每一幀預覽資料用于顯示預覽畫面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 資料,每一個 Surface 都可以有自己的尺寸和資料格式,你可以從 CameraCharacteristics 獲取某一個資料格式支持的尺寸串列,
CameraCaptureSessionCameraCaptureSession 實際上就是配置了目標 Surface 的 Pipeline 實體,我們在使用相機功能之前必須先創建 CameraCaptureSession 實體,一個 CameraDevice 一次只能開啟一個 CameraCaptureSession,絕大部分的相機操作都是通過向 CameraCaptureSession 提交一個 Capture 請求實作的,例如拍照、連拍、設定閃光燈模式、觸摸對焦、顯示預覽畫面等,
CaptureRequestCaptureRequest 是向 CameraCaptureSession 提交 Capture 請求時的資訊載體,其內部包括了本次 Capture 的引數配置和接收影像資料的 Surface,CaptureRequest 可以配置的資訊非常多,包括影像格式、影像解析度、傳感器控制、閃光燈控制、3A 控制等,可以說絕大部分的相機引數都是通過 CaptureRequest 配置的,值得注意的是每一個 CaptureRequest 表示一幀畫面的操作,這意味著你可以精確控制每一幀的 Capture 操作,
CaptureResultCaptureResult 是每一次 Capture 操作的結果,里面包括了很多狀態資訊,包括閃光燈狀態、對焦狀態、時間戳等等,例如你可以在拍照完成的時候,通過 CaptureResult 獲取本次拍照時的對焦狀態和時間戳,需要注意的是,CaptureResult 并不包含任何影像資料,前面我們在介紹 Surface 的時候說了,影像資料都是從 Surface 獲取的,
ImageReader用于從相機打開的通道中讀取需要的格式的原始影像資料,可以設定多個ImageReader,

開發流程

在這里插入圖片描述

1.獲取CameraManager

private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }

2.獲取相機資訊

val cameraIdList = cameraManager.cameraIdList
cameraIdList.forEach { cameraId ->
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
    if (cameraCharacteristics.isHardwareLevelSupported(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)) {
        if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) {
            frontCameraId = cameraId
            frontCameraCharacteristics = cameraCharacteristics
        } else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) {
            backCameraId = cameraId
            backCameraCharacteristics = cameraCharacteristics
        }
    }
}

通過CameraManager獲取到所有攝像頭cameraId,通過回圈判斷是前攝像頭(CameraCharacteristics.LENS_FACING_FRONT)還是后攝像頭(CameraCharacteristics.LENS_FACING_BACK

3.初始化ImageReader

private var jpegImageReader: ImageReader? = null
jpegImageReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 5)
jpegImageReader?.setOnImageAvailableListener(OnJpegImageAvailableListener(), cameraHandler)

......

private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener {

  private val dateFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault())
  private val cameraDir: String = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)}/Camera"

  @WorkerThread
  override fun onImageAvailable(imageReader: ImageReader) {
    val image = imageReader.acquireNextImage()
    val captureResult = captureResults.take()
    if (image != null && captureResult != null) {
      image.use {
        val jpegByteBuffer = it.planes[0].buffer// Jpeg image data only occupy the planes[0].
        val jpegByteArray = ByteArray(jpegByteBuffer.remaining())
        jpegByteBuffer.get(jpegByteArray)
        val width = it.width
        val height = it.height
        saveImageExecutor.execute {
          val date = System.currentTimeMillis()
          val title = "IMG_${dateFormat.format(date)}"// e.g. IMG_20190211100833786
          val displayName = "$title.jpeg"// e.g. IMG_20190211100833786.jpeg
          val path = "$cameraDir/$displayName"// e.g. /sdcard/DCIM/Camera/IMG_20190211100833786.jpeg
          val orientation = captureResult[CaptureResult.JPEG_ORIENTATION]
          val location = captureResult[CaptureResult.JPEG_GPS_LOCATION]
          val longitude = location?.longitude ?: 0.0
          val latitude = location?.latitude ?: 0.0

          // Write the jpeg data into the specified file.
          File(path).writeBytes(jpegByteArray)

          // Insert the image information into the media store.
          val values = ContentValues()
          values.put(MediaStore.Images.ImageColumns.TITLE, title)
          values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, displayName)
          values.put(MediaStore.Images.ImageColumns.DATA, path)
          values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, date)
          values.put(MediaStore.Images.ImageColumns.WIDTH, width)
          values.put(MediaStore.Images.ImageColumns.HEIGHT, height)
          values.put(MediaStore.Images.ImageColumns.ORIENTATION, orientation)
          values.put(MediaStore.Images.ImageColumns.LONGITUDE, longitude)
          values.put(MediaStore.Images.ImageColumns.LATITUDE, latitude)
          contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

          // Refresh the thumbnail of image.
          val thumbnail = getThumbnail(path)
          if (thumbnail != null) {
            runOnUiThread {
              thumbnailView.setImageBitmap(thumbnail)
              thumbnailView.scaleX = 0.8F
              thumbnailView.scaleY = 0.8F
              thumbnailView.animate().setDuration(50).scaleX(1.0F).scaleY(1.0F).start()
            }
          }
        }
      }
    }
  }
}

ImageReader是獲取影像資料的重要途徑,通過它可以獲取到不同格式的影像資料,例如JPEG、YUV、RAW等,通過ImageReader.newInstance(int width, int height, int format, int maxImages)創建ImageReader物件,有4個引數:

  • width:影像資料的寬度
  • height:影像資料的高度
  • format:影像資料的格式,例如ImageFormat.JPEGImageFormat.YUV_420_888
  • maxImages:最大Image個數,Image物件池的大小,指定了能從ImageReader獲取Image物件的最大值,過多獲取緩沖區可能導致OOM,所以最好按照最少的需要去設定這個值

ImageReader其他相關的方法和回呼:

  • ImageReader.OnImageAvailableListener:有新影像資料的回呼
  • acquireLatestImage():從ImageReader的佇列里面,獲取最新的Image,洗掉舊的,如果沒有可用的Image,回傳null
  • acquireNextImage():獲取下一個最新的可用Image,沒有則回傳null
  • close():釋放與此ImageReader關聯的所有資源
  • getSurface():獲取為當前ImageReader生成Image的Surface

4.打開相機設備

val cameraStateCallback = CameraStateCallback()
cameraManager.openCamera(cameraId, cameraStateCallback, mainHandler)

......

 private inner class CameraStateCallback : CameraDevice.StateCallback() {
        @MainThread
        override fun onOpened(camera: CameraDevice) {
            cameraDeviceFuture!!.set(camera)
            cameraCharacteristicsFuture!!.set(getCameraCharacteristics(camera.id))
        }

        @MainThread
        override fun onClosed(camera: CameraDevice) {

        }

        @MainThread
        override fun onDisconnected(camera: CameraDevice) {
            cameraDeviceFuture!!.set(camera)
            closeCamera()
        }

        @MainThread
        override fun onError(camera: CameraDevice, error: Int) {
            cameraDeviceFuture!!.set(camera)
            closeCamera()
        }
    }

cameraManager.openCamera(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)的三個引數:

  • cameraId:攝像頭的唯一標識
  • callback:設備連接狀態變化的回呼
  • handler:回呼執行的Handler物件,傳入null則使用當前的主執行緒Handler

其中CameraStateCallback回呼:

  • onOpened:表示相機打開成功,可以真正開始使用相機,創建Capture會話
  • onDisconnected:當相機斷開連接時回呼該方法,需要進行釋放相機的操作
  • onError:當相機打開失敗時,需要進行釋放相機的操作
  • onClosed:呼叫Camera.close()后的回呼方法

5.創建Capture Session

val sessionStateCallback = SessionStateCallback()
......
val cameraDevice = cameraDeviceFuture?.get()
cameraDevice?.createCaptureSession(outputs, sessionStateCallback, mainHandler)

......

 private inner class SessionStateCallback : CameraCaptureSession.StateCallback() {
        @MainThread
        override fun onConfigureFailed(session: CameraCaptureSession) {
            captureSessionFuture!!.set(session)
        }

        @MainThread
        override fun onConfigured(session: CameraCaptureSession) {
            captureSessionFuture!!.set(session)
        }

        @MainThread
        override fun onClosed(session: CameraCaptureSession) {

        }
    }

這段的代碼核心方法是mCameraDevice.createCaptureSession()創建Capture會話,它接受了三個引數:

  • outputs:用于接受影像資料的surface集合,這里傳入的是一個preview的surface
  • callback:用于監聽 Session 狀態的CameraCaptureSession.StateCallback物件
  • handler:用于執行CameraCaptureSession.StateCallback的Handler物件,傳入null則使用當前的主執行緒Handler

6.創建CaptureRequest

CaptureRequest是向CameraCaptureSession提交Capture請求時的資訊載體,其內部包括了本次Capture的引數配置和接收影像資料的Surface

if (cameraDevice != null) {
  previewImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
  captureImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
}

......

val cameraDevice = cameraDeviceFuture?.get()
val captureSession = captureSessionFuture?.get()
val previewImageRequestBuilder = previewImageRequestBuilder!!
val captureImageRequestBuilder = captureImageRequestBuilder!!
if (cameraDevice != null && captureSession != null) {
  val previewSurface = previewSurface!!
  val previewDataSurface = previewDataSurface
  previewImageRequestBuilder.addTarget(previewSurface)
  // Avoid missing preview frame while capturing image.
  captureImageRequestBuilder.addTarget(previewSurface)
  if (previewDataSurface != null) {
    previewImageRequestBuilder.addTarget(previewDataSurface)
    // Avoid missing preview data while capturing image.
    captureImageRequestBuilder.addTarget(previewDataSurface)
  }
  val previewRequest = previewImageRequestBuilder.build()
  captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)
}

......

private inner class RepeatingCaptureStateCallback : CameraCaptureSession.CaptureCallback() {
  @MainThread
  override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
    super.onCaptureStarted(session, request, timestamp, frameNumber)
  }

  @MainThread
  override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
    super.onCaptureCompleted(session, request, result)
  }
}

除了模式的配置,CaptureRequest還可以配置很多其他資訊,例如影像格式、影像解析度、傳感器控制、閃光燈控制、3A(自動對焦-AF、自動曝光-AE和自動白平衡-AWB)控制等,在createCaptureSession的回呼中可以進行設定,最后通過build()方法生成CaptureRequest物件,

7.預覽

Camera2中,通過連續重復的Capture實作預覽功能,每次Capture會把預覽畫面顯示到對應的Surface上,連續重復的Capture操作通過

captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)

實作,該方法有三個引數:

  • request:CaptureRequest物件
  • listener:監聽Capture 狀態的回呼
  • handler:用于執行CameraCaptureSession.CaptureCallback的Handler物件,傳入null則使用當前的主執行緒Handler

停止預覽使用mCaptureSession.stopRepeating()方法,

8.拍照

設定上面的request,session后,就可以真正的開始拍照操作

val captureImageRequest = captureImageRequestBuilder.build()
captureSession.capture(captureImageRequest, CaptureImageStateCallback(), mainHandler)

......

private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() {

  @MainThread
  override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
    super.onCaptureStarted(session, request, timestamp, frameNumber)
    // Play the shutter click sound.
    cameraHandler?.post { mediaActionSound.play(MediaActionSound.SHUTTER_CLICK) }
  }

  @MainThread
  override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
    super.onCaptureCompleted(session, request, result)
    captureResults.put(result)
  }
}

captureSession.capture()方法也有三個引數,和mCaptureSession.setRepeatingRequest一樣:

  • request:CaptureRequest物件
  • listener:監聽Capture 狀態的回呼
  • handler:用于執行CameraCaptureSession.CaptureCallback的Handler物件,傳入null則使用當前的主執行緒Handler

9.關閉相機

和其他硬體資源的使用一樣,當我們不再需要使用相機時記得呼叫 CameraDevice.close() 方法及時關閉相機回收資源,關閉相機的操作至關重要,因為如果你一直占用相機資源,其他基于相機開發的功能都會無法正常使用,嚴重情況下直接導致其他相機相關的 APP 無法正常使用,當相機被完全關閉的時候會通過 CameraStateCallback.onCllosed() 方法通知你相機已經被關閉,那么在什么時候關閉相機最合適呢?個人的建議是在 onPause() 的時候就一定要關閉相機,因為在這個時候相機頁面已經不是用戶關注的焦點,大部分情況下已經可以關閉相機了,

cameraDevice?.close()
previewDataImageReader?.close()
jpegImageReader?.close()

先后對CaptureSession,CameraDevice,ImageReader進行close操作,釋放資源,

從 Camera1遷移到Camera2的建議

如果你的專案正在使用Camera1,并且打算從Camera1遷移到Camera2的話,希望以下幾個建議可以對你有所幫助:

  1. Camera1嚴格區分了預覽和拍照兩個流程,而Camera2則把這兩個流程都抽象成了Capture行為,所以建議你不要帶著過多的Camera1思維使用Camera2,避免因為思維上的束縛而無法充分利用Camera2靈活的 API;
  2. 如同Camera1一樣,Camera2的一些 API呼叫也會耗時,所以建議你使用獨立的執行緒執行所有的相機操作,盡量避免直接在主執行緒呼叫Camera2的API,HandlerThread 是一個不錯的選擇;
  3. 可以認為Camera1是Camera2的一個子集,也就是說Camera1能做的事情Camera2一定能做,反過來則不一定行得通;
  4. 如果你的應用程式需要同時兼容Camera1 和Camera2,個人建議分開維護,因為Camera1蹩腳的API設計很可能讓Camera2靈活的API無法得到充分的發揮,另外將兩個設計上完全不兼容的東西攪和在一起帶來的痛苦可能遠大于其帶來便利性,多寫一些冗余的代碼也許還更開心;
  5. 官方說Camera2的性能會更好,但在較早期的一些機器上運行Camera2的性能并沒有比Camera1好多少;
  6. 當設備的 Supported Hardware Level 低于FULL的時候,建議還是使用Camera1,因為FULL級別以下的 Camera2 能提供的功能幾乎和Camera1一樣,所以倒不如選擇更加穩定的Camera1,

完整代碼:https://github.com/zhijunhong/custom_camera/tree/master/camera2

最后,別忘了start喲~

參考:

Android Camera-Camera2使用

Android Camera2 教程

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/277054.html

標籤:其他

上一篇:android NDK開發決議

下一篇:Android Fragment使用詳解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more