java.lang.SecurityException:权限拒绝:编写android.support.v4.content.FileProvider uri

时间:2018-11-12 02:31:56

标签: android android-intent android-camera android-manifest

我正在尝试使用相机并保存图像。按照steps的建议跟随commonsware。不断出现错误-

2018-11-12 02:10:54.588 3145-3173/com.bisw.weac E/DatabaseUtils: Writing exception to parcel java.lang.SecurityException: Permission Denial: writing android.support.v4.content.FileProvider uri content://com.bisw.weac.provider/external_files/Android/data/com.bisw.weac/files/wallpaper/theme.jpg from pid=5566, uid=10071 requires the provider be exported, or grantUriPermission() at android.content.ContentProvider.enforceWritePermissionInner(ContentProvider.java:713) at android.content.ContentProvider$Transport.enforceWritePermission(ContentProvider.java:519) at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:491) at android.content.ContentProvider$Transport.openAssetFile(ContentProvider.java:389) at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:251) at android.os.Binder.execTransact(Binder.java:682)

我几乎尝试了所有类似-intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);的方法,但没有任何帮助。我正在从相机裁剪图像。 活动代码

public class LocalAlbumActivity extends BaseActivity implements View.OnClickListener {

    private static final int REQUEST_IMAGE_CAPTURE_THEME = 1;
    private static final int REQUEST_IMAGE_CAPTURE_QRCODE_LOGO = 4;
    private static final int REQUEST_IMAGE_CROP_THEME = 2;
    private static final int REQUEST_IMAGE_CROP_QRCODE_LOGO = 5;
    private static final int REQUEST_ALBUM_DETAIL = 3;

    public static final String ALBUM_PATH = "album_path";
    public static final String ALBUM_NAME = "album_name";
    private LocalAlbumAdapter mLocalAlbumAdapter;
    private List<ImageBucket> mLocalAlbumList;
    private AsyncTask<Void, Void, List<ImageBucket>> mBucketLoadTask;
    private ListView mLocalAlbumListView;


    /**
     * 访问本地相册类型:0,主题;1,扫码;2,造码
     */
    private int mRequestType;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        OttoAppConfig.getInstance().register(this);

        setContentView(R.layout.activity_local_album);
        ViewGroup backGround = (ViewGroup) findViewById(R.id.background);
        MyUtil.setBackgroundBlur(backGround, this);

        initAdapter();
        assignViews();

    }

    private void initAdapter() {

        mLocalAlbumList = new ArrayList<>();
        mLocalAlbumAdapter = new LocalAlbumAdapter(this, mLocalAlbumList);
        //trying permission



        mBucketLoadTask = new AsyncTask<Void, Void, List<ImageBucket>>() {

            @Override
            protected void onPreExecute() {
                super.onPreExecute();
//                showLoading();
            }

            @Override
            protected List<ImageBucket> doInBackground(Void... params) {

                return LocalAlbumImagePickerHelper.getInstance(LocalAlbumActivity.this)
                        .getImagesBucketList();
            }

            @Override
            protected void onPostExecute(List<ImageBucket> list) {
                dismissLoadingDialog();

                TextView emptyView = (TextView) findViewById(R.id.local_album_lv_empty);
                mLocalAlbumListView.setEmptyView(emptyView);

                mLocalAlbumList.addAll(list);
                mLocalAlbumAdapter.notifyDataSetChanged();
            }
        };

        mBucketLoadTask.execute();
    }

    private void dismissLoadingDialog() {
        ViewGroup progressBarLlyt = (ViewGroup) findViewById(R.id.progress_bar_llyt);
        progressBarLlyt.setVisibility(View.GONE);
    }

    private void assignViews() {
        TextView loadingMsg = (TextView) findViewById(R.id.loading_msg);
        loadingMsg.setText(R.string.scanning);

        ImageView backBtn = (ImageView) findViewById(R.id.action_back);
        TextView captureBtn = (TextView) findViewById(R.id.action_capture);

        backBtn.setOnClickListener(this);

        mRequestType = getIntent().getIntExtra(WeacConstants.REQUEST_LOCAL_ALBUM_TYPE, 0);
        switch (mRequestType) {
            // 主题
            case 0:
                // 造码
            case 2:
                captureBtn.setOnClickListener(this);
                break;
            // 扫码
            case 1:
                // 隐藏拍照按钮
                captureBtn.setVisibility(View.GONE);
                break;
        }

        mLocalAlbumListView = (ListView) findViewById(R.id.local_album_lv);
        mLocalAlbumListView.setAdapter(mLocalAlbumAdapter);
        mLocalAlbumListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (MyUtil.isFastDoubleClick()) {
                    return;
                }
                Intent intent = new Intent(LocalAlbumActivity.this, LocalAlbumDetailActivity.class);
                intent.putParcelableArrayListExtra(ALBUM_PATH,
                        mLocalAlbumAdapter.getItem(position).bucketList);
                intent.putExtra(ALBUM_NAME, mLocalAlbumAdapter.getItem(position).bucketName);
                intent.putExtra(WeacConstants.REQUEST_LOCAL_ALBUM_TYPE, mRequestType);
                startActivityForResult(intent, REQUEST_ALBUM_DETAIL);
            }
        });

