Android Studio下两台安卓设备间文件传输代码调试及文件路径获取问题求助
安卓跨设备文件传输:路径获取与代码优化指南
首先要明确:你通过Intent.ACTION_GET_CONTENT获取的Uri调用getPath()得到的不是真实的文件系统路径,这是很多安卓开发者都会踩的典型坑。
为什么你的路径获取方式有问题?
Intent.ACTION_GET_CONTENT返回的Uri通常是content://开头的内容Uri,而非file://开头的文件Uri。调用getPath()得到的只是Uri的路径片段(比如/document/primary:Download/test.txt),这并不是设备上真实的文件路径——直接用这个路径创建File对象肯定会失败,尤其是在Android 10(API 29)及以上的分区存储机制下,App无法直接访问其他应用或公共目录的真实文件路径。
正确的文件读取方式(无需真实路径)
与其纠结获取真实路径,不如直接通过ContentResolver打开文件输入流,这样更符合现代安卓的存储规范,也能兼容所有版本:
第一步:修改文件选择后的参数传递
启动文件选择Intent时,不要传递getPath()的结果,直接传递Uri本身:
// 启动文件选择 Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); startActivityForResult(intent, FILE_PICK_REQUEST); // 在onActivityResult(或ActivityResultCallback)中处理结果 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == FILE_PICK_REQUEST && resultCode == RESULT_OK) { Uri fileUri = data.getData(); // 直接传递Uri字符串给后台任务,而非路径 new BackgroundTask().execute("目标IP地址", fileUri.toString()); } }
第二步:修改发送端代码,用Uri获取流
重构BackgroundTask,通过ContentResolver直接读取文件,无需创建File对象:
class BackgroundTask extends AsyncTask<String, Void, String> { String ipaddress; Uri fileUri; @Override protected String doInBackground(String... params) { ipaddress = params[0]; fileUri = Uri.parse(params[1]); try { Socket socket = new Socket(ipaddress, 5321); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); DataOutputStream dos = new DataOutputStream(bos); // 通过ContentResolver获取文件信息 ContentResolver resolver = getContentResolver(); String fileName = getFileNameFromUri(resolver, fileUri); // 获取文件大小(部分Uri可能无法获取,需处理异常) long fileLength = resolver.openFileDescriptor(fileUri, "r").getStatSize(); dos.writeLong(fileLength); dos.writeUTF(fileName); // 直接打开输入流读写 InputStream inputStream = resolver.openInputStream(fileUri); BufferedInputStream bis = new BufferedInputStream(inputStream); // 用缓冲区提升读写效率,避免单字节读写的性能问题 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); } // 按顺序关闭资源 bis.close(); dos.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); // 不要吞掉异常,打印日志方便调试 } return null; } // 从Uri中解析文件名的工具方法 private String getFileNameFromUri(ContentResolver resolver, Uri uri) { String fileName = "unknown_file"; Cursor cursor = resolver.query(uri, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (nameIndex != -1) { fileName = cursor.getString(nameIndex); } cursor.close(); } return fileName; } }
接收端代码优化
你的接收端代码有两个明显问题:单字节读写效率极低,异常处理不完善。修改后的代码如下:
@Override public void run() { try { ServerSocket ss = new ServerSocket(5321); handler.post(() -> Toast.makeText(getApplicationContext(), "waiting for connecting ..", Toast.LENGTH_LONG).show()); while (true){ Socket mysocket = ss.accept(); handler.post(() -> Toast.makeText(getApplicationContext(), "connected", Toast.LENGTH_LONG).show()); BufferedInputStream bis = new BufferedInputStream(mysocket.getInputStream()); DataInputStream dis = new DataInputStream(bis); long fileLength = dis.readLong(); String fileName = dis.readUTF(); String downloadfolderpath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); File outputFile = new File(downloadfolderpath, fileName); FileOutputStream fos = new FileOutputStream(outputFile); BufferedOutputStream bos = new BufferedOutputStream(fos); // 用缓冲区批量读写,大幅提升大文件传输速度 byte[] buffer = new byte[4096]; long bytesReceived = 0; int bytesRead; while (bytesReceived < fileLength && (bytesRead = bis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); bytesReceived += bytesRead; } // 确保所有数据写入磁盘 bos.flush(); // 按顺序关闭资源 bos.close(); fos.close(); dis.close(); bis.close(); mysocket.close(); handler.post(() -> Toast.makeText(getApplicationContext(), "File received: " + fileName, Toast.LENGTH_LONG).show()); } } catch (IOException e) { e.printStackTrace(); handler.post(() -> Toast.makeText(getApplicationContext(), "Connection error", Toast.LENGTH_LONG).show()); } }
额外注意事项
- 权限配置:
- Android 9及以下:需要添加
READ_EXTERNAL_STORAGE权限到Manifest - Android 10及以上:使用
ACTION_GET_CONTENT选择文件时,无需额外存储权限(SAF机制自动授权) - 必须添加网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
- Android 9及以下:需要添加
- AsyncTask已废弃:从Android API 30开始,
AsyncTask被标记为废弃,建议改用Coroutines或WorkManager处理后台任务 - 异常处理:不要像原代码那样吞掉IOException,打印日志或给用户提示,方便调试和问题定位
内容的提问来源于stack exchange,提问作者Snipar 7
相关产品推荐
相关产品推荐

