如何在data文件夹中以URI形式获取相机结果?

67

我正在创建一个应用程序,需要捕获图像,并将其作为附件通过电子邮件发送。

我使用android.provider.MediaStore.ACTION_IMAGE_CAPTURE意图操作打开相机,并将文件的Uri作为参数EXTRA_OUTPUT传递以将图像返回到该文件。如果我在EXTRA_OUTPUT中使用外部存储器Uri,则完美运行,我能够获取捕获的图像,但如果我使用数据文件夹uri,则无法正常工作,相机不会关闭,并且其所有按钮都无法使用。

以下是我在外部存储目录中获取结果的代码:

Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
File out = Environment.getExternalStorageDirectory();
out = new File(out, imagename);
i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(out));
startActivityForResult(i, CAMERA_RESULT);

这段代码用于获取数据文件夹中的图像

Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
File out = getFilesDir();
out = new File(out, MyPharmacyOptions.PRESCRIPTION_IMAGE_NAME);
i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(out));
startActivityForResult(i, CAMERA_RESULT);

我知道数据文件夹对于第三方应用程序是不可访问的,所以这可能会导致问题,因此我创建了一个内容提供者来共享文件。

这是我的内容提供者类:

public class MyContentProvider extends ContentProvider {
    private static final String Tag = RingtonContentProvider.class.getName();
    public static final Uri CONTENT_URI = Uri
            .parse("content://x.y.z/");
    private static final HashMap<String, String> MIME_TYPES = new HashMap<String, String>();

    static {
        MIME_TYPES.put(".mp3", "audio/mp3");
        MIME_TYPES.put(".wav", "audio/mp3");
        MIME_TYPES.put(".jpg", "image/jpeg");
    }

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public String getType(Uri uri) {
        String path = uri.toString();

        for (String extension : MIME_TYPES.keySet()) {
            if (path.endsWith(extension)) {
                return (MIME_TYPES.get(extension));
            }
        }

        return (null);
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode)
            throws FileNotFoundException {
        File f = new File(getContext().getFilesDir(), uri.getPath());

        if (f.exists()) {
            return (ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY));
        }

        throw new FileNotFoundException(uri.getPath());
    }

    @Override
    public Cursor query(Uri url, String[] projection, String selection,
            String[] selectionArgs, String sort) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        File file = new File(getContext().getFilesDir(), uri.getPath());
        if(file.exists()) file.delete();
        try {
            file.createNewFile();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return Uri.fromFile(file);
    }

    @Override
    public int update(Uri uri, ContentValues values, String where,
            String[] whereArgs) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public int delete(Uri uri, String where, String[] whereArgs) {
        File f = new File(getContext().getFilesDir(), "image1.jpg");
        if(f.exists()) f.delete();
        f = new File(getContext().getFilesDir(), "image2.jpg");
        if(f.exists()) f.delete();

        getContext().getContentResolver().notifyChange(CONTENT_URI, null);

    }
}

为了使用此内容提供程序,我使用以下代码将uri传递给相机活动

Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
Uri uri = MyContentProvider.CONTENT_URI;
uri = Uri.withAppendedPath(uri, imagename);
getContentResolver().insert(uri, null);
getContentResolver().notifyChange(RingtonContentProvider.CONTENT_URI, null);
Log.d(Tag, uri.toString());
i.putExtra(MediaStore.EXTRA_OUTPUT, uri);

startActivityForResult(i, CAMERA_RESULT);

如果我传递的URL不是外部存储目录,相机会打开但在模拟器上无法关闭,但在设备上相机可以关闭,但我没有得到结果。

我已在清单文件中声明了此内容提供程序。

<provider
android:name=".contentproviders.MyContentProvider"
android:authorities="x.y.z" />

我已经被授权可以写入外部存储并且也可以使用相机

我能够使用外部存储捕获图像,但是我想将图像存储在数据目录中,而不是外部存储,因为如果外部存储不可用,我想要捕获图像并发送邮件。

如果我创建内容提供程序,那么我也可以将我的图像共享到电子邮件应用程序中。

如果我们在相机意图中不提供附加项,它会将图像作为字节数组返回给活动结果的数据附加项,但这只适用于缩略图,所以我无法以这种方式获取高分辨率图像。


public int delete(Uri uri, String where, String[] whereArgs) 的返回值是什么? - George
6个回答

71
有两种方法可以解决这个问题。 1. 保存从onActivityResult方法接收到的位图 您可以通过以下代码启动相机拍摄照片:
Intent cameraIntent=new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent, CAMERA_REQUEST);

在拍摄照片后,您将在onActivityResult方法中获得位图。
if (requestCode == CAMERA_REQUEST) {  
    Bitmap photo = (Bitmap) data.getExtras().get("data"); 
 }

