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
相关产品推荐
相关产品推荐

