Android照片参数如何获取?

99ANYc3cd6
预计阅读时长 50 分钟
位置: 首页 参数 正文
  1. EXIF 参数:照片的元数据,由相机在拍摄时写入,包括拍摄时间、相机型号、光圈、快门速度、ISO、GPS 地理位置、是否使用闪光灯等。
  2. 图像文件参数:图像文件本身的基本属性,图像的分辨率(宽度和高度)、文件大小、格式(JPEG, PNG)、位深度等。

下面我将详细讲解如何获取这两类参数,并提供完整的代码示例。

android 从照片 参数
(图片来源网络,侵删)

获取 EXIF 参数 (元数据)

EXIF 信息通常存储在 JPEG 和 DNG 文件的头部,Android 提供了 ExifInterface 类来方便地读写这些信息。

关键步骤:

  1. 添加依赖:较新版本的 ExifInterface 已经包含在 Android SDK 中,但为了确保使用最新功能,最好在 build.gradle 文件中显式声明。

    // app/build.gradle
    dependencies {
        implementation 'androidx.exifinterface:exifinterface:1.3.6' // 使用最新版本
    }
  2. 获取 ExifInterface 实例:传入照片文件的 URI。

  3. 调用 getAttribute() 方法:使用预定义的常量来获取你需要的参数。

    android 从照片 参数
    (图片来源网络,侵删)

常用 EXIF 参数列表:

常量 描述 示例值
TAG_DATETIME 拍摄日期时间 "2025:10:27 15:30:00"
TAG_MAKE 相机制造商 "samsung"
TAG_MODEL 相机型号 "SM-G998B"
TAG_F_NUMBER 光圈值 "2.4"
TAG_EXPOSURE_TIME 曝光时间 (快门速度) "1/250"
TAG_ISO ISO 感光度 "100"
TAG_FLASH 闪光灯状态 "1" (闪光灯已开)
TAG_FOCAL_LENGTH 焦距 "6.0"
TAG_GPS_LATITUDE 纬度 "39.9"
TAG_GPS_LONGITUDE 经度 "116.4"
TAG_GPS_LATITUDE_REF 纬度参考 "N" (北纬) 或 "S" (南纬)
TAG_GPS_LONGITUDE_REF 经度参考 "E" (东经) 或 "W" (西经)
TAG_WHITE_BALANCE 白平衡模式 "auto"

代码示例:

