android 写入内存时遇到问题

发布于 2024-10-21 06:23:40 字数 1277 浏览 1 评论 0原文

void launchImageCapture(Activity context) {
    Uri imageFileUri = context.getContentResolver()
        .insert(Media.INTERNAL_CONTENT_URI, new ContentValues());
    m_queue.add(imageFileUri);
    Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

    i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri); 
    context.startActivityForResult(i, ImportActivity.CAMERA_REQUEST); 
}

上面的代码一直有效,现在在 insert() 中为我生成此异常。

java.lang.UnsupportedOperationException: Writing to internal storage is not supported.
     at com.android.providers.media.MediaProvider.generateFileName(MediaProvider.java:2336)
     at com.android.providers.media.MediaProvider.ensureFile(MediaProvider.java:1851)
     at com.android.providers.media.MediaProvider.insertInternal(MediaProvider.java:2006)
     at com.android.providers.media.MediaProvider.insert(MediaProvider.java:1974)
     at android.content.ContentProvider$Transport.insert(ContentProvider.java:150)
     at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:140)
     at android.os.Binder.execTransact(Binder.java:287)
     at dalvik.system.NativeStart.run(Native Method)

这不是一个空间问题,我唯一改变的是一个不相关的类的包在一起。另外,我重新启动了手机。

void launchImageCapture(Activity context) {
    Uri imageFileUri = context.getContentResolver()
        .insert(Media.INTERNAL_CONTENT_URI, new ContentValues());
    m_queue.add(imageFileUri);
    Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

    i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri); 
    context.startActivityForResult(i, ImportActivity.CAMERA_REQUEST); 
}

The above code, which has always worked, is now generating this exception for me at insert().

java.lang.UnsupportedOperationException: Writing to internal storage is not supported.
     at com.android.providers.media.MediaProvider.generateFileName(MediaProvider.java:2336)
     at com.android.providers.media.MediaProvider.ensureFile(MediaProvider.java:1851)
     at com.android.providers.media.MediaProvider.insertInternal(MediaProvider.java:2006)
     at com.android.providers.media.MediaProvider.insert(MediaProvider.java:1974)
     at android.content.ContentProvider$Transport.insert(ContentProvider.java:150)
     at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:140)
     at android.os.Binder.execTransact(Binder.java:287)
     at dalvik.system.NativeStart.run(Native Method)

It is not a space issue, and the only thing I changed was the package of an unrelated class all together. Also, I restarted my phone.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

何止钟意 2024-10-28 06:23:40

在这里面临同样的问题,我很高兴找到这个线程。尽管在这个解决方法中有两件事困扰着我,但这篇文章让我找到了正确的方向。我想分享我自己的解决方法/解决方案。

让我首先阐述一下我不认为自己会经历的事情。

首先,我不想将应用程序私有文件保留为 MODE_WORLD_WRITEABLE。这对我来说似乎毫无意义,尽管我无法确切地弄清楚另一个应用程序如何访问此文件,除非知道在哪里使用完整的名称和路径查找它。我并不是说这对你的场景一定不好,但它仍然以某种方式困扰着我。我更愿意通过让图片文件对我的应用程序真正私有来覆盖我的所有基础。在我的业务案例中,图片在应用程序之外没有任何用处,并且绝不应该通过 Android Gallery 等方式删除它们。我的应用程序将在适当的时间触发清理,以免耗尽 Droid 设备存储空间。

其次,openFileOutput()不保留任何选项,而是将结果文件保存在getFilesDir()的根目录中。如果我需要一些目录结构来保持秩序怎么办?此外,我的应用程序必须处理不止一张图片,因此我希望生成文件名,以便稍后参考。

您看,使用相机拍摄照片并将其保存到 Droid 设备上的公共图像区域(通过 MediaStore)非常简单。从 MediaStore 操作(查询、更新、删除)媒体也很容易。有趣的是,将相机图片插入 MediaStore 会生成一个看起来唯一的文件名。为具有目录结构的应用程序创建私有文件也很容易。 “捕获相机图片并将其保存到内存”问题的症结在于您无法直接执行此操作,因为 Android 会阻止 ContentResolver 使用 Media.INTERNAL_CONTENT_URI,并且根据定义,私有应用程序文件是无法通过(外部)相机活动访问。

