开发者

Trouble writing internal memory android

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 co开发者_开发问答m.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.


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.


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) {

}


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.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