You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中使用Uri上传录屏视频时出现空指针异常的问题排查与解决

Android中使用Uri上传录屏视频时出现空指针异常的问题排查与解决

我看到你遇到的问题了——录屏后通过Uri上传视频时,因为获取的File对象为null导致空指针崩溃,日志里显示File path null,最后在file!!.asRequestBody这行代码直接炸了。咱们一步步来排查和解决这个问题:

一、先定位空指针的直接原因

你的崩溃日志指向file!!.asRequestBody这行,这说明getFileFromContentUri5(this, uri)返回了null,而你用!!强制非空触发了空指针异常。

为什么这个方法会返回null?大概率是从ContentUri复制文件到缓存目录的过程中出了问题——可能是权限不足、录屏生成的文件不存在、流复制时出错,或者复制后的文件大小为0,导致方法返回null。

二、紧急修复:先避免崩溃

在解决根本问题前,先给代码加个null判断,至少不会直接闪退:

private fun uploadVideo(uri: Uri) {
    val file = getFileFromContentUri5(this, uri) ?: run {
        Log.e("uploadVideo", "无法从Uri获取有效文件")
        // 这里可以给用户弹个提示,比如"上传失败:文件获取异常"
        return
    }
    // 后面的原有代码保持不变
}

三、排查getFileFromContentUri5的问题

给这个工具方法加详细日志,就能精准找到哪里出错了:

fun getFileFromContentUri5(context: Context, contentUri: Uri): File? {
    val contentResolver = context.contentResolver
    val file = File(context.cacheDir, "temp_video.mp4")
    Log.e("getFileFromUri", "准备复制文件到临时目录:${file.path}")

    try {
        contentResolver.openInputStream(contentUri)?.use { inputStream ->
            Log.e("getFileFromUri", "成功打开Uri对应的输入流")
            FileOutputStream(file).use { outputStream ->
                val bytesCopied = inputStream.copyTo(outputStream)
                Log.e("getFileFromUri", "共复制了$bytesCopied 字节到临时文件")
            }
        }
    } catch (e: Exception) {
        Log.e("getFileFromUri", "复制文件出错:${e.message}", e)
        return null
    }

    if (file.length() > 0) {
        Log.e("getFileFromUri", "临时文件大小:${file.length()} 字节")
        return file
    } else {
        Log.e("Error", "复制后临时文件为空")
        return null
    }
}

运行后看日志,就能知道是:

  • 打开输入流失败(比如权限不足、Uri无效)
  • 复制的字节数为0(文件本身为空)
  • 还是抛出了其他异常(比如IO错误)

常见问题的解决方向

  1. 权限问题
    在AndroidManifest.xml中添加对应权限,并在运行时请求用户授权:

    <!-- Android 12及以下需要这个 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
    <!-- Android 13+需要这个 -->
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" android:minSdkVersion="33" />
    
  2. Uri有效性问题
    先验证HBRecorder生成的Uri是否有效:

    fun isUriValid(context: Context, uri: Uri): Boolean {
        return try {
            context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
                cursor.moveToFirst()
            } ?: false
        } catch (e: Exception) {
            false
        }
    }
    

    在uploadVideo开头调用这个方法,确认Uri是否指向真实存在的文件。

四、更优雅的长期解决方案:跳过File直接用InputStream上传

在Android 10+的Scoped Storage机制下,很多ContentUri并没有对应的真实File路径,强行转File很容易踩坑。更推荐直接通过Uri获取InputStream创建RequestBody,既适配Scoped Storage,又节省磁盘空间:

1. 先写个获取文件名的工具方法

fun getFileNameFromUri(context: Context, uri: Uri): String {
    val contentResolver = context.contentResolver
    // 优先从系统媒体库获取文件名
    contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
        if (cursor.moveToFirst()) {
            val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            if (nameIndex != -1) {
                return cursor.getString(nameIndex)
            }
        }
    }
    // 如果获取不到,用Uri的最后一段作为文件名
    return uri.lastPathSegment ?: "unknown_video.mp4"
}

2. 修改uploadVideo方法,直接用InputStream上传

private fun uploadVideo(uri: Uri) {
    val contentResolver = this.contentResolver
    val fileName = getFileNameFromUri(this, uri)

    // 直接从Uri创建RequestBody,不需要转成File
    val videoRequestBody = object : RequestBody() {
        override fun contentType(): MediaType? {
            return "video/*".toMediaTypeOrNull()
        }

        override fun writeTo(sink: BufferedSink) {
            contentResolver.openInputStream(uri)?.use { inputStream ->
                sink.writeAll(inputStream.source())
            } ?: throw IOException("无法打开Uri对应的输入流:$uri")
        }
    }

    val bodyBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
    bodyBuilder.addFormDataPart("file", fileName, videoRequestBody)
    val requestBody = bodyBuilder.build()

    val requestBodyWithProgress = RequestBodyWithProgress(requestBody) { progress ->
        runOnUiThread {
            binding.apply {
                uploadProgress.progress = progress
                uploadPercentageTxt.text = "$progress%"
            }
        }
    }

    // 调用上传接口
    viewModel.callPostUploadVideo(testId, requestBodyWithProgress)
}

这种方式完全绕开了File转换的问题,适配Android 10+的存储机制更友好,还能避免临时文件占用磁盘空间。

备注:内容来源于stack exchange,提问作者Mohsen Mohebi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 09:10:31