//        OverScrollDecoratorHelper.setUpOverScroll(mLocalAlbumListView);
    }

    private Uri mImageUri;

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            // 返回
            case R.id.action_back:
                myFinish();
                break;
            // 拍照
            case R.id.action_capture:
                PackageManager pm = getPackageManager();
                // FEATURE_CAMERA - 后置相机
                // FEATURE_CAMERA_FRONT - 前置相机
                if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
                    // 访问相机类型
                    int requestType;
                    // 截取主题壁纸
                    if (mRequestType != 2) {
                        requestType = REQUEST_IMAGE_CAPTURE_THEME;
                    } else { // 截取二维码logo
                        requestType = REQUEST_IMAGE_CAPTURE_QRCODE_LOGO;
                    }

                    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    //mImageUri = Uri.fromFile(MyUtil.getFileDirectory(this, "/Android/data/" +
                    //        getPackageName() + "/capture/temporary.jpg"));



                    mImageUri = FileProvider.getUriForFile(this,BuildConfig.APPLICATION_ID+".provider",MyUtil.getFileDirectory(this, "/Android/data/" +
                                    getPackageName() + "/capture/temporary.jpg"));

                    this.grantUriPermission(getPackageName(),mImageUri,Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

                    intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
                    startActivityForResult(intent, requestType);
                    overridePendingTransition(0, R.anim.zoomin);
                } else { // 没有可用相机
                    Intent intent = new Intent(this, MyDialogActivitySingle.class);
                    intent.putExtra(WeacConstants.TITLE, getString(R.string.prompt));
                    intent.putExtra(WeacConstants.DETAIL, getString(R.string.camera_error));
                    startActivity(intent);
                }
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK) {
            // 截图/相机返回
            overridePendingTransition(0, R.anim.zoomout);
            return;
        }

        // 扫描二维码相册详细取消
        if (data != null) {
            boolean isFinishMe = data.getBooleanExtra(LocalAlbumDetailActivity.FINISH_ACTIVITY, false);
            if (isFinishMe && !isFinishing()) {
                myFinish2();
                return;
            }
        }

        switch (requestCode) {
            // 拍照(截取主题壁纸)
            case REQUEST_IMAGE_CAPTURE_THEME:
                cropImage(0, REQUEST_IMAGE_CROP_THEME, WeacConstants.DIY_WALLPAPER_PATH);
                break;
            // 拍照(截取二维码logo)
            case REQUEST_IMAGE_CAPTURE_QRCODE_LOGO:
                cropImage(1, REQUEST_IMAGE_CROP_QRCODE_LOGO, WeacConstants.DIY_QRCODE_LOGO_PATH);
                break;
            // 截图(截取主题壁纸)
            case REQUEST_IMAGE_CROP_THEME:
                String filePath = MyUtil.getFilePath(this, WeacConstants.DIY_WALLPAPER_PATH);
                // 更新壁纸信息
                MyUtil.saveWallpaper(this, WeacConstants.WALLPAPER_PATH, filePath);
                // 发送壁纸更新事件
                OttoAppConfig.getInstance().post(new WallpaperEvent());
                myFinish();
                break;
            // 截图(截取二维码logo)
            case REQUEST_IMAGE_CROP_QRCODE_LOGO:
                String logoPath = MyUtil.getFilePath(this, WeacConstants.DIY_QRCODE_LOGO_PATH);
                // 保存自定义二维码logo地址
                MyUtil.saveQRcodeLogoPath(this, logoPath);
                // 发送自定义二维码logo截取地址事件
                OttoAppConfig.getInstance().post(new QRcodeLogoEvent(logoPath));
                myFinish();
                break;
            // 相册详细图片
            case REQUEST_ALBUM_DETAIL:
                assert data != null;
                String url = data.getStringExtra(WeacConstants.IMAGE_URL);
                OttoAppConfig.getInstance().post(new ScanCodeEvent(url));
                myFinish2();
                break;
        }
    }

    private void cropImage(int type, int requestType, String path) {

        ToastUtil.showLongToast(this, "path:"+path);
        Intent intent = MyUtil.getCropImageOptions(this, mImageUri, path, type);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, requestType);
            overridePendingTransition(0, 0);
        } else {
            // 不可以复制其他应用的内部文件
            // TODO: 全屏裁剪&自定义裁剪功能
            ToastUtil.showLongToast(this, getString(R.string.no_crop_action));
        }
    }

    @Subscribe
    public void finishMeEvent(FinishLocalAlbumActivityEvent event) {
        myFinish2();
    }

    @Override
    public void onBackPressed() {
        myFinish();
    }

    private void myFinish() {
        finish();
        if (mRequestType != 2) {
            overridePendingTransition(0, R.anim.zoomout);
        } else {
            overridePendingTransition(0, R.anim.move_out_bottom);
        }
    }

    private void myFinish2() {
        finish();
        overridePendingTransition(0, 0);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        OttoAppConfig.getInstance().unregister(this);
        if (null != mBucketLoadTask && mBucketLoadTask.getStatus() == AsyncTask.Status.RUNNING) {
            mBucketLoadTask.cancel(true);
        }
    }
}