最后我采用了以下策略:

  1. 启动相机活动以获取我的应用程序的结果,并意图捕获图像。
  2. 返回我的应用程序时,将捕获插入 MediaStore。
  3. 查询MediaStore获取生成的图片文件名。
  4. 使用 Context.getDir() 在相对于私有应用程序数据文件夹的任何路径上创建真正的内部文件。
  5. 使用 OutputStream 将位图数据写入此私有文件。
  6. 从 MediaStore 中删除捕获。
  7. 可选)在我的应用程序中显示捕获的 ImageView。

这是启动摄像头的代码:

public void onClick (View v)
{
    ContentValues values = new ContentValues ();

    values.put (Media.IS_PRIVATE, 1);
    values.put (Media.TITLE, "Xenios Mobile Private Image");
    values.put (Media.DESCRIPTION, "Classification Picture taken via Xenios Mobile.");

    Uri picUri = getActivity ().getContentResolver ().insert (Media.EXTERNAL_CONTENT_URI, values);

    //Keep a reference in app for now, we might need it later.
    ((XeniosMob) getActivity ().getApplication ()).setCamPicUri (picUri);

    Intent takePicture = new Intent (MediaStore.ACTION_IMAGE_CAPTURE);

    //May or may not be populated depending on devices.
    takePicture.putExtra (MediaStore.EXTRA_OUTPUT, picUri);

    getActivity ().startActivityForResult (takePicture, R.id.action_camera_start);
}

这是我获取摄像头结果的活动:

@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data)
{
    super.onActivityResult (requestCode, resultCode, data);
    if (requestCode == R.id.action_camera_start)
    {
        if (resultCode == RESULT_OK)
        {
            Bitmap pic = null;
            Uri picUri = null;

            //Some Droid devices (as mine: Acer 500 tablet) leave data Intent null.
            if (data == null) {
                picUri = ((XeniosMob) getApplication ()).getCamPicUri ();
            } else
            {
                Bundle extras = data.getExtras ();
                picUri = (Uri) extras.get (MediaStore.EXTRA_OUTPUT);
            }

            try
            {
                pic = Media.getBitmap (getContentResolver (), picUri);
            } catch (FileNotFoundException ex)
            {
                Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, null, ex);
            } catch (IOException ex)
            {
                Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, null, ex);
            }

            //Getting (creating it if necessary) a private directory named app_Pictures
            //Using MODE_PRIVATE seems to prefix the directory name provided with "app_".
            File dir = getDir (Environment.DIRECTORY_PICTURES, Context.MODE_PRIVATE);

            //Query the MediaStore to retrieve generated filename for the capture.
            Cursor query = getContentResolver ().query (
                        picUri,
                        new String [] {
                            Media.DISPLAY_NAME,
                            Media.TITLE
                        },
                        null, null, null
                    );
            boolean gotOne = query.moveToFirst ();
            File internalFile = null;
            if (gotOne)
            {
                String dn = query.getString (query.getColumnIndexOrThrow (Media.DISPLAY_NAME));
                String title = query.getString (query.getColumnIndexOrThrow (Media.TITLE));
                query.close ();

                //Generated name is a ".jpg" on my device (tablet Acer 500).
                //I prefer to work with ".png".
                internalFile = new File (dir, dn.subSequence (0, dn.lastIndexOf (".")).toString () + ".png");
                internalFile.setReadable (true);
                internalFile.setWritable (true);
                internalFile.setExecutable (true);
                try
                {
                    internalFile.createNewFile ();

                    //Use an output stream to write picture data to internal file.
                    FileOutputStream fos = new FileOutputStream (internalFile);
                    BufferedOutputStream bos = new BufferedOutputStream (fos);

                    //Use lossless compression.
                    pic.compress (Bitmap.CompressFormat.PNG, 100, bos);

                    bos.flush ();
                    bos.close ();
                } catch (FileNotFoundException ex)
                {
                    Logger.getLogger (EvaluationActivity.class.getName()).log (Level.SEVERE, null, ex);
                } catch (IOException ex)
                {
                    Logger.getLogger (EvaluationActivity.class.getName()).log (Level.SEVERE, null, ex);
                }
            }

            //Update picture Uri to that of internal file.
            ((XeniosMob) getApplication ()).setCamPicUri (Uri.fromFile (internalFile));

            //Don't keep capture in public storage space (no Android Gallery use)
            int delete = getContentResolver ().delete (picUri, null, null);

            //rather just keep Uri references here
            //visit.add (pic);

            //Show the picture in app!
            ViewGroup photoLayout = (ViewGroup) findViewById (R.id.layout_photo_area);
            ImageView iv = new ImageView (photoLayout.getContext ());
            iv.setImageBitmap (pic);
            photoLayout.addView (iv, 120, 120);
        }
        else if (resultCode == RESULT_CANCELED)
        {
            Toast toast = Toast.makeText (this, "Picture capture has been cancelled.", Toast.LENGTH_LONG);
            toast.show ();
        }
    }
}

