1. 程式人生 > >Android Camera 系列(一)拍照和錄製視訊

Android Camera 系列(一)拍照和錄製視訊

Camera系列文章首發於 我的慕課網,歡迎關注。

概述

Camera 可能是接下來個人想深入學習的課題,準備新起一個系列,從個人的角度總結闡述自己對於 Android Camera 的研究過程,希望也能夠對其他想學習 Camera 的同學一些幫助。

一、拍照

本課程將闡述如何通過委託Android裝置上的其他相機應用程式進行拍照 (如果您更願意構建自己的相機功能,請參閱 控制相機 )。

請求相機功能

如果您的應用程式的基本功能涉及到 拍照,請將其在Google Play上的可見性限制為具有相機的裝置。 以宣告您的應用程式依賴於攝像頭,請在清單檔案中放置<uses-feature>

標記。

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

使用其他相機APP拍照

你可以通過Android的Intent將拍照行為委託給其他的拍照應用, 此過程涉及三個部分:Intent本身,呼叫並啟動外部Activity,以及在Activity中處理回撥的資料。

下面是呼叫啟動拍照應用的函式程式碼:

val REQUEST_IMAGE_CAPTURE =
1 private fun dispatchTakePictureIntent() { Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> takePictureIntent.resolveActivity(packageManager)?.also { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) } } }

請注意,呼叫startActivityForResult

函式之前,請先通過呼叫resolveActivity函式以保證startActivityForResult函式中的Intent能夠被正確的處理,否則將會導致應用的崩潰。

獲取縮圖

Android Camera應用程式將返回的Intent中的照片通過onActivityResult()返回,作為附加內容中的一個小點陣圖,位於關鍵字data下。 以下程式碼檢索此結果並將其顯示在ImageView中:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        val imageBitmap = data.extras.get("data") as Bitmap
        mImageView.setImageBitmap(imageBitmap)
    }
}

完整儲存拍照結果

通常,使用者使用裝置攝像頭拍攝的任何照片都應儲存在公共外部儲存裝置中,以便所有應用都可以訪問。 共享照片的正確目錄由getExternalStoragePublicDirectory()提供,帶有DIRECTORY_PICTURES引數。 由於此方法提供的目錄在所有應用程式之間共享,因此讀取和寫入該目錄分別需要READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE許可權。 寫許可權隱式允許讀取,因此如果您需要寫入外部儲存,那麼您只需要請求一個許可權:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

但是,如果您希望照片僅保留為應用程式的私密照片,則可以使用getExternalFilesDir()提供的目錄。 在Android 4.3及更低版本中,寫入此目錄還需要WRITE_EXTERNAL_STORAGE許可權。 從Android 4.4開始,不再需要該許可權,因為其他應用程式無法訪問該目錄,因此您可以通過新增maxSdkVersion來宣告僅在較低版本的Android上請求許可權:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
    ...
</manifest>

注意:當用戶解除安裝應用程式時,將刪除由getExternalFilesDir()getFilesDir()提供的目錄中儲存的檔案。

確定檔案的目錄後,需要建立一個防衝突的檔名。 您可能還希望將路徑儲存在成員變數中以供以後使用。 以下解決方案是通過時間戳為新照片返回唯一檔名的示例:

var mCurrentPhotoPath: String

@Throws(IOException::class)
private fun createImageFile(): File {
    // Create an image file name
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
            "JPEG_${timeStamp}_", /* prefix */
            ".jpg", /* suffix */
            storageDir /* directory */
    ).apply {
        // Save a file: path for use with ACTION_VIEW intents
        mCurrentPhotoPath = absolutePath
    }
}

使用此方法可以為照片建立檔案,您現在可以像這樣建立和呼叫Intent:

val REQUEST_TAKE_PHOTO = 1

private fun dispatchTakePictureIntent() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        // 保證intent可正確的跳轉
        takePictureIntent.resolveActivity(packageManager)?.also {
            // 建立儲存照片的檔案路徑
            val photoFile: File? = try {
                createImageFile()
            } catch (ex: IOException) {
                // 處理異常
                ...
                null
            }
            // 僅在成功建立檔案時繼續
            photoFile?.also {
                val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        "com.example.android.fileprovider",
                        it
                )
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
            }
        }
    }
}

注意:我們使用 getUriForFile(Context,String,File)返回content:// URI。 對於針對Android 7.0(API級別24)及更高版本的更新應用,在包邊界上傳遞file:// URI會導致FileUriExposedException。 因此,我們現在提供一種使用 FileProvider 儲存影象的更通用的方法。

現在,您需要配置 FileProvider 。 在您的應用清單中,向您的應用新增對應的Provider

<application>
   ...
   <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.example.android.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"></meta-data>
    </provider>
    ...
</application>

確保將authority字串與getUriForFile(Context,String,File)的第二個引數匹配。 在APP的 meta-data中,您可以看到APP期望在資原始檔res/xml/file_paths.xml中配置符合條件的 path。 以下是此示例所需的程式碼內容:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
</paths>

將照片加入相簿

當您通過意圖建立照片時,您應該知道其所在位置,因為您首先要說明將影象儲存在何處。 對於其他所有人來說,使照片可以訪問的最簡單方法可能是從系統的相簿訪問它:

// 將圖片儲存到相簿
private fun galleryAddPic() {
   Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
       val f = File(mCurrentPhotoPath)
       mediaScanIntent.data = Uri.fromFile(f)
       sendBroadcast(mediaScanIntent)
   }
}

解碼縮放圖片

管理多個全尺寸的圖片可能會因記憶體有限而變得棘手。 如果在顯示幾個圖片後發現應用程式記憶體不足,則可以通過將圖片壓縮減少動態堆的使用量。 以下示例方法演示了此技術:

private fun setPic() {
    // Get the dimensions of the View
    val targetW: Int = mImageView.width
    val targetH: Int = mImageView.height

    val bmOptions = BitmapFactory.Options().apply {
        // Get the dimensions of the bitmap
        inJustDecodeBounds = true
        BitmapFactory.decodeFile(mCurrentPhotoPath, this)
        val photoW: Int = outWidth
        val photoH: Int = outHeight

        // Determine how much to scale down the image
        val scaleFactor: Int = Math.min(photoW / targetW, photoH / targetH)

        // Decode the image file into a Bitmap sized to fill the View
        inJustDecodeBounds = false
        inSampleSize = scaleFactor
        inPurgeable = true
    }
    BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions)?.also { bitmap ->
        mImageView.setImageBitmap(bitmap)
    }
}

二、視訊錄製

請求相機功能

如果您的應用程式的基本功能涉及到 拍照,請將其在Google Play上的可見性限制為具有相機的裝置。 以宣告您的應用程式依賴於攝像頭,請在清單檔案中放置<uses-feature>標記。

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

使用相機應用錄製視訊

const val REQUEST_VIDEO_CAPTURE = 1

private fun dispatchTakeVideoIntent() {
    Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
        takeVideoIntent.resolveActivity(packageManager)?.also {
            startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
        }
    }
}

觀看視訊

override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent) {
    if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {
        val videoUri: Uri = intent.data
        mVideoView.setVideoURI(videoUri)
    }
}