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

App在物理设备安装运行后崩溃:DISPLAY_NAME列值为空问题排查求助

解决三星Galaxy A30(Android 11)上App启动崩溃问题:DISPLAY_NAME column is null

从你提供的崩溃栈来看,核心问题是访问Content Uri时,DISPLAY_NAME字段返回null,导致ContentUriFileDescriptor抛出BadContentProviderException。这个问题在Android 11设备上出现,大概率和分区存储(Scoped Storage)的行为变化,或者三星设备自身的Content Provider实现有关。下面是具体的排查和解决步骤:

一、先明确崩溃触发的场景

崩溃发生在App启动阶段,说明你的App在启动时(比如Application.onCreate或主Activity的初始化逻辑中)调用了SAI相关的文件/APK处理代码,尝试读取某个Content Uri对应的文件名称,但该Uri在三星A30上无法返回有效的DISPLAY_NAME值。

二、针对性解决方案

1. 检查Content Uri的权限与有效性

  • 如果你的App是通过文件选择器(比如Intent.ACTION_OPEN_DOCUMENT)获取的Content Uri,务必在获取Uri后调用takePersistableUriPermission持久化权限。Android 11下,重启App后临时权限会失效,此时访问Uri可能返回null值:
    // 获取Uri后立即执行
    ContentResolver resolver = getContentResolver();
    int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    resolver.takePersistableUriPermission(uri, takeFlags);
    
  • 确认启动时访问的Uri对应的文件是否还存在,比如用户是否删除了该文件,或者Uri是之前缓存的无效值。

2. 修改ContentUriFileDescriptor的容错逻辑

如果你的项目依赖了SAI的库,或者直接使用了ContentUriFileDescriptor类,需要修改其name()方法,增加对DISPLAY_NAME为null的处理,避免直接抛出异常:

public String name() throws BadContentProviderException {
    try (Cursor cursor = mContext.getContentResolver().query(mUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
        if (cursor != null && cursor.moveToFirst()) {
            String displayName = cursor.getString(0);
            // 当DISPLAY_NAME为null时,从Uri路径提取文件名或使用默认值
            if (displayName != null) {
                return displayName;
            }
            return extractFileNameFromUri(mUri) != null ? extractFileNameFromUri(mUri) : "unknown_file";
        }
    } catch (Exception e) {
        throw new BadContentProviderException(e);
    }
    throw new BadContentProviderException("Failed to retrieve display name for content URI " + mUri);
}

// 从Uri路径中提取文件名的辅助方法
private String extractFileNameFromUri(Uri uri) {
    String path = uri.getPath();
    if (path == null) return null;
    int lastSlashIndex = path.lastIndexOf('/');
    return lastSlashIndex != -1 && lastSlashIndex < path.length() - 1 ? path.substring(lastSlashIndex + 1) : null;
}

这段代码会在DISPLAY_NAME为空时,尝试从Uri的路径中提取文件名,或者返回一个默认名称,避免崩溃。

3. 适配Android 11的分区存储权限

  • 确保你的App已经申请了READ_EXTERNAL_STORAGE权限(targetSdk 31下,分区存储模式仍需要这个权限来访问媒体文件),并且在运行时请求用户授权:
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityResultContracts.RequestPermission requestPermission = new ActivityResultContracts.RequestPermission();
        registerForActivityResult(requestPermission, isGranted -> {
            if (isGranted) {
                // 权限已授予,执行文件操作
            } else {
                // 提示用户开启权限
            }
        }).launch(Manifest.permission.READ_EXTERNAL_STORAGE);
    }
    
  • 对于非媒体文件,建议使用Intent.ACTION_OPEN_DOCUMENT获取Uri,而不是直接访问文件路径,这是Android 10+推荐的方式。

4. 排查启动时的自动操作逻辑

检查App启动时是否有自动加载某个文件的逻辑,比如缓存的Uri。如果是,可以改为让用户手动触发该操作,或者在启动时先检查Uri的有效性,避免直接访问无效Uri导致崩溃。

三、测试建议

  • 在三星A30上,手动触发文件选择和处理逻辑,看是否会出现同样的问题,排除启动时自动操作的异常场景。
  • 在代码中增加日志,输出要访问的Content Uri,以及查询Cursor时的所有列值,确认DISPLAY_NAME是否确实为null,帮助定位问题根源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 10:37:45