辅助方法

/**
 * Returns specified directory(/mnt/sdcard/...).
 * directory will be created on SD card by defined path if card
 * is mounted. Else - Android defines files directory on device's
 * files(/data/data/<application package>/files) system.
 *
 * @param context context
 * @param path    file path (e.g.: "/AppDir/a.mp3", "/AppDir/files/images/a.jp")
 * @return File {@link File directory}
 */
public static File getFileDirectory(Context context, String path) {
    File file = null;
    if (isHasSDCard()) {
        file = new File(Environment.getExternalStorageDirectory(), path);
        if (!file.getParentFile().exists()) {
            if (!file.getParentFile().mkdirs()) {
                file = null;
            }
        }
    }
    if (file == null) {
        // 使用内部缓存[MediaStore.EXTRA_OUTPUT ("output")]是无法正确写入裁切后的图片的。
        // 系统是用全局的ContentResolver来做这个过程的文件io操作,app内部的存储被忽略。(猜测)
        file = new File(context.getFilesDir(), path);
    }
    return file;
}

/**
 * Returns specified directory(/mnt/sdcard/Android/data/<application package>/files/...).
 * directory will be created on SD card by defined path if card
 * is mounted. Else - Android defines files directory on device's
 * files(/data/data/<application package>/files) system.
 *
 * @param context context
 * @param path    file  path (e.g.: "/music/a.mp3", "/pictures/a.jpg")
 * @return File {@link File directory}
 */
public static File getExternalFileDirectory(Context context, String path) {
    File file = null;
    if (isHasSDCard()) {
        file = new File(context.getExternalFilesDir(null), path);
        if (!file.getParentFile().exists()) {
            if (!file.getParentFile().mkdirs()) {
                file = null;
            }
        }
    }
    if (file == null) {
        // 使用内部缓存[MediaStore.EXTRA_OUTPUT ("output")]是无法正确写入裁切后的图片的。
        // 系统是用全局的ContentResolver来做这个过程的文件io操作,app内部的存储被忽略。(猜测)
        file = new File(context.getFilesDir(), path);
    }
    return file;
}

public static boolean isHasSDCard() {
    return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}

/**
 * Returns directory absolutePath.
 *
 * @param context context
 * @param path    file path (e.g.: "/AppDir/a.mp3", "/AppDir/files/images/a.jpg")
 * @return /mnt/sdcard/Android/data/<application package>/files/....
 */
public static String getFilePath(Context context, String path) {
    return getExternalFileDirectory(context, path).getAbsolutePath();
}

/**
 * set intent options
 *
 * @param context  context
 * @param uri      image path uri
 * @param filePath save path (e.g.: "/AppDir/a.mp3", "/AppDir/files/images/a.jpg")
 * @param type     0,截取壁纸/拍照;1,截取Logo
 * @return Intent
 */
public static Intent getCropImageOptions(Context context, Uri uri, String filePath, int type) {
    int width;
    int height;
    // 截取壁纸/拍照
    if (type == 0) {
        width = context.getResources().getDisplayMetrics().widthPixels;
        height = context.getResources().getDisplayMetrics().heightPixels;
    } else { // 截取logo
        width = height = dip2px(context, 30);
    }
    //filePath="/Internal storage/Download/a.jpg";
    LogUtil.e(LOG_TAG, " filePath:" + filePath );
    Intent intent = new Intent();
    intent.setAction("com.android.camera.action.CROP");
    intent.setDataAndType(uri, "image/*");
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);


    intent.putExtra("crop", "true");
    // 裁剪框比例
    intent.putExtra("aspectX", width);
    intent.putExtra("aspectY", height);
    // 保存路径


    //#ToDO: Uri.fromFile change to FileProvider.getUriForFile
    Uri mImageUri;
    //mImageUri=Uri.fromFile(getExternalFileDirectory(context, filePath));

    intent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(context,BuildConfig.APPLICATION_ID+".provider",getExternalFileDirectory(context, filePath)));

    // 是否去除面部检测
    intent.putExtra("noFaceDetection", true);

    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    // 是否保留比例
    intent.putExtra("scale", true);
    intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
    // 裁剪区的宽高
    intent.putExtra("outputX", width);
    intent.putExtra("outputY", height);

    // 是否将数据保留在Bitmap中返回
    intent.putExtra("return-data", false);
    return intent;
}

1 个答案:

答案 0 :(得分:0)

您需要在清单文件的提供程序声明中添加以下内容

 <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="<application-id>.fileprovider" // com.abc.def 
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths" /> 
    </provider>