Camera系列文章首發(fā)于 我的慕課網(wǎng)怎囚,歡迎關(guān)注事秀。
概述
Camera 可能是接下來個人想深入學(xué)習(xí)的課題读慎,準(zhǔn)備新起一個系列,從個人的角度總結(jié)闡述自己對于 Android Camera 的研究過程峡钓,希望也能夠?qū)ζ渌雽W(xué)習(xí) Camera 的同學(xué)一些幫助妓笙。
本小節(jié)內(nèi)容為 Android Camera 官方文檔 的精要翻譯,原文請參考:
一能岩、拍照
本課程將闡述如何通過委托Android設(shè)備上的其他相機(jī)應(yīng)用程序進(jìn)行拍照 (如果您更愿意構(gòu)建自己的相機(jī)功能寞宫,請參閱 控制相機(jī) )。
請求相機(jī)功能
如果您的應(yīng)用程序的基本功能涉及到 拍照拉鹃,請將其在Google Play上的可見性限制為具有相機(jī)的設(shè)備淆九。 以聲明您的應(yīng)用程序依賴于攝像頭,請在清單文件中放置<uses-feature>
標(biāo)記毛俏。
<manifest ... >
<uses-feature android:name="android.hardware.camera"
android:required="true" />
...
</manifest>
使用其他相機(jī)APP拍照
你可以通過Android的Intent
將拍照行為委托給其他的拍照應(yīng)用, 此過程涉及三個部分:Intent
本身饲窿,調(diào)用并啟動外部Activity
煌寇,以及在Activity
中處理回調(diào)的數(shù)據(jù)。
下面是調(diào)用啟動拍照應(yīng)用的函數(shù)代碼:
val REQUEST_IMAGE_CAPTURE = 1
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
takePictureIntent.resolveActivity(packageManager)?.also {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
請注意逾雄,調(diào)用startActivityForResult
函數(shù)之前阀溶,請先通過調(diào)用resolveActivity
函數(shù)以保證startActivityForResult
函數(shù)中的Intent
能夠被正確的處理,否則將會導(dǎo)致應(yīng)用的崩潰鸦泳。
獲取縮略圖
Android Camera應(yīng)用程序?qū)⒎祷氐?code>Intent中的照片通過onActivityResult()
返回银锻,作為附加內(nèi)容中的一個小位圖,位于關(guān)鍵字data
下做鹰。 以下代碼檢索此結(jié)果并將其顯示在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)
}
}
完整保存拍照結(jié)果
通常击纬,用戶使用設(shè)備攝像頭拍攝的任何照片都應(yīng)保存在公共外部存儲設(shè)備中,以便所有應(yīng)用都可以訪問钾麸。 共享照片的正確目錄由getExternalStoragePublicDirectory()
提供更振,帶有DIRECTORY_PICTURES
參數(shù)。 由于此方法提供的目錄在所有應(yīng)用程序之間共享饭尝,因此讀取和寫入該目錄分別需要READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
權(quán)限肯腕。 寫權(quán)限隱式允許讀取,因此如果您需要寫入外部存儲钥平,那么您只需要請求一個權(quán)限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
但是实撒,如果您希望照片僅保留為應(yīng)用程序的私密照片,則可以使用getExternalFilesDir()
提供的目錄。 在Android 4.3及更低版本中知态,寫入此目錄還需要WRITE_EXTERNAL_STORAGE
權(quán)限捷兰。 從Android 4.4開始,不再需要該權(quán)限肴甸,因為其他應(yīng)用程序無法訪問該目錄寂殉,因此您可以通過添加maxSdkVersion
來聲明僅在較低版本的Android上請求權(quán)限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
...
</manifest>
注意:當(dāng)用戶卸載應(yīng)用程序時,將刪除由
getExternalFilesDir()
或getFilesDir()
提供的目錄中保存的文件原在。
確定文件的目錄后友扰,需要創(chuàng)建一個防沖突的文件名。 您可能還希望將路徑保存在成員變量中以供以后使用庶柿。 以下解決方案是通過時間戳為新照片返回唯一文件名的示例:
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
}
}
使用此方法可以為照片創(chuàng)建文件村怪,您現(xiàn)在可以像這樣創(chuàng)建和調(diào)用Intent:
val REQUEST_TAKE_PHOTO = 1
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
// 保證intent可正確的跳轉(zhuǎn)
takePictureIntent.resolveActivity(packageManager)?.also {
// 創(chuàng)建保存照片的文件路徑
val photoFile: File? = try {
createImageFile()
} catch (ex: IOException) {
// 處理異常
...
null
}
// 僅在成功創(chuàng)建文件時繼續(xù)
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浮庐,F(xiàn)ile)
返回content:// URI
甚负。 對于針對Android 7.0(API級別24)及更高版本的更新應(yīng)用,在包邊界上傳遞file:// URI
會導(dǎo)致FileUriExposedException
审残。 因此梭域,我們現(xiàn)在提供一種使用 FileProvider 存儲圖像的更通用的方法。
現(xiàn)在搅轿,您需要配置 FileProvider 病涨。 在您的應(yīng)用清單中,向您的應(yīng)用添加對應(yīng)的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既穆,F(xiàn)ile)
的第二個參數(shù)匹配。 在APP的 meta-data
中雀鹃,您可以看到APP期望在資源文件res/xml/file_paths.xml
中配置符合條件的 path幻工。 以下是此示例所需的代碼內(nèi)容:
<?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>
將照片加入相冊
當(dāng)您通過意圖創(chuàng)建照片時,您應(yīng)該知道其所在位置黎茎,因為您首先要說明將圖像保存在何處囊颅。 對于其他所有人來說,使照片可以訪問的最簡單方法可能是從系統(tǒng)的相冊訪問它:
// 將圖片保存到相冊
private fun galleryAddPic() {
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
val f = File(mCurrentPhotoPath)
mediaScanIntent.data = Uri.fromFile(f)
sendBroadcast(mediaScanIntent)
}
}
解碼縮放圖片
管理多個全尺寸的圖片可能會因內(nèi)存有限而變得棘手傅瞻。 如果在顯示幾個圖片后發(fā)現(xiàn)應(yīng)用程序內(nèi)存不足迁酸,則可以通過將圖片壓縮減少動態(tài)堆的使用量。 以下示例方法演示了此技術(shù):
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)
}
}
二俭正、視頻錄制
請求相機(jī)功能
如果您的應(yīng)用程序的基本功能涉及到 拍照奸鬓,請將其在Google Play上的可見性限制為具有相機(jī)的設(shè)備。 以聲明您的應(yīng)用程序依賴于攝像頭掸读,請在清單文件中放置<uses-feature>
標(biāo)記串远。
<manifest ... >
<uses-feature android:name="android.hardware.camera"
android:required="true" />
...
</manifest>
使用相機(jī)應(yīng)用錄制視頻
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)
}
}