【问题标题】:Android: save a file from an existing URIAndroid:从现有 URI 保存文件
【发布时间】:2012-10-30 06:09:40
【问题描述】:

如何从现有的 URI 中保存媒体文件(例如 .mp3),而我是从隐式意图中获取的?

【问题讨论】:

  • 我从意图 Uri mediaUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); 获取 URI

标签: android android-intent media


【解决方案1】:

用这个方法,有效

void savefile(URI sourceuri)
{
    String sourceFilename= sourceuri.getPath();
    String destinationFilename = android.os.Environment.getExternalStorageDirectory().getPath()+File.separatorChar+"abc.mp3";

    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;

    try {
      bis = new BufferedInputStream(new FileInputStream(sourceFilename));
      bos = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
      byte[] buf = new byte[1024];
      bis.read(buf);
      do {
        bos.write(buf);
      } while(bis.read(buf) != -1);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (bis != null) bis.close();
        if (bos != null) bos.close();
      } catch (IOException e) {
            e.printStackTrace();
      }
    }
}

【讨论】:

  • 在 Android 5.x (Lollipop) 上,必须包括: - 在使用 FileOutputStream 之前为“destinationFilename”创建空文件以避免抛出 IOException!
  • 这有效只是因为sourceuri 看起来像file:///something 并指向一个文件。这不再可能,因为 API 24(禁止在进程/应用程序之间共享文件 Uris)。始终使用 ContentResolver 打开内容 Uris。
  • 使用 ContentResolver:将 sourceuri.getPath() 更改为:stackoverflow.com/a/13275282/8283938
【解决方案2】:

如果 Uri 是从 Google Drive 接收的,它也可以是虚拟文件 Uri。 Check this 来自 CommonsWare 的文章以获取更多信息。因此,在从 Uri 保存文件时,您也必须考虑这种情况。

要查找文件 Uri 是否为虚拟文件,您可以使用

private static boolean isVirtualFile(Context context, Uri uri) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        if (!DocumentsContract.isDocumentUri(context, uri)) {
            return false;
        }
        Cursor cursor = context.getContentResolver().query(
                uri,
                new String[]{DocumentsContract.Document.COLUMN_FLAGS},
                null, null, null);
        int flags = 0;
        if (cursor.moveToFirst()) {
            flags = cursor.getInt(0);
        }
        cursor.close();
        return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
    } else {
        return false;
    }
}

你可以像这样从这个虚拟文件中获取流数据:

private static InputStream getInputStreamForVirtualFile(Context context, Uri uri, String mimeTypeFilter)
        throws IOException {

    ContentResolver resolver = context.getContentResolver();
    String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);
    if (openableMimeTypes == null || openableMimeTypes.length < 1) {
        throw new FileNotFoundException();
    }
    return resolver
            .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
            .createInputStream();
}

寻找 MIME 类型试试

private static String getMimeType(String url) {
    String type = null;
    String extension = MimeTypeMap.getFileExtensionFromUrl(url);
    if (extension != null) {
        type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
    }
    return type;
}

总体来说可以用

public static boolean saveFile(Context context, String name, Uri sourceuri, String destinationDir, String destFileName) {

    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
    InputStream input = null;
    boolean hasError = false;

    try {
        if (isVirtualFile(context, sourceuri)) {
            input = getInputStreamForVirtualFile(context, sourceuri, getMimeType(name));
        } else {
            input = context.getContentResolver().openInputStream(sourceuri);
        }

        boolean directorySetupResult;
        File destDir = new File(destinationDir);
        if (!destDir.exists()) {
            directorySetupResult = destDir.mkdirs();
        } else if (!destDir.isDirectory()) {
            directorySetupResult = replaceFileWithDir(destinationDir);
        } else {
            directorySetupResult = true;
        }

        if (!directorySetupResult) {
            hasError = true;
        } else {
            String destination = destinationDir + File.separator + destFileName;
            int originalsize = input.available();

            bis = new BufferedInputStream(input);
            bos = new BufferedOutputStream(new FileOutputStream(destination));
            byte[] buf = new byte[originalsize];
            bis.read(buf);
            do {
                bos.write(buf);
            } while (bis.read(buf) != -1);
        }
    } catch (Exception e) {
        e.printStackTrace();
        hasError = true;
    } finally {
        try {
            if (bos != null) {
                bos.flush();
                bos.close();
            }
        } catch (Exception ignored) {
        }
    }

    return !hasError;
}

private static boolean replaceFileWithDir(String path) {
    File file = new File(path);
    if (!file.exists()) {
        if (file.mkdirs()) {
            return true;
        }
    } else if (file.delete()) {
        File folder = new File(path);
        if (folder.mkdirs()) {
            return true;
        }
    }
    return false;
}

从 AsycTask 调用此方法。让我知道这是否有帮助。