import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import androidx.exifinterface.media.ExifInterface
import java.io.File
import java.io.IOException
object PhotoMetadataUtils {
    // 从文件路径获取 EXIF 数据
    fun getExifFromFile(context: Context, filePath: String): Map<String, String> {
        val exifData = mutableMapOf<String, String>()
        try {
            val exif = ExifInterface(filePath)
            // 获取各个属性
            exifData["Make"] = exif.getAttribute(ExifInterface.TAG_MAKE)
            exifData["Model"] = exif.getAttribute(ExifInterface.TAG_MODEL)
            exifData["DateTime"] = exif.getAttribute(ExifInterface.TAG_DATETIME)
            exifData["FNumber"] = exif.getAttribute(ExifInterface.TAG_F_NUMBER)
            exifData["ExposureTime"] = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME)
            exifData["ISO"] = exif.getAttribute(ExifInterface.TAG_ISO)
            exifData["Flash"] = exif.getAttribute(ExifInterface.TAG_FLASH)
            exifData["FocalLength"] = exif.getAttribute(ExifInterface.TAG_FOCAL_LENGTH)
            exifData["WhiteBalance"] = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE)
            // 处理 GPS 信息
            val lat = exif.latLong
            if (lat != null && lat.size == 2) {
                exifData["GPSLatitude"] = lat[0].toString()
                exifData["GPSLongitude"] = lat[1].toString()
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return exifData
    }
    // 从 Uri 获取 EXIF 数据 (推荐方式)
    fun getExifFromUri(context: Context, photoUri: Uri): Map<String, String> {
        val filePath = getRealPathFromUri(context, photoUri)
        return if (filePath != null) {
            getExifFromFile(context, filePath)
        } else {
            // 如果无法获取真实路径,尝试直接从 Uri 读取 (Android 10+)
            try {
                val parcelFileDescriptor = context.contentResolver.openFileDescriptor(photoUri, "r")
                val fileDescriptor = parcelFileDescriptor?.fileDescriptor
                if (fileDescriptor != null) {
                    val exif = ExifInterface(fileDescriptor)
                    // ... 同上,从 exif 对象获取数据 ...
                    parcelFileDescriptor.close()
                    return mapOf("Note" to "Read from Uri directly")
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            mapOf("Error" to "Could not read EXIF data")
        }
    }
    // 这是一个辅助函数,用于将 Content URI 转换为文件路径
    // 注意:在 Android 10+ (API 29) 及以上,这个方法可能返回 null,因为Scoped Storage限制了直接访问
    private fun getRealPathFromUri(context: Context, uri: Uri): String? {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return getDataColumn(context, uri, null, null)
        }
        // DocumentProvider
        if (DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                val docId = DocumentsContract.getDocumentId(uri)
                val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                val type = split[0]
                if ("primary".equals(type, ignoreCase = true)) {
                    return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
                }
            } else if (isDownloadsDocument(uri)) {
                // DownloadsProvider
                val id = DocumentsContract.getDocumentId(uri)
                val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
                return getDataColumn(context, contentUri, null, null)
            } else if (isMediaDocument(uri)) {
                // MediaProvider
                val docId = DocumentsContract.getDocumentId(uri)
                val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                val type = split[0]
                var contentUri: Uri? = null
                if ("image".equals(type, ignoreCase = true)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                } else if ("video".equals(type, ignoreCase = true)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                } else if ("audio".equals(type, ignoreCase = true)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                }
                val selection = "_id=?"
                val selectionArgs = arrayOf(split[1])
                return getDataColumn(context, contentUri, selection, selectionArgs)
            }
        } else if ("content".equals(uri.scheme, ignoreCase = true)) {
            // Return the remote address
            return if (isGooglePhotosUri(uri)) {
                uri.lastPathSegment
            } else {
                getDataColumn(context, uri, null, null)
            }
        } else if ("file".equals(uri.scheme, ignoreCase = true)) {
            // File
            return uri.path
        }
        return null
    }
    private fun getDataColumn(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? {
        var cursor: Cursor? = null
        val column = "_data"
        val projection = arrayOf(column)
        try {
            cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
            if (cursor != null && cursor.moveToFirst()) {
                val index = cursor.getColumnIndexOrThrow(column)
                return cursor.getString(index)
            }
        } finally {
            cursor?.close()
        }
        return null
    }
    private fun isExternalStorageDocument(uri: Uri): Boolean {
        return "com.android.externalstorage.documents" == uri.authority
    }
    private fun isDownloadsDocument(uri: Uri): Boolean {
        return "com.android.providers.downloads.documents" == uri.authority
    }
    private fun isMediaDocument(uri: Uri): Boolean {
        return "com.android.providers.media.documents" == uri.authority
    }
    private fun isGooglePhotosUri(uri: Uri): Boolean {
        return "com.google.android.apps.photos.content" == uri.authority
    }
}

获取图像文件参数 (分辨率、大小等)

获取这些参数更直接,主要通过 BitmapFactoryContentResolver 来完成。

关键步骤:

  1. 获取 InputStream:通过 ContentResolver 打开照片 URI 的输入流。
  2. 获取文件大小:直接从 InputStreamAssetFileDescriptor 获取。
  3. 获取图像尺寸:使用 BitmapFactory.OptionsinJustDecodeBounds 属性,这个属性允许你在不真正解码图片的情况下获取其原始宽度和高度,非常节省内存。
  4. (可选)获取图像格式BitmapFactory 会根据文件扩展名自动识别格式。

代码示例:

import android.content.ContentResolver
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import java.io.File
import java.io.FileInputStream
import java.io.IOException
object ImageFileUtils {
    // 从 Uri 获取图像参数
    fun getImageParametersFromUri(context: Context, photoUri: Uri): Map<String, Any> {
        val parameters = mutableMapOf<String, Any>()
        // 1. 获取文件大小
        parameters["FileSize"] = getFileSizeFromUri(context, photoUri)
        // 2. 获取图像分辨率
        val (width, height) = getImageDimensionsFromUri(context, photoUri)
        parameters["Width"] = width
        parameters["Height"] = height
        // 3. 获取文件格式 (通常可以从 Uri 或文件名推断)
        // parameters["Format"] = getImageFormat(photoUri) // 这个需要更复杂的实现
        return parameters
    }
    // 从 Uri 获取文件大小 (单位: KB)
    private fun getFileSizeFromUri(context: Context, uri: Uri): Long {
        return try {
            val inputStream = context.contentResolver.openInputStream(uri)
            if (inputStream != null) {
                val available = inputStream.available()
                inputStream.close()
                available.toLong()
            } else {
                0L
            }
        } catch (e: IOException) {
            e.printStackTrace()
            0L
        }
    }
    // 从 Uri 获取图像尺寸 (宽度和高度)
    private fun getImageDimensionsFromUri(context: Context, uri: Uri): Pair<Int, Int> {
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true // 只解码边界,不加载像素数据
        try {
            val inputStream = context.contentResolver.openInputStream(uri)
            if (inputStream != null) {
                BitmapFactory.decodeStream(inputStream, null, options)
                inputStream.close()
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return Pair(options.outWidth, options.outHeight)
    }
    // 如果你有文件的绝对路径,可以直接操作 File 对象
    fun getImageParametersFromFile(filePath: String): Map<String, Any> {
        val parameters = mutableMapOf<String, Any>()
        val file = File(filePath)
        // 1. 获取文件大小
        parameters["FileSize"] = file.length()
        // 2. 获取图像分辨率
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeFile(filePath, options)
        parameters["Width"] = options.outWidth
        parameters["Height"] = options.outHeight
        return parameters
    }
}

完整使用示例 (在 Activity 或 Fragment 中)

假设你已经通过 Intent 获取到了一张照片的 URI。

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
    private lateinit var tvMetadata: TextView
    private val PICK_IMAGE_REQUEST = 1001
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val btnPickImage: Button = findViewById(R.id.btn_pick_image)
        tvMetadata = findViewById(R.id.tv_metadata)
        btnPickImage.setOnClickListener {
            val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            startActivityForResult(intent, PICK_IMAGE_REQUEST)
        }
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK) {
            val photoUri: Uri? = data?.data
            if (photoUri != null) {
                processImageMetadata(photoUri)
            }
        }
    }
    private fun processImageMetadata(uri: Uri) {
        // 获取 EXIF 参数
        val exifData = PhotoMetadataUtils.getExifFromUri(this, uri)
        // 获取图像文件参数
        val imageParams = ImageFileUtils.getImageParametersFromUri(this, uri)
        // 合并并显示结果
        val allParams = mutableMapOf<String, Any>()
        allParams.putAll(exifData)
        allParams.putAll(imageParams)
        val resultText = StringBuilder()
        allParams.forEach { (key, value) ->
            resultText.append("$key: $value\n")
        }
        tvMetadata.text = resultText.toString()
    }
}

重要注意事项

  1. 运行时权限:访问照片和存储需要权限,对于 Android 13 (API 33) 及以上,你需要请求 READ_MEDIA_IMAGES 权限,对于更早的版本,需要 READ_EXTERNAL_STORAGE 权限。
  2. Scoped Storage (作用域存储):从 Android 10 (API 29) 开始,应用默认无法直接访问共享外部存储上的任意文件,从 ContentProvider 返回的 Uri 可能没有直接对应的文件路径,这就是为什么推荐直接使用 Uri 来操作(如 ExifInterface 支持从 FileDescriptor 读取),而不是尝试获取一个已废弃的文件路径。
  3. 性能:对于非常大的图片,使用 inJustDecodeBounds 来获取尺寸是最佳实践,因为它避免了将整个图片加载到内存中,直接解码大图片可能会导致 OutOfMemoryError
  4. EXIF 数据可能不存在:并非所有图片都有完整的 EXIF 数据,从微信等社交软件保存的图片通常会丢失 EXIF 信息,在读取时应该进行空值检查,避免应用崩溃。
android 从照片 参数
(图片来源网络,侵删)
-- 展开阅读全文 --
头像
ThinkPad E425拆机步骤是怎样的?
« 上一篇 今天
谷歌智能眼镜有什么功能
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]