在 Nougat 上使用 Scoped Directory Access 捕获图像

Capture image with Scoped Directory Access on Nougat

我正在尝试使用 Scoped Directory Access 从相机拍摄图像,但它给了我以下异常,

    java.io.IOException: Permission denied
    09-22 22:43:40.556 23767-23767/mp.wall.client W/System.err:     at java.io.UnixFileSystem.createFileExclusively0(Native Method)
09-22 22:43:40.556 23767-23767/mp.wall.client W/System.err:     at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:280)
09-22 22:43:40.556 23767-23767/mp.wall.client W/System.err:     at java.io.File.createNewFile(File.java:948)
09-22 22:43:40.556 23767-23767/mp.wall.client W/System.err:     at java.io.File.createTempFile(File.java:1862)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at mp.wall.utility.imageCapturePickerCompressor.ImageCapture.getCameraPicturesLocation(ImageCapture.java:91)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at mp.wall.utility.imageCapturePickerCompressor.ImageCapture.createCameraPictureFile(ImageCapture.java:64)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at mp.wall.utility.imageCapturePickerCompressor.ImageCapture.createCameraIntent(ImageCapture.java:50)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at mp.wall.utility.imageCapturePickerCompressor.ImageCapture.openCamera(ImageCapture.java:116)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at mp.wall.ui.dashboard.viewInfo.personalInfo.editProfile.EditProfile.onActivityResult(EditProfile.java:219)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at mp.wall.ui.dashboard.viewInfo.personalInfo.editProfile.imagePickerDialog.ImagePickerDialogFragment.sideMenuItem(ImagePickerDialogFragment.java:65)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at mp.wall.ui.dashboard.viewInfo.personalInfo.editProfile.imagePickerDialog.ItemAdapter$VHItem.onClick(ItemAdapter.java:58)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at android.view.View.performClick(View.java:6205)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at android.widget.TextView.performClick(TextView.java:11103)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at android.view.View$PerformClick.run(View.java:23653)
09-22 22:43:40.557 23767-23767/mp.wall.client W/System.err:     at android.os.Handler.handleCallback(Handler.java:751)
09-22 22:43:40.558 23767-23767/mp.wall.client W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)
09-22 22:43:40.558 23767-23767/mp.wall.client W/System.err:     at android.os.Looper.loop(Looper.java:154)
09-22 22:43:40.558 23767-23767/mp.wall.client W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6682)
09-22 22:43:40.558 23767-23767/mp.wall.client W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
09-22 22:43:40.558 23767-23767/mp.wall.client W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1534)
09-22 22:43:40.558 23767-23767/mp.wall.client W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1424)

根据 documentation of google,它是在您的清单中请求 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 的替代方法,我的代码在 Naugat 下面工作得很好但是因为我已经删除了 Naugat 的这些权限并使用了 Scoped Directory Access 但它抛出了上述错误。

以下是我的片段代码,

package mp.wall.ui.dashboard.viewInfo.personalInfo.editProfile;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;

import java.io.File;

import mp.wall.R;
import mp.wall.controllers.constants.NumericEnums;
import mp.wall.controllers.constants.RestConstants;
import mp.wall.ui.dashboard.viewInfo.personalInfo.editProfile.imagePickerDialog.ImagePickerDialogFragment;
import mp.wall.utility.AndroidVersions;
import mp.wall.utility.ContextFunction;
import mp.wall.utility.IMPermission;
import mp.wall.utility.Intents;
import mp.wall.utility.imageCapturePickerCompressor.ImageCapture;
import   mp.wall.utility.imageCapturePickerCompressor.ImageCapturePickerCallbacks;
import tr004.libraryutils.LogUtil;

public class EditProfile extends Fragment implements View.OnClickListener, ImageCapturePickerCallbacks {

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    return inflater.inflate(R.layout.fragment_edit_profile, container, false);
}

@Override
public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    ImageView imgBack = ContextFunction.findViewByIdAndCast(view, R.id.hb_img_back);
    imgBack.setOnClickListener(this);

    TextView tvTitle = ContextFunction.findViewByIdAndCast(view, R.id.hb_tv_title);
    tvTitle.setText(ContextFunction.getStringResource(context, R.string.edit_info_profile_title));
    tvTitle.setGravity(Gravity.LEFT);

    TextView tvSave = ContextFunction.findViewByIdAndCast(view, R.id.hb_tv_action);
    tvSave.setText(ContextFunction.getStringResource(context, R.string.edit_info_profile_save));
    ContextFunction.viewShow(tvSave);
    tvSave.setOnClickListener(this);

    ImageView imgCam = ContextFunction.findViewByIdAndCast(view, R.id.fep_img_camGallery);
    imgCam.setOnClickListener(this);

    Button btChangePassword = ContextFunction.findViewByIdAndCast(view, R.id.fep_bt_changePassword);
    btChangePassword.setOnClickListener(this);
}

