3

content://com.android.externalstorage.documents/tree/primary:如果选择了设备的主存储器,我需要从 DocumentFile 或 Uri 获取具有正确方案的文件,而不是使用正确方案的文件。要获取图像的文件或绝对路径,我需要使用 file:///storage/emulated/0 或 storage/emulated/0 的路径,但我找不到一种方法来获取正确的 Uri 来构建文件以将 EXIF 数据写入图片。

我的情况是:

  1. 用户选择保存图像的路径,该路径使用content://com.android.externalstorage.documentsonActivityResult() 返回 Uri。我将此路径保存treeUri.toString()到 SharedPreferences 以供以后使用。
  2. 用户拍照并保存图像DocumentFile.fromTreeUri(MainActivity.this, Uri.parse(uriString));
  3. 这是我失败的地方,得到一个正确指向图像的文件,带有 content:// 的 Uri 不返回现有图像。正确的 Uri 应该file:///storage/emulated/并且我可以使用此 Uri 将此 Uri 转换为文件File filePath = new File(URI.create(saveDir.getUri().toString()));

如何使用从 SAF UI 获得的 Uri 获取构建文件或文件所需的 Uri?

编辑: 为可以使用 InputStream 或 FileDescriptor 的 Android 7.1+ 引入了ExifInterface 支持库。

Uri uri; // the URI you've received from the other app
InputStream in;
try {
  in = getContentResolver().openInputStream(uri);
  ExifInterface exifInterface = new ExifInterface(in);
  // Now you can extract any Exif tag you want
  // Assuming the image is a JPEG or supported raw format
} catch (IOException e) {
  // Handle any errors
} finally {
  if (in != null) {
    try {
      in.close();
    } catch (IOException ignored) {}
  }
}

注意: ExifInterface 不适用于远程 InputStream,例如从 HttpURLConnection 返回的那些。强烈建议仅将它们与 content:// 或 file:// URI 一起使用。

上面的代码段显然是在读取数据,因为它打开了一个 InputStream 来读取数据。我需要能够将 EXIF 数据写入 JPEG 文件。

4

1 回答 1

3

FileDescriptor如果 Api 为 24 或更高,则将Exif 数据写入先前保存且具有已知内容 Uri 的图像的答案

private void writeEXIFWithFileDescriptor(Uri uri) {

    if (Build.VERSION.SDK_INT < 24) {
        showToast("writeEXIFWithInputStream() API LOWER 24", Toast.LENGTH_SHORT);
        return;
    }

    ParcelFileDescriptor parcelFileDescriptor = null;
    try {

        parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(uri, "rw");
        FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
        showToast("writeEXIFWithFileDescriptor(): " + fileDescriptor.toString(), Toast.LENGTH_LONG);
        ExifInterface exifInterface = new ExifInterface(fileDescriptor);
        // TODO Create  Exif Tags class to save Exif data
        exifInterface.saveAttributes();

    } catch (FileNotFoundException e) {
        showToast("File Not Found " + e.getMessage(), Toast.LENGTH_LONG);

    } catch (IOException e) {
        // Handle any errors
        e.printStackTrace();
        showToast("IOEXception " + e.getMessage(), Toast.LENGTH_LONG);
    } finally {
        if (parcelFileDescriptor != null) {
            try {
                parcelFileDescriptor.close();
            } catch (IOException ignored) {
                ignored.printStackTrace();
            }
        }
    }
}

如果 Api 低于 24,则需要DocumentFile在写入 Exif 数据后使用缓冲区文件并将该缓冲区文件保存到实际位置。

private boolean exportImageWithEXIF(Bitmap bitmap, DocumentFile documentFile) {
        OutputStream outputStream = null;
        File bufFile = new File(Environment.getExternalStorageDirectory(), "buffer.jpg");
        long freeSpace = Environment.getExternalStorageDirectory().getFreeSpace() / 1048576;
        double bitmapSize = bitmap.getAllocationByteCount() / 1048576d;

        showToast("exportImageWithEXIF() freeSpace " + freeSpace, Toast.LENGTH_LONG);
        showToast("exportImageWithEXIF() bitmap size " + bitmapSize, Toast.LENGTH_LONG);
        try {
            outputStream = new FileOutputStream(bufFile);
            // Compress image from bitmap with JPEG extension
            if (mCameraSettings.getImageFormat().equals(Constants.IMAGE_FORMAT_JPEG)) {
                isImageSaved = bitmap.compress(CompressFormat.JPEG, mCameraSettings.getImageQuality(), outputStream);
                showToast("isImageSaved: " + isImageSaved, Toast.LENGTH_SHORT);
            }

            if (isImageSaved) {
                writeEXIFWithFile(bufFile);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        OutputStream os = null;
        InputStream is = null;
        try {
            int len;
            byte[] buf = new byte[4096];

            os = mContext.getContentResolver().openOutputStream(documentFile.getUri());
            is = new FileInputStream(bufFile);

            while ((len = is.read(buf)) > 0) {
                os.write(buf, 0, len);
            }

            os.close();
            is.close();

            if (bufFile != null) {
                bufFile.delete();
                bufFile = null;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return isImageSaved;
    }

这两种方法都可用于将 Exif 数据写入保存到设备内存或 SD 卡的图像。您还可以使用来自 Storage Access Framework 的有效 Uri 将图像保存到 SD 卡。

我还找到了一种从内容 Uri 获取内存和 SD 卡的绝对路径的方法,但这与这个问题无关,鼓励使用 Uri 而不是绝对路径,并且不会导致未注意到的错误,我也无法将图像保存到具有绝对路径的 SD 卡,只能从中读取。

于 2017-10-15T12:56:45.703 回答