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

Android与Java(PC) Socket传图遇FileNotFoundException求解决

解决Android从相册选图后Socket发送时的FileNotFoundException问题

你遇到的核心问题是从相册获取的图片Uri并非真实的文件系统路径,直接用这个路径创建File对象自然会找不到文件。从Android 4.4(API 19)开始,通过ACTION_GET_CONTENT选取文件返回的Uri可能是content://或document://开头的内容提供者Uri,而不是传统的文件路径——这就是你看到/document/image:78这种奇怪路径的原因。

解决方案1:修复getPath方法,适配内容Uri

你现有的getPath方法只处理了MediaStore类型的Uri,没有适配DocumentProvider的Uri。可以修改这个方法,让它能正确解析不同类型的Uri为真实文件路径:

public String getPath(Uri uri) {
    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // 处理DocumentProvider类型的Uri
    if (isKitKat && DocumentsContract.isDocumentUri(this, uri)) {
        // 外部存储文档
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }
        }
        // 下载文档
        else if (isDownloadsDocument(uri)) {
            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(this, contentUri, null, null);
        }
        // 媒体文档(图片/视频/音频)
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[]{split[1]};

            return getDataColumn(this, contentUri, selection, selectionArgs);
        }
    }
    // 处理MediaStore或通用content类型Uri
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(this, uri, null, null);
    }
    // 处理直接文件Uri
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

// 从ContentResolver中获取数据列(真实文件路径)
private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {column};

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
        if (cursor != null && cursor.moveToFirst()) {
            final int columnIndex = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(columnIndex);
        }
    } finally {
        if (cursor != null) cursor.close();
    }
    return null;
}

// 判断是否为外部存储文档Uri
private boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

// 判断是否为下载文档Uri
private boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

// 判断是否为媒体文档Uri
private boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

之后在onActivityResult里,把yol = selectedImageUri.getPath();替换成yol = getPath(selectedImageUri);,就能拿到真实的文件路径了。

解决方案2:直接通过ContentResolver读取文件流(更推荐)

其实不需要纠结于获取真实文件路径,直接通过ContentResolver打开输入流读取图片内容,兼容性更好,也不用处理复杂的Uri适配:

首先把selectedImageUri改成类的成员变量(让SocketThread能访问到),然后修改SocketThread的run方法:

@Override
public void run() {
    Socket sock;
    try {
        sock = new Socket("10.0.2.2", 8080);
        System.out.println("Connecting...");

        // 直接通过ContentResolver获取图片输入流
        InputStream inputStream = getContentResolver().openInputStream(selectedImageUri);
        if (inputStream == null) {
            System.out.println("无法获取图片输入流");
            sock.close();
            return;
        }

        BufferedInputStream bis = new BufferedInputStream(inputStream);
        OutputStream os = sock.getOutputStream();
        System.out.println("Sending...");

        // 分块发送数据,避免大图片导致内存溢出
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = bis.read(buffer)) != -1) {
            os.write(buffer, 0, bytesRead);
        }

        os.flush();
        bis.close();
        inputStream.close();
        sock.close();
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

额外注意事项

  • 确保你的APP已经申请了READ_EXTERNAL_STORAGE权限(Android 6.0及以上需要动态申请),否则可能无法读取图片内容。
  • 发送大图片时,分块读取发送比一次性把整个图片读到内存里更安全,能有效避免OOM(内存溢出)问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:34:26