CameraX保存图像至文件,FileNotFoundException: (Permission Denied)

3

我正在使用Android Studio编写Java代码。 我想编写一个软件,基于某个事件从相机中拍摄照片。所以首先要做的是找到API。Camera已经过时了,现在有Camera2和更高版本的CameraX。我尝试使用CameraX和Camera2,但是出现了无法保存任何图像的问题。我相信拍摄功能是正常的,但问题在于保存文件。我不知道发生了什么。

首先,让我们谈一下权限。是的,我已经为相机和外部存储设置了权限。

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

  

现在我们来看看代码本身。

我以前从未保存过照片,但我已经处理过文本文件,例如.txt和.csv文件。我已经成功地完成了这些工作。

现在我只需要点击一个简单的按钮来捕获图像,这只是一个onclicklistener。按下按钮后,将执行以下操作。

                SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
                File file = new File(Environment.getDataDirectory(), mDateFormat.format(new Date())+ ".jpg");

                if(!file.exists())
                    file.mkdirs();

                ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
                imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback () {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        Toast.makeText(MainActivity.this, "TEST", Toast.LENGTH_SHORT).show();

                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.this, "Image Saved successfully", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                    @Override
                    public void onError(@NonNull ImageCaptureException error) {
                        error.printStackTrace();
//                        Toast.makeText(MainActivity.this, "Image save FAILED!!!!", Toast.LENGTH_SHORT).show();
                    }
                });

当应用程序第一次启动时,它会请求权限,我同意了。但是,当我按下捕获按钮时,在Android Studio ID的运行选项卡中看到以下输出结果。
D/ImageCapture: Send image capture request [current, pending] = [0, 1]
D/CaptureSession: Issuing capture request.
W/example.camera: JNI critical lock held for 21.148ms on Thread[14,tid=10076,Runnable,Thread*=0xdc743000,peer=0x13184cf8,"Binder:10045_2"]
W/ExifInterface: Stop reading file since a wrong offset may cause an infinite loop: 0
    Skip the tag entry since data format (ULONG) is unexpected for tag: GPSAltitudeRef
W/ExifInterface: Stop reading file since a wrong offset may cause an infinite loop: 0
    Stop reading file since a wrong offset may cause an infinite loop: 0
W/System.err: androidx.camera.core.ImageCaptureException: Failed to write or close the file
W/System.err:     at androidx.camera.core.ImageCapture$3.onError(ImageCapture.java:669)
        at androidx.camera.core.ImageSaver.lambda$postError$1$ImageSaver(ImageSaver.java:263)
        at androidx.camera.core.-$$Lambda$ImageSaver$eAp-cZyzsEk-LVLazzLE-ezQzwo.run(Unknown Source:8)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
    Caused by: java.io.FileNotFoundException: /data/20200728142756.jpg: open failed: EACCES (Permission denied)
        at libcore.io.IoBridge.open(IoBridge.java:496)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:235)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:186)
        at androidx.camera.core.ImageSaver.run(ImageSaver.java:97)
        ... 3 more
W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7255)
        at libcore.io.IoBridge.open(IoBridge.java:482)
        ... 6 more
W/example.camera: JNI critical lock held for 17.651ms on Thread[16,tid=10083,Runnable,Thread*=0xdc74bc00,peer=0x13184de8,"Binder:10045_4"]

我已经搜索过了,寻找了保存文件的不同方法,并且使用相机2 api并使用outputstream保存文件时,我在保存图像方面取得了一定的成功。

当我使用Camera2时,它似乎保存得很好,但我不知道它保存在哪里,我找不到它。 我指出这一点,因为它仍然似乎是一个权限被拒绝的错误,因为当我打开设备文件浏览器时,它不让我查看/storage/emulator/,它只会显示。

ls: /storage/emulated/: Permission denied. 

图片描述请见下方

我找到的所有示例都假定可以无问题地访问模拟文件系统。

有人能否解释一下为什么我会遇到“权限被拒绝”的错误?

更新

至今我仍未解决与失败的 mkdir() 调用相关的问题。然而,我确实修复了一个可能导致问题的问题。我创建目录和文件本身的方式不正确。

SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
File file = new File(getBatchDirectoryName(), mDateFormat.format(new Date())+ ".jpg");

public String getBatchDirectoryName() {

        String app_folder_path = "";
        app_folder_path = Environment.getExternalStorageDirectory() + "/images";
        File dir = new File(app_folder_path);
        if (!dir.exists() && !dir.mkdirs()) {
            Toast.makeText(MainActivity.this, "Trip", Toast.LENGTH_SHORT).show();
        }

        return app_folder_path;
    }

然而,它仍然无法创建目录。这使我回到了权限问题。
Environment.getExternalStorageDirectory()似乎已被弃用。文档建议使用getExternalFilesDir(string)或MediaStore。然而,我在寻找示例用例时遇到了问题。我无法想象,尽管被弃用,它会完全停止工作。市场上大多数设备的API版本不会高于29。

1
您的文件file定义了一个jpg文件,但是您在它上面使用了file.mkdirs();,这将创建一个目录。 - blackapps
1
file.mkdirs(); 创建文件夹。检查返回值,因为它可能失败。如果失败,请显示一个toast通知用户并返回。不要继续执行! - blackapps
@blackapps 你好。file.mkdirs() 的返回值总是 false。仔细查看文档,它明确说明“如果目录被创建则返回 true,否则返回 false”。因此,我假设在调用 mkdirs() 之前检查目录是否存在时,True 表示成功创建,而 false 表示创建失败。 - Michael
1
请在这里修改您的代码,并决定是否应该成为一个目录。这看起来不对。 - blackapps
我会深入研究。感谢你的来往回复。非常有帮助! - Michael
显示剩余2条评论
1个回答

2

在@blackapps的帮助下,我成功修改了我的清单文件以允许访问外部存储。

更多信息可以在这里找到

请求遗留的外部存储

通过@blackapps和stackoverflow上的这个线程,我能够更新我的权限以允许遗留支持。

因为在Android Q中他们已经禁用了直接文件访问,他们添加了一个解决方法来允许遗留支持。正如其他人所提到的,这只是问题的临时解决方案。您将需要按照Android R的新格式保存文件。在我的情况下,我只是在开发概念验证,并不需要担心未来版本的Android。

要进行临时修复,请执行以下操作

添加以下行

android:requestLegacyExternalStorage="true">

在您的清单文件中,应用程序括号内部。应该类似于这样。
    <?xml version="1.0" encoding="utf-8"?>
...
    package="com.example.camerax">

...

    <application
        ...
        android:requestLegacyExternalStorage="true">
        <activity android:name=".MainActivity">
            <intent-filter>
                ...
            </intent-filter>
        </activity>
    </application>

请记住,这不是长期解决方案,并且在设备升级到Android R后将无法使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接