@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.fep_img_camGallery:
            if(AndroidVersions.isNaugatOrHighier())
            {
                StorageManager sm = (StorageManager)context.getSystemService(Context.STORAGE_SERVICE);
                StorageVolume volume = sm.getPrimaryStorageVolume();
                Intent intent = volume.createAccessIntent(Environment.DIRECTORY_DCIM);
                startActivityForResult(intent, 2001);
            }else
                openGalleryAndCamera();
            break;
    }
}

private void openGalleryAndCamera() {
    String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
    if (IMPermission.hasPermissions(this, perms))
        showImagePickerDialogFragment();
    else
        IMPermission.requestPermissions(this, ContextFunction.getStringResource(context,
                R.string.perm_common, R.string.perm_camera_read_write),
                NumericEnums.PermissionConstant.CAMERA_GALLERY.getCode(), perms);
}

private void showImagePickerDialogFragment()
{
    ImageCapture.openCamera(this, this);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    NumericEnums.PermissionConstant permissionConstant = NumericEnums.PermissionConstant.fromRepresentation(requestCode);
    switch (permissionConstant) {
        case CAMERA_GALLERY:
            if (ContextFunction.checkAllPermisssionGranterd(permissions, grantResults))
                openGalleryAndCamera();
            break;
    }
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch(requestCode) {
        case 2001:
            if (Activity.RESULT_OK == resultCode ) {
                showImagePickerDialogFragment();
            }else
                LogUtil.e("per","permisson not granted");
            break;
        default:
            ImageCapture.handleActivityResult(requestCode,resultCode, data, this, this);
    }
    super.onActivityResult(requestCode, resultCode, data);
}

@Override
public void success(File file) {
    Glide.with(context).asBitmap().load(file).into(new BitmapImageViewTarget(imgPic) {
        @Override
        protected void setResource(Bitmap resource) {
            RoundedBitmapDrawable circularBitmapDrawable =
                    RoundedBitmapDrawableFactory.create(getResources(),
                            resource);
            circularBitmapDrawable.setCircular(true);
            imgPic.setImageDrawable(circularBitmapDrawable);
        }
    });
}

@Override
public void onImagePickerError(Exception e, NumericEnums.DeviceFeaturesCons source) {
    ContextFunction.showToast(context, R.string.field_select, R.string.image_selectin_error);
}

@Override
public void onCanceled(NumericEnums.DeviceFeaturesCons source) {
    File photoFile = ImageCapture.lastlyTakenButCanceledPhoto(context);
    if (photoFile != null)
        photoFile.delete();
}

@Override
public void onError(String msg) {
    showToast("simple error");
}

}

下面是我的IMageCapture文件如下,

package mp.Wall.utility.imageCapturePickerCompressor;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.FileProvider;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

import mp.Wall.BuildConfig;
import mp.Wall.controllers.constants.NumericEnums;
import mp.Wall.data.sPrefs.StorageManager;
import mp.Wall.utility.ContextFunction;
import mp.Wall.utility.Validation;

public class ImageCapture
{
private static final String KEY_PHOTO_URI = "thGreat004.photopicker.photo_uri";
private static final String KEY_LAST_CAMERA_PHOTO = "thGreat004.photopicker.last_photo";

private static Intent createCameraIntent(@NonNull Object object, ImageCapturePickerCallbacks imageCapturePickerCallback) {
    Context context= ContextFunction.getActivity(object);

    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    try {
        Uri capturedImageUri = createCameraPictureFile(context);
        //We have to explicitly grant the write permission since Intent.setFlag
        // works only on API Level >=20
        grantWritePermission(context, intent, capturedImageUri);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri);
    } catch (Exception e) {
        e.printStackTrace();
        imageCapturePickerCallback.onError("errorOccured");
    }

    return intent;
}

public static Uri createCameraPictureFile(@NonNull Context context) throws IOException {
    File imagePath = getCameraPicturesLocation();
    Uri uri = getUriToFile(context, imagePath);

    StorageManager storageManager = StorageManager.getInstance(context);
    storageManager.setValue(KEY_PHOTO_URI, uri.toString());
    storageManager.setValue(KEY_LAST_CAMERA_PHOTO, imagePath.toString());

    return uri;
}

private static void grantWritePermission(@NonNull Context context, Intent intent, Uri uri) {
    List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent,
            PackageManager.MATCH_DEFAULT_ONLY);
    for (ResolveInfo resolveInfo : resInfoList) {
        String packageName = resolveInfo.activityInfo.packageName;
        context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
                Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
}

