CameraX 保存图像到文件时出现异常:(权限被拒绝)

huangapple go评论45阅读模式
英文:

CameraX Saving images to file, FileNotFoundException: (Permission Denied)

问题

我正在使用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文件,我已经成功地使它们工作。

现在,我只需点击按钮来捕获图像,这不过是一个点击监听器。一旦我按下按钮,以下操作发生:

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, "测试", Toast.LENGTH_SHORT).show();

        new Handler().post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "图像保存成功", Toast.LENGTH_SHORT).show();
            }
        });
    }
    @Override
    public void onError(@NonNull ImageCaptureException error) {
        error.printStackTrace();
        //Toast.makeText(MainActivity.this, "图像保存失败!!!", Toast.LENGTH_SHORT).show();
    }
});

当应用程序首次启动时,它会请求权限,我授予权限。但是,当我按下捕获按钮时,查看Android Studio ID的运行选项卡,我会得到以下输出:

D/ImageCapture: 发送图像捕获请求 [current, pending] = [0, 1]
D/CaptureSession: 发出捕获请求。
W/example.camera: 线程[14,tid=10076,Runnable,Thread*=0xdc743000,peer=0x13184cf8,"Binder:10045_2"]上持有JNI关键锁21.148ms
W/ExifInterface: 由于错误的偏移可能导致无限循环,因此停止读取文件:0
    跳过标签条目,因为数据格式(ULONG)对于标签GPSAltitudeRef是意外的
W/ExifInterface: 由于错误的偏移可能导致无限循环,因此停止读取文件:0
    由于错误的偏移可能导致无限循环,因此停止读取文件:0
W/System.err: androidx.camera.core.ImageCaptureException: 无法写入或关闭文件
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)
    引起于:java.io.FileNotFoundException: /data/20200728142756.jpg: 打开失败:EACCES(权限被拒绝)
        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: 引起于:android.system.ErrnoException: 打开失败:EACCES(权限被拒绝)
        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: 线程[16,tid=10083,Runnable,Thread*=0xdc74bc00,peer=0x13184de8,"Binder:10045_4"]上持有JNI关键锁17.651ms

我已经搜索过并尝试了不同的保存文件方法,当我使用Camera2 API并使用OutputStream保存文件时,似乎可以成功保存,但我不知道它保存在哪里。我之所以指出这一点,是因为我看到的是当我打开设备文件资源管理器时,它不允许我查看/storage/emulated/,只显示以下内容:

ls: /storage/emulated/: 权限被拒绝。

所有我找到的示例都假定可以轻松访问emulated。有没有人能提供关于为什么我会出现权限被拒绝错误的见解?

更新

所以,我仍然没有解决与失败的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

<details>
<summary>英文:</summary>

I&#39;m working with Android Studio, writing in Java. I&#39;m trying to write a piece of software that will take pictures from a camera based on an event. So first steps first was to figure out the API. Camera is depreciated, so there&#39;s Camera2, but the latest is CameraX. I&#39;ve attempted to use both CameraX and Camera2, but for some reason I&#39;m not saving any images. I believe the capture works, but the issue is saving the files. I don&#39;t know what&#39;s going on. 

First lets start with permissions. Yes, I have set permissions for both the camera and external storage. 

   
        &lt;uses-permission android:name=&quot;android.permission.CAMERA&quot; /&gt;
        &lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;/&gt;
        &lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot;/&gt;
   
      
So lets move on to the code itself. 

I&#39;ve never saved photos before, but I have worked with text files such as .txt and .csv files. Which I&#39;ve been able to make work. 

Right now I do a simple button click to capture the image. Which is nothing more than an onclicklistener. Once I press the button this is what actions occur. 

                    SimpleDateFormat mDateFormat = new SimpleDateFormat(&quot;yyyyMMddHHmmss&quot;, Locale.US);
                    File file = new File(Environment.getDataDirectory(), mDateFormat.format(new Date())+ &quot;.jpg&quot;);
    
                    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, &quot;TEST&quot;, Toast.LENGTH_SHORT).show();
    
                            new Handler().post(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(MainActivity.this, &quot;Image Saved successfully&quot;, Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                        @Override
                        public void onError(@NonNull ImageCaptureException error) {
                            error.printStackTrace();
    //                        Toast.makeText(MainActivity.this, &quot;Image save FAILED!!!!&quot;, Toast.LENGTH_SHORT).show();
                        }
                    });