现在,您可以将此位图简单地保存到内部存储器
注意:这里的位图对象包含缩略图像,而不是完整分辨率的图像
2. 使用内容提供程序直接将位图保存到内部存储器
我们将创建内容提供程序类以允许相机活动访问本地存储目录的权限
以下是示例提供程序示例
public class MyFileContentProvider extends ContentProvider {
    public static final Uri CONTENT_URI = Uri.parse
                                    ("content://com.example.camerademo/");
    private static final HashMap<String, String> MIME_TYPES = 
                                     new HashMap<String, String>();

    static {
        MIME_TYPES.put(".jpg", "image/jpeg");
        MIME_TYPES.put(".jpeg", "image/jpeg");
    }

    @Override
    public boolean onCreate() {

        try {
            File mFile = new File(getContext().getFilesDir(), "newImage.jpg");
            if(!mFile.exists()) {
                mFile.createNewFile();
            }
            getContext().getContentResolver().notifyChange(CONTENT_URI, null);
            return (true);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    @Override
    public String getType(Uri uri) {
        String path = uri.toString();

        for (String extension : MIME_TYPES.keySet()) {
            if (path.endsWith(extension)) {
                return (MIME_TYPES.get(extension));
            }
        }
        return (null);
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode)
    throws FileNotFoundException {

        File f = new File(getContext().getFilesDir(), "newImage.jpg");
        if (f.exists()) {
            return (ParcelFileDescriptor.open(f,
                    ParcelFileDescriptor.MODE_READ_WRITE));
        }
        throw new FileNotFoundException(uri.getPath());
    }
}

之后,您可以使用以下代码将URI传递给相机活动

Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, MyFileContentProvider.CONTENT_URI);
startActivityForResult(i, CAMERA_RESULT);

如果您不想创建自己的提供程序,则可以使用来自support-library-v4的FileProvider。有关详细帮助,请查看此文章

2
我喜欢第一种解决方案的简单性,并在我的应用程序中实现了它。但是,它只能获取图像的低分辨率版本... - StuStirling
3
将文件名复制到全局变量中,然后在onActivityResult()方法中使用该变量。 - Dharmendra
@Dharmendra,你尝试过你的解决方案2吗?还是在复制时错过了什么? - user1914692
@user1914692 你所说的“复制到其他地方”是什么意思? 我已经在我的某个应用程序中编写了这段代码,它可以正常工作。 你可以查看我在博客链接中详细描述解决方案的文章。 - Dharmendra
抱歉,您的代码非常棒。但是这里的复制品并不完整。我先尝试了一下,失败了,并意识到许多方法都没有实现。然后我按照博客上的步骤操作,它成功了!谢谢。但请注意不要提供不完整的解决方案而不说明。我遇到过类似的问题很多次。 - user1914692
显示剩余6条评论

21

我找到的最佳解决方案是:FileProvider(需要支持库v4)它使用内部存储! https://developer.android.com/reference/android/support/v4/content/FileProvider.html

  1. Define your FileProvider in Manifest in Application element:

    <provider
          android:name="android.support.v4.content.FileProvider"
          android:authorities="your.package.name.fileprovider"
          android:exported="false"
          android:grantUriPermissions="true" >
          <meta-data
                     android:name="android.support.FILE_PROVIDER_PATHS"
                     android:resource="@xml/image_path" />
    </provider>
    
  2. Add permissions in manifest root element:

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />
    
  3. Define your image paths in for example res/xml/image_path.xml:

    <paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="captured_image" path="your/path/"/>
    </paths>
    
  4. Java:

    private static final int IMAGE_REQUEST_CODE = 1;
    // your authority, must be the same as in your manifest file 
    private static final String CAPTURE_IMAGE_FILE_PROVIDER = "your.package.name.fileprovider";
    

4.1 捕获意图:

    File path = new File(activity.getFilesDir(), "your/path");
    if (!path.exists()) path.mkdirs();
    File image = new File(path, "image.jpg");
    Uri imageUri = FileProvider.getUriForFile(activity, CAPTURE_IMAGE_FILE_PROVIDER, image);
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    startActivityForResult(intent, IMAGE_REQUEST_CODE);

4.2 onActivityResult():

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == IMAGE_REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                File path = new File(getFilesDir(), "your/path");
                if (!path.exists()) path.mkdirs();
                File imageFile = new File(path, "image.jpg");
                // use imageFile to open your image
            }
        }
        super.onActivityResult(requestCode, resultCode, intent);
    }

CAPTURE_IMAGE_PROVIDER 是谁? - Radu
// 您的权限必须与清单文件中的相同 private static final String CAPTURE_IMAGE_FILE_PROVIDER = "your.package.name.fileprovider"; - Harry
@Harry android:authorities="your.package.name.fileprovider" 这是什么路径?你能简单解释一下fileprovider吗? - ULHAS PATIL
不要使用 Uri imageUri = FileProvider.getUriForFile(activity, CAPTURE_IMAGE_FILE_PROVIDER, image);,而是使用 Uri imageUri = FileProvider.getUriForFile(activity, getApplicationContext().getPackageName() + ". fileprovider, image);" - ClassA

5

意图调用以捕获照片,

Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);  
startActivityForResult(cameraIntent, CAMERA_REQUEST);  

