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

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());
    } 
}

额外注意事项

  1. 权限配置:
    • Android 9及以下:需要添加READ_EXTERNAL_STORAGE权限到Manifest
    • Android 10及以上:使用ACTION_GET_CONTENT选择文件时,无需额外存储权限(SAF机制自动授权)
    • 必须添加网络权限:<uses-permission android:name="android.permission.INTERNET"/>
  2. AsyncTask已废弃:从Android API 30开始,AsyncTask被标记为废弃,建议改用Coroutines或WorkManager处理后台任务
  3. 异常处理:不要像原代码那样吞掉IOException,打印日志或给用户提示,方便调试和问题定位

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 12:54:08