when the app launches the first time, it asks for permission and I grant permission. but looking at the run tab as part of the Android Studio ID, I get the following readout, when I press the capture button

    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,&quot;Binder:10045_2&quot;]
    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.&lt;init&gt;(FileOutputStream.java:235)
            at java.io.FileOutputStream.&lt;init&gt;(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,&quot;Binder:10045_4&quot;]


I&#39;ve searched around, and looked for different methods of saving the file, and I&#39;ve had success with saving the image, sort of, when I use the camera2 api and save the file using outputstream

When I use Camera2 it appears to save just fine, but I have no idea where it saves, I can&#39;t find it. I point this out, because it still appears to be a permission denied error as what I&#39;m seeing is when I open up Device File Explorer its not letting me view /storage/emulator/ it just shows me 

    ls: /storage/emulated/: Permission denied. 

[![enter image description here][1]][1]


  [1]: https://i.stack.imgur.com/6PCyU.png

all the examples I have found have assumed access with no issues to emulated. 

Does anyone have any insight as to why I&#39;m having permission denied errors?

__________**UPDATE**__________

So I still haven&#39;t solved the issue related to the failed mkdir() call. However, I did fix one issue, that could have been causing the issue. The means as to how I was creating the directory and the file itself wasn&#39;t correct. 



    SimpleDateFormat mDateFormat = new SimpleDateFormat(&quot;yyyyMMddHHmmss&quot;, Locale.US);
    File file = new File(getBatchDirectoryName(), mDateFormat.format(new Date())+ &quot;.jpg&quot;);
    
    public String getBatchDirectoryName() {
    
            String app_folder_path = &quot;&quot;;
            app_folder_path = Environment.getExternalStorageDirectory() + &quot;/images&quot;;
            File dir = new File(app_folder_path);
            if (!dir.exists() &amp;&amp; !dir.mkdirs()) {
                Toast.makeText(MainActivity.this, &quot;Trip&quot;, Toast.LENGTH_SHORT).show();
            }
    
            return app_folder_path;
        }

However, it still fails to create the directory. Which still makes me come back to a permission issue. 

Environment.getExternalStorageDirectory() appears to be deprecated. Documentation appears to recommend using getExternalFilesDir(string) or MediaStore. However, I&#39;m having issues finding sample use cases. And I can&#39;t imagine, that while deprecated, it would just stop working at all. Most devices on the market are not API 29 or higher.  

</details>


# 答案1
**得分**: 2

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

更多信息可以在这里找到

[请求传统外部存储][1]

通过 @blackapps 和 stackoverflow 上的这个帖子我成功更新了我的权限以允许传统访问

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

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

在您的清单文件的 <application> 标签内添加以下行

android:requestLegacyExternalStorage="true">


应该类似于这样
```xml
<?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>
</manifest>

请记住,这不是长期解决方案,当设备升级到 Android R 时将无效。

英文:

Based on help from @blackapps, I was able to make changes to my Manifest file to allow access to external storage.

More information can be found here

Request Legacy External Storage

With @blackapps, and this thread on stackoverflow I was able to update my permissions to allow legacy access.

Because in Android Q they have disabled direct file access, they have added a work around to allow legacy support. This is, and as others have mentioned, only a temporary fix for the issue. You will need to follow the new format for saving files going forward with Android R. In my situation, I am only developing a proof of concept and will not need to worry about future versions of Android.

To make the temporary fix, do the following

Add the following line

android:requestLegacyExternalStorage=&quot;true&quot;&gt;

inside your <application </application> bracket on the manifest file. Should look similar to this

    &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
...
package=&quot;com.example.camerax&quot;&gt;
...
&lt;application
...
android:requestLegacyExternalStorage=&quot;true&quot;&gt;
&lt;activity android:name=&quot;.MainActivity&quot;&gt;
&lt;intent-filter&gt;
...
&lt;/intent-filter&gt;
&lt;/activity&gt;
&lt;/application&gt;

</manifest>

remember this is not a long term fix, and will not work when a device is updated to Android R

huangapple
  • 本文由 发表于 2020年7月29日 02:57:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/63141030.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定