Skip to content

在 Android 中调用摄像头和相册是常见的功能需求,以下是详细的实现步骤和 Kotlin 示例代码,涵盖权限请求、拍照、选择图片、处理返回结果等关键环节。


1. 添加权限

AndroidManifest.xml 中添加以下权限:

xml
<!-- 摄像头权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 存储权限(读取相册) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 针对 Android 13+ 的媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

注意:从 Android 10(API 29)开始,作用域存储(Scoped Storage)限制了直接访问外部存储的权限,需使用 MediaStore API。


2. 动态权限请求

ActivityFragment 中请求运行时权限:

kotlin
private val REQUEST_PERMISSION_CODE = 100

// 检查并请求权限
private fun checkPermissions() {
    val permissions = mutableListOf<String>()
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        permissions.add(Manifest.permission.CAMERA)
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
        }
    } else {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
    }

    if (permissions.isNotEmpty()) {
        ActivityCompat.requestPermissions(this, permissions.toTypedArray(), REQUEST_PERMISSION_CODE)
    } else {
        // 权限已授予,执行操作
    }
}

// 处理权限结果
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_PERMISSION_CODE) {
        if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
            // 权限已授予
        } else {
            Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show()
        }
    }
}

3. 调用摄像头拍照

步骤 1:创建临时文件

kotlin
private var photoFile: File? = null
private val REQUEST_TAKE_PHOTO = 101

private fun createImageFile(): File {
    val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
        "JPEG_${System.currentTimeMillis()}_",
        ".jpg",
        storageDir
    ).apply {
        photoFile = this
    }
}

步骤 2:启动摄像头 Intent

kotlin
private fun takePhoto() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        photoFile = createImageFile()
        photoFile?.let { file ->
            val photoURI: Uri = FileProvider.getUriForFile(
                this,
                "${packageName}.fileprovider", // 需配置 FileProvider
                file
            )
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
            startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
        }
    }
}

步骤 3:配置 FileProvider

AndroidManifest.xml 中添加:

xml
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

res/xml/file_paths.xml 中定义路径:

xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path name="my_images" path="Pictures/" />
</paths>

4. 调用相册选择图片

kotlin
private val REQUEST_PICK_PHOTO = 102

private fun pickPhotoFromGallery() {
    val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
    intent.type = "image/*"
    startActivityForResult(intent, REQUEST_PICK_PHOTO)
}

5. 处理返回结果

onActivityResult 中处理拍照或相册返回的图片:

kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == Activity.RESULT_OK) {
        when (requestCode) {
            REQUEST_TAKE_PHOTO -> {
                // 从临时文件读取拍照结果
                photoFile?.let { file ->
                    val bitmap = BitmapFactory.decodeFile(file.absolutePath)
                    imageView.setImageBitmap(bitmap)
                }
            }
            REQUEST_PICK_PHOTO -> {
                // 从相册 URI 获取图片
                data?.data?.let { uri ->
                    val inputStream = contentResolver.openInputStream(uri)
                    val bitmap = BitmapFactory.decodeStream(inputStream)
                    imageView.setImageBitmap(bitmap)
                }
            }
        }
    }
}

6. 完整示例代码

kotlin
class MainActivity : AppCompatActivity() {

    private lateinit var imageView: ImageView
    private var photoFile: File? = null
    private val REQUEST_TAKE_PHOTO = 101
    private val REQUEST_PICK_PHOTO = 102
    private val REQUEST_PERMISSION_CODE = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        imageView = findViewById(R.id.imageView)

        findViewById<Button>(R.id.btnCamera).setOnClickListener {
            checkPermissions()
            takePhoto()
        }

        findViewById<Button>(R.id.btnGallery).setOnClickListener {
            checkPermissions()
            pickPhotoFromGallery()
        }
    }

    // 动态权限检查和请求(见步骤 2)

    // 拍照相关方法(见步骤 3)

    // 相册选择方法(见步骤 4)

    // 处理返回结果(见步骤 5)
}

关键注意事项

  1. FileProvider 配置:必须正确配置 FileProvider,否则在 Android 7.0+ 会抛出 FileUriExposedException
  2. 作用域存储:从 Android 10 开始,直接访问外部存储受限,需使用 MediaStore API。
  3. 大图处理:加载大图时建议使用 GlidePicasso 等库,避免内存溢出(OOM)。
  4. 兼容性:针对不同 Android 版本调整权限请求逻辑(如 Android 13+ 的 READ_MEDIA_IMAGES)。

通过上述代码,可以实现在 Android 应用中调用摄像头拍照和从相册选择图片的功能。