You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Kotlin Fragment中拍照无显示、图片上传崩溃问题求助

问题排查与解决方案

我来帮你分析并解决这两个困扰你的问题,咱们一步步来:

问题1:拍照后ImageView无法显示图片

问题2:从相册选图时应用崩溃(NullPointerException


先说说崩溃的核心原因(问题2)

你在onActivityResult里统一处理拍照和选图的逻辑,但从相册选择图片返回的Intent里根本没有extras数据,你直接调用data.getExtras().get("data")就会触发空指针——因为getExtras()返回了null

而拍照后不显示的问题,主要是两个小坑:一是你重复调用了super.onActivityResult导致生命周期混乱;二是每次在onActivityResult里用view!!.findViewById获取ImageView存在视图为空的风险,部分设备的默认拍照返回缩略图也可能存在显示异常。


完整修复方案

第一步:重构成员变量与权限处理

把ImageView和常量提升为Fragment成员变量,同时补上动态权限请求(Android 6.0+必须要做,否则拍照/选图会直接失败):

class Category_Description : Fragment() {
    // 成员变量保存ImageView,避免重复findViewById
    private lateinit var displayImageView: ImageView
    // 统一管理请求码常量
    private val REQUEST_IMAGE_CAPTURE = 12345
    private val IMAGE_PICK_CODE = 1000
    private val PERMISSION_CODE = 1001
    // 存储拍照生成的图片Uri(用来获取完整尺寸图片)
    private var photoUri: Uri? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val v = inflater.inflate(R.layout.fragment_category__description, container, false)
        
        // 初始化视图组件
        displayImageView = v.findViewById(R.id.display_image)
        val cameraFAB = v.findViewById<FloatingActionButton>(R.id.camera)
        val uploadFAB = v.findViewById<FloatingActionButton>(R.id.upload)
        val backBtn = v.findViewById<ImageView>(R.id.backToHistory)

        // 相册选择逻辑(先检查权限)
        uploadFAB.setOnClickListener {
            if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_IMAGES)
                == PackageManager.PERMISSION_GRANTED
            ) {
                pickImageFromGallery()
            } else {
                requestPermissions(arrayOf(Manifest.permission.READ_MEDIA_IMAGES), PERMISSION_CODE)
            }
        }

        // 拍照逻辑(先检查权限)
        cameraFAB.setOnClickListener {
            if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED
            ) {
                dispatchTakePictureIntent()
            } else {
                requestPermissions(arrayOf(Manifest.permission.CAMERA), PERMISSION_CODE)
            }
        }

        backBtn.setOnClickListener {
            backFragment()
        }
        return v
    }

第二步:优化拍照逻辑(获取完整尺寸图片)

原来的ACTION_IMAGE_CAPTURE默认返回缩略图,我们可以指定输出Uri来获取高清完整图片,显示效果更好:

private fun dispatchTakePictureIntent() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        // 确保有相机应用能处理这个Intent
        takePictureIntent.resolveActivity(requireContext().packageManager)?.also {
            // 创建临时图片文件
            val photoFile: File? = try {
                createImageFile()
            } catch (ex: IOException) {
                Toast.makeText(requireContext(), "创建图片文件失败", Toast.LENGTH_SHORT).show()
                null
            }
            // 文件创建成功后,获取Uri并启动相机
            photoFile?.also {
                photoUri = FileProvider.getUriForFile(
                    requireContext(),
                    "${requireContext().packageName}.fileprovider", // 需在Manifest配置FileProvider
                    it
                )
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
}

// 创建临时图片文件
@Throws(IOException::class)
private fun createImageFile(): File {
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
    val storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
        "JPEG_${timeStamp}_",
        ".jpg",
        storageDir
    )
}

注意:使用FileProvider需要额外配置
AndroidManifest.xml<application>标签内添加:

<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 version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
</paths>

第三步:修复onActivityResult逻辑

分开处理拍照和选图的返回结果,彻底避免空指针:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data) // 只调用一次super即可

    if (resultCode == Activity.RESULT_OK) {
        when (requestCode) {
            REQUEST_IMAGE_CAPTURE -> {
                // 拍照成功:优先用输出Uri加载完整图片,兼容缩略图情况
                photoUri?.let { uri ->
                    // 用Glide加载更高效,避免OOM
                    Glide.with(this)
                        .load(uri)
                        .centerCrop()
                        .into(displayImageView)
                } ?: run {
                    // 兼容部分设备返回缩略图的情况
                    data?.extras?.get("data")?.let { thumbnail ->
                        displayImageView.setImageBitmap(thumbnail as Bitmap)
                    }
                }
            }
            IMAGE_PICK_CODE -> {
                // 选图成功:从data.getData()获取图片Uri
                data?.data?.let { uri ->
                    Glide.with(this)
                        .load(uri)
                        .centerCrop()
                        .into(displayImageView)
                    // 如果需要获取真实路径,可调用你的getRealPathFromURI方法
                    // val realPath = getRealPathFromURI(uri)
                } ?: run {
                    Toast.makeText(requireContext(), "无法获取图片", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

第四步:处理权限请求结果

添加权限回调,确保用户授权后能继续操作:

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == PERMISSION_CODE) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(requireContext(), "权限已授权", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(requireContext(), "需要权限才能完成操作", Toast.LENGTH_SHORT).show()
        }
    }
}

其他小修复

  • 删除onCreateView里的hasPermissionInManifest方法(它只检查Manifest声明,无法替代动态权限请求)
  • 删除原来onActivityResult里重复的super.onActivityResult调用
  • 如果使用Glide,需要在build.gradle添加依赖:implementation 'com.github.bumptech.glide:glide:4.16.0'

为什么这样修改能解决问题?

  1. 选图崩溃:不再从相册返回的Intent里错误地读取extras,而是通过data.getData()获取合法的图片Uri,彻底避免空指针。
  2. 拍照不显示:用成员变量保存ImageView,避免view!!的空风险;同时支持完整图片和缩略图两种返回方式,确保图片能正确加载。
  3. 权限合规:补上了动态权限请求,符合Android 6.0+的权限要求,不会因为权限缺失导致操作失败。

内容的提问来源于stack exchange,提问作者Aldor

火山引擎 最新活动