【讨论】:

    【解决方案3】:
    private static String FILE_NAM  = "video";
    String outputfile = getFilesDir() + File.separator+FILE_NAM+"_tmp.mp4";
    
    InputStream in = getContentResolver().openInputStream(videoFileUri);
    private static File createFileFromInputStream(InputStream inputStream, String fileName) {
    
        try {
            File f = new File(fileName);
            f.setWritable(true, false);
            OutputStream outputStream = new FileOutputStream(f);
            byte buffer[] = new byte[1024];
            int length = 0;
    
            while((length=inputStream.read(buffer)) > 0) {
                outputStream.write(buffer,0,length);
            }
    
            outputStream.close();
            inputStream.close();
    
            return f;
        } catch (IOException e) {
            System.out.println("error in creating a file");
            e.printStackTrace();
        }
    
        return null;
    }
    

    【讨论】:

    • 嗨,为什么需要buffer[]?这是否特定于保存Uri
    • @Azurespot InputStream 和 OutputStream 使用字节缓冲区进行读写。在 inputStrea.read(buffer) 中,该方法获取缓冲区数组并填充它。设置为适当文件名的 OutputStream 获取缓冲区数组并写入其内容。但是,我找到了一个非常巧妙的解决方案来获取输入流并将其转换为字符串。把它放在某种 Util 类中,你就可以开始了。看这里:stackoverflow.com/a/5445161/4687402
    【解决方案4】:

    我使用以下代码将文件从 Intent 返回的现有 Uri 保存到我的应用托管的 Uri:

     private void copyFile(Uri pathFrom, Uri pathTo) throws IOException {
            try (InputStream in = getContentResolver().openInputStream(pathFrom)) {
                if(in == null) return;
                try (OutputStream out = getContentResolver().openOutputStream(pathTo)) {
                    if(out == null) return;
                    // Transfer bytes from in to out
                    byte[] buf = new byte[1024];
                    int len;
                    while ((len = in.read(buf)) > 0) {
                        out.write(buf, 0, len);
                    }
                }
            }
        }
    

    【讨论】:

    • ...因为已经发布了 7 个看起来相似的答案...请在您的回答中解释为什么您的回答比其他人更有利(否则它不属于低质量的回答,可能会在全部)。 (来自评论)。
    • 其他答案是从一个 Uri 到一个文件,在我的回答中,它显示了如何将文件从一个 Uri 保存到另一个 Uri。
    【解决方案5】:

    这是最简单和最干净的:

    private void saveFile(Uri sourceUri, File destination)
        try {
            File source = new File(sourceUri.getPath());
            FileChannel src = new FileInputStream(source).getChannel();
            FileChannel dst = new FileOutputStream(destination).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    

    【讨论】:

    • 嗨,destination 会是整个File 路径,包括文件名吗?还是只是目录名/路径?
    • 目标是 File 的类型,用它的名字表示整个文件。
    • 使用此代码我得到java.io.FileNotFoundException: /document/image:18019: open failed: ENOENT (No such file or directory)
    • @FilipHazubski 例外不言自明。
    • @ΕГИІИО 它表示您的代码不适用于 URI 返回的 Intent 打开图库以选择图像。至少它对我不起作用,可能对我的评论投了赞成票的人。我用 Android 5.1.1 测试过。
    【解决方案6】:

    从外部源接收android.net.Uri 时,保存文件的最佳方法是从流中:

    try (InputStream ins = activity.getContentResolver().openInputStream(source_uri)) {
        File dest = new File(destination_path);
        createFileFromStream(ins, dest);
    } catch (Exception ex) {
        Log.e("Save File", ex.getMessage());
        ex.printStackTrace();
    }
    

    createFileFromStream方法:

    public static void createFileFromStream(InputStream ins, File destination) {
        try (OutputStream os = new FileOutputStream(destination)) {
            byte[] buffer = new byte[4096];
            int length;
            while ((length = ins.read(buffer)) > 0) {
                os.write(buffer, 0, length);
            }
            os.flush();
        } catch (Exception ex) {
            Log.e("Save File", ex.getMessage());
            ex.printStackTrace();
        }
    }
    

    【讨论】:

      【解决方案7】:

      1.从 URI 路径创建文件为:

      File from = new File(uri.toString());
      

      2.在您希望将文件另存为的位置创建另一个文件:

      File to = new File("target file path");
      

      3.将文件重命名为:

      from.renameTo(to);
      

      这样,默认路径中的文件将被自动删除并在新路径中创建。

      【讨论】:

      • Gurmeet,如果我还必须保留现有文件,那么如何进行?我需要创建一个新文件或副本。
      • @CyBer2t 然后你需要一个文件副本。这是stackoverflow internal link 显示文件副本。
      • uri.toString 和 uri.toPath() 并不总是有效,尤其是在图库中。我怀疑使用输入流是要走的路。
      【解决方案8】:

      如何获取外部存储位置并保存文件

      这个答案不是针对问题,而是针对标题。 由于没有文章完全解释该过程,因此需要数小时才能弄清楚如何做到这一点,而其中一些已经存在多年并且使用了已弃用的 API。希望这可能对未来的开发者有所帮助。

      获取外部存储的位置

      例如,从片段内部,

      // when user choose file location
      private val uriResult = registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
          // do when user choose file location
          createFile(uri)
      }
      
      fun openFileChooser() {
          // startActivityForResult() is deprecated
          val suggestedFileName = "New Document.txt"
          uriResult.launch(suggestedFileName)
      }
      

      使用 Uri 写入文件数据

      android.net.Uri 创建java.io.File 似乎很困难,因为没有直接的方法可以将android.net.Uri 转换为java.net.URI。但是如果你有ApplicationContext,你可以很容易地做到这一点。

      fun createFile(uri: Uri) {
          try {
      requireContext().applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { fd ->
              FileOutputStream(fd).use { fos ->
      
                  // do your job on the FileOutputStream
                  // also use background thread
      
                  fos.close()
              }
          }
        } catch (e: Exception) {
      
        }
      }
      

      注意文件操作会抛出多个异常,请谨慎处理。并且还在工作线程中进行文件操作。

      【讨论】:

        【解决方案9】:

        你可以使用

        new File(uri.getPath());
        

        【讨论】:

        • uri.getPath() 可以解析为无效的文件路径。
        猜你喜欢
        • 2018-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        • 2021-07-16
        • 1970-01-01
        • 1970-01-01
        • 2015-09-13
        相关资源
        最近更新 更多