private static void revokeWritePermission(@NonNull Context context, Uri uri) {
    context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
            Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

private static File getCameraPicturesLocation() throws IOException {
    File dir = imageDirectory();
    return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir);
}

private static Uri getUriToFile(@NonNull Context context, @NonNull File file) {
    String authority = BuildConfig.APPLICATION_ID + ".Wall.fileprovider";
    return FileProvider.getUriForFile(context, authority, file);
}

public static void openCamera(Fragment fragment, ImageCapturePickerCallbacks imageCapturePickerCallback) {
    Intent intent = createCameraIntent(fragment, imageCapturePickerCallback);
    // Ensure that there's a camera activity to handle the intent
    if (!Validation.isNull(intent.resolveActivity(fragment.getContext().getPackageManager()))) {
        fragment.startActivityForResult(intent, NumericEnums.DeviceFeaturesCons.CAMERA
                .getCode());
    }else
        imageCapturePickerCallback.onError("No app is installed to handle camera");
    // ContextFunction.showToast(activity, R.string.intents_no_app_common, R.string.cons_gallery);
}

public static void handleActivityResult(int requestCode, int resultCode, Intent data,
                                        Object object, ImageCapturePickerCallbacks imageCapturePickerCallback) {
    {
        if (Activity.RESULT_OK == resultCode) {
            if (NumericEnums.DeviceFeaturesCons.CAMERA.getCode() == requestCode) {
                onPictureReturnedFromCamera(object, imageCapturePickerCallback);
            }
        }
        else if (Activity.RESULT_CANCELED == resultCode){
            if (NumericEnums.DeviceFeaturesCons.CAMERA.getCode() == requestCode)
                imageCapturePickerCallback.onCanceled(NumericEnums.DeviceFeaturesCons.CAMERA);
        }
    }
}

private static void onPictureReturnedFromCamera(Object object,ImageCapturePickerCallbacks imageCapturePickerCallback) {
    Activity activity= ContextFunction.getActivity(object);

    String lastImageUri = StorageManager.getInstance(activity).getValue(KEY_PHOTO_URI, null);
    if (!Validation.isEmpty(lastImageUri))
        revokeWritePermission(activity, Uri.parse(lastImageUri));
    File photoFile = takenCameraPicture(activity);

    if (Validation.isNull(photoFile)) {
        Exception e = new IllegalStateException("Unable to get the picture " +
                "returned from camera");
        imageCapturePickerCallback.onImagePickerError(e, NumericEnums.DeviceFeaturesCons.CAMERA);
    } else
        compressImage(photoFile,imageCapturePickerCallback);

    clearPhotoStorate(activity);
}

private static void compressImage(File file,ImageCapturePickerCallbacks imageCapturePickerCallback)
{
    imageCapturePickerCallback.success(file);
}

public static void clearPhotoStorate(Context context)
{
    StorageManager.getInstance(context).removeValue(KEY_LAST_CAMERA_PHOTO);
    StorageManager.getInstance(context).removeValue(KEY_PHOTO_URI);
}

private static File imageDirectory() {
    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Wall");

    // Create the storage directory if it does not exist
    if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
            Log.d("sdf", "Oops! Failed create "
                    + "Wall" + " directory");
            return null;
        }
    }
    return mediaStorageDir;
}

@Nullable
private static File takenCameraPicture(Context context){
    String lastCameraPhoto = StorageManager.getInstance(context).getValue(KEY_LAST_CAMERA_PHOTO, null);
    if (!Validation.isEmptyorNull(lastCameraPhoto))
        return new File(lastCameraPhoto);
    else
        return null;
}

/**
 * @param context context
 * @return File containing lastly taken (using camera) photo. Returns null if
 * there was no photo taken or it doesn't exist anymore.
 */
public static File lastlyTakenButCanceledPhoto(@NonNull Context context) {
    String filePath =StorageManager.getInstance(context).getValue(KEY_LAST_CAMERA_PHOTO, null);
    if (filePath == null)
        return null;
    File file = new File(filePath);
    if (file.exists())
        return file;
    else
        return null;
}

}

这是我的清单文件如下,

<?xml version="1.0" encoding="utf-8"?>

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

<application
    android:label="Wall"
    android:theme="@style/AppTheme">

    <activity android:name=".ui.splash.SplashActivity"
        android:noHistory="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.wall.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>
</application>

最后这是我的 filepath.xml 如下,

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <external-path name="WallParkImages" path="DCIM/Wall" />
</paths>

您不能使用 File class 创建使用存储访问框架的文件。

你应该使用 DocumentFile.createFile().