瞧!现在我们有了一个真正的应用程序私有图片文件,该文件的名称已由 Droid 设备生成。并且公共存储区域中不保留任何内容,从而防止意外的图片篡改。

Facing same problem here, I was happy to find this thread. Even though two things were bugging me in this workaround, this post had me looking in the right direction. I'd like to share my own workaround/solution.

Let me begin by stating what I did not see myself living with.

First, I did not want to leave the application private file as MODE_WORLD_WRITEABLE. This looks like non-sense to me, although I cannot figure exactly how another application could access this file unless knowing where to look for it with complete name and path. I'm not saying it is necessarily bad for your scenario, but it is still bugging me somehow. I would prefer to cover all my bases by having picture files really private to my app. In my business case, pictures are of no use outside of the application and by no means should they be deleteable via, say, the Android Gallery. My app will trigger cleanup at an appropriate time so as to not vampirize Droid device storage space.

Second, openFileOutput() do not leave any option but to save the resulting file in the root of getFilesDir(). What if I need some directory structure to keep things in order? In addition, my application must handle more than one picture, so I would like to have the filename generated so I can refer to it later on.

See, it is easy to capture a photo with the camera and save it to public image area (via MediaStore) on the Droid device. It is also easy to manipulate (query, update, delete) media from MediaStore. Interestingly, inserting camera picture to MediaStore genreates a filename which appears to be unique. It is also easy to create private File for an application with a directory structure. The crux of the "Capturea camera picture and save it to internal memory" problem is that you can't do so directly because Android prevents ContentResolver to use Media.INTERNAL_CONTENT_URI, and because private app files are by definition not accessible via the (outside) Camera activity.

Finally I adopted the following strategy:

  1. Start the Camera activity for result from my app with the Intent to capture image.
  2. When returning to my app, insert capture to the MediaStore.
  3. Query the MediaStore to obtain generated image file name.
  4. Create a truly internal file onto whatever path relative to private application data folder using Context.getDir().
  5. Use an OutputStream to write Bitmap data to this private file.
  6. Delete capture from MediaStore.
  7. (Optional) show an ImageView of the capture in my app.

Here is the code starting the cam:

public void onClick (View v)
{
    ContentValues values = new ContentValues ();

    values.put (Media.IS_PRIVATE, 1);
    values.put (Media.TITLE, "Xenios Mobile Private Image");
    values.put (Media.DESCRIPTION, "Classification Picture taken via Xenios Mobile.");

    Uri picUri = getActivity ().getContentResolver ().insert (Media.EXTERNAL_CONTENT_URI, values);

    //Keep a reference in app for now, we might need it later.
    ((XeniosMob) getActivity ().getApplication ()).setCamPicUri (picUri);

    Intent takePicture = new Intent (MediaStore.ACTION_IMAGE_CAPTURE);

    //May or may not be populated depending on devices.
    takePicture.putExtra (MediaStore.EXTRA_OUTPUT, picUri);

    getActivity ().startActivityForResult (takePicture, R.id.action_camera_start);
}

And here is my activity getting cam result:

@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data)
{
    super.onActivityResult (requestCode, resultCode, data);
    if (requestCode == R.id.action_camera_start)
    {
        if (resultCode == RESULT_OK)
        {
            Bitmap pic = null;
            Uri picUri = null;

            //Some Droid devices (as mine: Acer 500 tablet) leave data Intent null.
            if (data == null) {
                picUri = ((XeniosMob) getApplication ()).getCamPicUri ();
            } else
            {
                Bundle extras = data.getExtras ();
                picUri = (Uri) extras.get (MediaStore.EXTRA_OUTPUT);
            }

            try
            {
                pic = Media.getBitmap (getContentResolver (), picUri);
            } catch (FileNotFoundException ex)
            {
                Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, null, ex);
            } catch (IOException ex)
            {
                Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, null, ex);
            }

            //Getting (creating it if necessary) a private directory named app_Pictures
            //Using MODE_PRIVATE seems to prefix the directory name provided with "app_".
            File dir = getDir (Environment.DIRECTORY_PICTURES, Context.MODE_PRIVATE);

            //Query the MediaStore to retrieve generated filename for the capture.
            Cursor query = getContentResolver ().query (
                        picUri,
                        new String [] {
                            Media.DISPLAY_NAME,
                            Media.TITLE
                        },
                        null, null, null
                    );
            boolean gotOne = query.moveToFirst ();
            File internalFile = null;
            if (gotOne)
            {
                String dn = query.getString (query.getColumnIndexOrThrow (Media.DISPLAY_NAME));
                String title = query.getString (query.getColumnIndexOrThrow (Media.TITLE));
                query.close ();

                //Generated name is a ".jpg" on my device (tablet Acer 500).
                //I prefer to work with ".png".
                internalFile = new File (dir, dn.subSequence (0, dn.lastIndexOf (".")).toString () + ".png");
                internalFile.setReadable (true);
                internalFile.setWritable (true);
                internalFile.setExecutable (true);
                try
                {
                    internalFile.createNewFile ();

                    //Use an output stream to write picture data to internal file.
                    FileOutputStream fos = new FileOutputStream (internalFile);
                    BufferedOutputStream bos = new BufferedOutputStream (fos);

                    //Use lossless compression.
                    pic.compress (Bitmap.CompressFormat.PNG, 100, bos);

                    bos.flush ();
                    bos.close ();
                } catch (FileNotFoundException ex)
                {
                    Logger.getLogger (EvaluationActivity.class.getName()).log (Level.SEVERE, null, ex);
                } catch (IOException ex)
                {
                    Logger.getLogger (EvaluationActivity.class.getName()).log (Level.SEVERE, null, ex);
                }
            }

            //Update picture Uri to that of internal file.
            ((XeniosMob) getApplication ()).setCamPicUri (Uri.fromFile (internalFile));

            //Don't keep capture in public storage space (no Android Gallery use)
            int delete = getContentResolver ().delete (picUri, null, null);

            //rather just keep Uri references here
            //visit.add (pic);

            //Show the picture in app!
            ViewGroup photoLayout = (ViewGroup) findViewById (R.id.layout_photo_area);
            ImageView iv = new ImageView (photoLayout.getContext ());
            iv.setImageBitmap (pic);
            photoLayout.addView (iv, 120, 120);
        }
        else if (resultCode == RESULT_CANCELED)
        {
            Toast toast = Toast.makeText (this, "Picture capture has been cancelled.", Toast.LENGTH_LONG);
            toast.show ();
        }
    }
}

Voila! Now we have a truly application private picture file, which name has been generated by the Droid device. And nothing is kept in the public storage area, thus preventing accidental picture manipulation.

请恋爱 2024-10-28 06:23:40

这是我的工作代码,用于将相机捕获的图像保存到应用程序内部存储:

首先,使用所需的文件名创建文件。在本例中它是“MyFile.jpg”,然后按照以下意图启动活动。您的回调方法(onActivityResult)将在完成后被调用。调用 onActivityResult 后,您的图像应保存到内部存储中。关键提示:openFileOutput 中使用的模式需要是全局的。Context.MODE_WORLD_WRITEABLE 工作正常,我还没有测试其他模式。

try {
FileOutputStream fos = openFileOutput("MyFile.jpg", Context.MODE_WORLD_WRITEABLE);
fos.close();
File f = new File(getFilesDir() + File.separator + "MyFile.jpg");
startActivityForResult(
        new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            .putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f))
        , IMAGE_CAPTURE_REQUEST_CODE);
}
catch(IOException e) {

}

并在活动结果方法中:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == IMAGE_CAPTURE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        Log.i(TAG, "Image is saved.");
    }
}

检索您的图像:

try {
InputStream is = openFileInput("MyFile.jpg");
BitmapFactory.Options options = new BitmapFactory.Options();
//options.inSampleSize = 4;
Bitmap retrievedBitmap = BitmapFactory.decodeStream(is, null, options);
}
catch(IOException e) {

}

here is my working code to save a captured image from the camera to app internal storage:

first, create the file with the desired filename. in this case it is "MyFile.jpg", then start the activity with the intent below. you're callback method(onActivityResult), will be called once complete. After onActivityResult has been called your image should be saved to internal storage. key note: the mode used in openFileOutput needs to be global.. Context.MODE_WORLD_WRITEABLE works fine, i have not tested other modes.

try {
FileOutputStream fos = openFileOutput("MyFile.jpg", Context.MODE_WORLD_WRITEABLE);
fos.close();
File f = new File(getFilesDir() + File.separator + "MyFile.jpg");
startActivityForResult(
        new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            .putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f))
        , IMAGE_CAPTURE_REQUEST_CODE);
}
catch(IOException e) {

}

and in the activity result method:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == IMAGE_CAPTURE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        Log.i(TAG, "Image is saved.");
    }
}

to retrieve your image:

try {
InputStream is = openFileInput("MyFile.jpg");
BitmapFactory.Options options = new BitmapFactory.Options();
//options.inSampleSize = 4;
Bitmap retrievedBitmap = BitmapFactory.decodeStream(is, null, options);
}
catch(IOException e) {

}
一瞬间的火花 2024-10-28 06:23:40

该相机显然不支持写入内部存储。

不幸的是,文档中没有提到这一点。

MediaProvider.java 有以下代码:

private String generateFileName(boolean internal,
    String preferredExtension, String directoryName)
{
     // create a random file
    String name = String.valueOf(System.currentTimeMillis());

    if (internal) {
        throw new UnsupportedOperationException(
            "Writing to internal storage is not supported.");
//      return Environment.getDataDirectory()
//          + "/" + directoryName + "/" + name + preferredExtension;
    } else {
        return Environment.getExternalStorageDirectory()
            + "/" + directoryName + "/" + name + preferredExtension;
    }
}

因此暂时禁止写入内部存储。

编辑 - 我认为你可以使用 binnyb 的方法作为解决方法,但我不推荐它;我不确定这是否会在未来的版本中继续有效。我认为其目的是禁止写入媒体文件的内部存储。

我在 Android 问题跟踪器中提交了错误

编辑 - 我现在明白为什么宾尼布的方法有效。相机应用程序被认为只是另一个应用程序。如果没有权限,则无法写入内部存储。将文件设置为全局可写会授予其他应用程序写入该文件的权限。

然而,我仍然认为这不是一个好主意,原因如下:

  • 您通常不希望其他应用程序写入您的私人存储。
  • 某些手机​​的内部存储空间非常有限,并且原始相机图像非常大。
  • 如果您打算调整图像大小,那么您可以从外部存储读取它并自行将其写入内部存储。

The camera apparently doesn't support writing to internal storage.

Unfortunately this is not mentioned in the documentation.

MediaProvider.java has the following code:

private String generateFileName(boolean internal,
    String preferredExtension, String directoryName)
{
     // create a random file
    String name = String.valueOf(System.currentTimeMillis());

    if (internal) {
        throw new UnsupportedOperationException(
            "Writing to internal storage is not supported.");
//      return Environment.getDataDirectory()
//          + "/" + directoryName + "/" + name + preferredExtension;
    } else {
        return Environment.getExternalStorageDirectory()
            + "/" + directoryName + "/" + name + preferredExtension;
    }
}

So writing to internal storage has been intentionally disabled for the time being.

Edit - I think you can use binnyb's method as a work-around, but I wouldn't recommend it; I'm not sure if this will continue to work on future versions. I think the intention is to disallow writing to internal storage for media files.

I filed a bug in the Android issue tracker.

Edit - I now understand why binnyb's method works. The camera app is considered to be just another application. It can't write to internal storage if it doesn't have permissions. Setting your file to be world-writable gives other applications permission to write to that file.

I still don't think that this is a very good idea, however, for a few reasons:

  • You don't generally want other apps writing to your private storage.
  • Internal storage is quite limited on some phones, and raw camera images are quite large.
  • If you were planning on resizing the image anyway, then you can read it from external storage and write it yourself to your internal storage.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文