那么在ActivityResult中获取位图。
 if (requestCode == CAMERA_REQUEST) {   
            Bitmap photo = (Bitmap) data.getExtras().get("data");  
 }   

将其写入内部存储器,参见此处

// The openfileOutput() method creates a file on the phone/internal storage in the context of your application  
final FileOutputStream fos = openFileOutput("my_new_image.jpg", Context.MODE_PRIVATE); 

// Use the compress method on the BitMap object to write image to the OutputStream 
bm.compress(CompressFormat.JPEG, 90, fos); 

下次读取该文件时,

Bitmap bitmap = BitmapFactory.decodeFile(file); 

1
很好,但是我现在得到的是缩略图,我想获取原始图片,怎么办? - kalyan pvs

3

首先将照片保存在外部存储中,然后尝试使用它 -

@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.main);
   this.imageView = (ImageView)this.findViewById(R.id.imageView1);
   Button photoButton = (Button) this.findViewById(R.id.button1);
   photoButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); 
        startActivityForResult(cameraIntent, CAMERA_REQUEST); 
    }
});
}

protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
   if (requestCode == CAMERA_REQUEST) {  
        Bitmap bmp = intent.getExtras().get("data");
        ByteArrayOutputStream stream = new ByteArrayOutputStream();

         bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
         byte[] byteArray = stream.toByteArray(); // convert camera photo to byte array

         // save it in your external storage.
        FileOutputStream fo = new FileOutputStream(new File(Environment.getExternalStorageDirectory() + "/_camera.png"));

        fo.write(byteArray);
        fo.flush();
        fo.close();
   }  
} 

下一个目标 -
File cameraFile = new File(Environment.getExternalStorageDirectory() + "/_camera.png");                 
startActivityForResult(Intent.createChooser(new Intent(Intent.ACTION_SEND)
        .setType("image/jpg")
        .putExtra(Intent.EXTRA_SUBJECT, "Subject")
        .putExtra(Intent.EXTRA_STREAM, Uri.fromFile(cameraFile))
        .putExtra(Intent.EXTRA_TEXT, textBody), "Send your message using"), Constant.EMAIL);

我能够使用外部存储捕获图像,但我想将图像存储在数据目录中,而不是外部存储中,因为如果外部存储不可用,我想要捕获图像并发送邮件。如果我创建内容提供程序,则还可以将我的图像共享到电子邮件应用程序。 - Dharmendra
或者你也可以尝试使用 - bmp.compress(Bitmap.CompressFormat.PNG, 0, stream); - Suvam Roy
使用这种方法,如果您更改相机的分辨率,则结果不会受到影响。它将保持相机的任何设置不变。 - Dharmendra
尝试一下 - cameraIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, [</data/data/<your package name>/camera.png>]); startActivity(cameraIntent); - Suvam Roy
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/9779/discussion-between-dharmendra-and-suvam-roy - Dharmendra
显示剩余8条评论

1
经过一段时间的研究,我发现调用相机意图的活动仅在手机内存不足时重新启动。因此,由于活动被重新启动,保存捕获图像路径或Uri的对象会被刷新(置空)。
因此,我建议您在onActivityResult中捕获/检测空对象,并提示用户释放设备上的空间或重新启动手机以进行快速临时修复。

1

您可以不需要内容提供者也可以这样做,因为无论如何您都需要SD卡来打开相机图像捕获意图。当然,您可以绕过SD卡的存在,但不能使用相机意图捕获....所以您正在检查外部存储器,但此时需要它....顺便说一下,您应该查看一个裁剪库(例如crop-image),而不是使用本地库,因为它在设备上的支持不好。

File mediaStorageDir;
String photoFileName = "photo.jpg";


    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // page = getArguments().getInt("someInt", 0);
    // title = getArguments().getString("someTitle");
    // Get safe storage directory for photos
        mediaStorageDir = new File(
                Environment                          .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                APP_TAG);
        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()) {
            Log.d(APP_TAG, "Directory exists: " + mediaStorageDir.isDirectory());
            Log.d(APP_TAG, "Directory exists: " + mediaStorageDir.getPath());
            Log.d(APP_TAG,
                    "Directory exists: "
                            + Environment.getExternalStorageState());
            Log.d(APP_TAG, "failed to create directory");
        }

}

在你的“拍照”代码中:
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, getPhotoFileUri(photoFileName));

...

public Uri getPhotoFileUri(String fileName) {

    return Uri.fromFile(new File(mediaStorageDir.getPath() + File.separator
            + fileName));
}

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