我正在尝试使用相机并保存图像。按照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;
}
答案 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>