Android 兼容Android 7拍摄照片/打开相册/选择照片/剪裁照片/显示照片 带demo
- 前言
- 效果
- 上代码
- 共享文件夹
- 配置
- 声明权限
- 创建文件管理工具
- 布局文件
- 活动
- 服务类
- GitHub
- 完事
前言
项目里需要给用户修改头像的功能。就需要能做到标题的功能。
先拟定需要完成的任务
/**
* 首页
* -------------------------
* 1、点击拍照,先判断是否有相机权限和文件写入读取权限,没有就请求,有就打开相机
* 2、点击选择照片,先判断是否有文件读取权限,没有就请求,有就打开图册
* 3、拿到照片返回进行剪裁
* 4、剪裁成功后显示
* @author D10NG
* @date on 2019-05-15 09:02
*/
先上参考文章表示感谢
@那一夜_ ------ 适配Android7.0应用间文件共享FileProvider@那一夜_ ------ android从相册选择图片和拍照选择图片
效果
GIF图
上代码
共享文件夹
首先在项目res目录下新建xml目录,并新建file_paths.xml,这个文件主要用来配置应用共享文件的路径
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path
name="root"
path="" />
<files-path
name="files"
path="." />
<cache-path
name="cache"
path="." />
<external-path
name="external"
path="." />
<external-files-path
name="external_file_path"
path="." />
<external-cache-path
name="external_cache_path"
path="." />
</paths>
其中配置的path="."
的意思就是应用可以使用整个目录
配置
在AndroidManifest.xml的application节点下增加FileProvider的声明
<application
...
<!-- 适配android 7.0文件访问 org.rydc.phototest是应用的包名 -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="org.rydc.phototest.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
声明权限
<!-- 相册读取 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 拍照 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
创建文件管理工具
直接复制下面的即可
package org.rydc.phototest.utils;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* 适配Android 7 读取文件工具
*
* @author D10NG
* @date on 2019-05-15 08:45
*/
public class FileProviderUtils {
/**
* 获取文件的Uri
* @param context
* @param file
* @return
*/
public static Uri getUriForFile(Context context, File file) {
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
fileUri = getUriForFile24(context, file);
} else {
fileUri = Uri.fromFile(file);
}
return fileUri;
}
/**
* Android 7 获取文件的Uri
* @param context
* @param file
* @return
*/
private static Uri getUriForFile24(Context context, File file) {
return android.support.v4.content.FileProvider.getUriForFile(context,
context.getPackageName() + ".fileprovider", file);
}
/**
* 设定intent的data和type
* @param context
* @param intent
* @param type
* @param file
* @param writeAble
*/
public static void setIntentDataAndType(Context context,
Intent intent,
String type,
File file,
boolean writeAble) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setDataAndType(getUriForFile(context, file), type);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setDataAndType(Uri.fromFile(file), type);
// apk放在cache文件中,需要获取读写权限
chmod("777", file.getAbsolutePath());
}
}
/**
* 修改文件权限
* @param permission
* @param path
*/
public static void chmod(String permission, String path) {
try {
String command = "chmod " + permission + " " + path;
Runtime runtime = Runtime.getRuntime();
runtime.exec(command);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 设定intent的data
* @param context
* @param intent
* @param file
* @param writeAble
*/
public static void setIntentData(Context context,
Intent intent,
File file,
boolean writeAble) {
if (Build.VERSION.SDK_INT >= 24) {
intent.setData(getUriForFile(context, file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setData(Uri.fromFile(file));
}
}
/**
* 授予权限
* @param context
* @param intent
* @param uri
* @param writeAble
*/
public static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) {
int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (writeAble) {
flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
}
intent.addFlags(flag);
List<ResolveInfo> resInfoList = context.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, flag);
}
}
/**
* 根据URI获取文件真实路径(兼容多机型)
* @param context
* @param uri
* @return
*/
public static String getFilePathByUri(Context context, Uri uri) {
// 判断uri的标头是 content 还是 file,分别用不同的方法处理
if ("content".equalsIgnoreCase(uri.getScheme())) {
int sdkVersion = Build.VERSION.SDK_INT;
if (sdkVersion >= 19) {
// api >= 19
return getRealPathFromUriAboveApi19(context, uri);
} else {
// api < 19
return getRealPathFromUriBelowAPI19(context, uri);
}
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* 适配api19及以上,根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint("NewApi")
private static String getRealPathFromUriAboveApi19(Context context, Uri uri) {
String filePath = null;
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的 uri, 则通过document id来进行处理
String documentId = DocumentsContract.getDocumentId(uri);
if (isMediaDocument(uri)) { // MediaProvider
// 使用':'分割
String type = documentId.split(":")[0];
String id = documentId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = {id};
// 判断文件类型
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
filePath = getDataColumn(context, contentUri, selection, selectionArgs);
} else if (isDownloadsDocument(uri)) {
// DownloadsProvider
Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
Long.valueOf(documentId));
filePath = getDataColumn(context, contentUri, null, null);
}else if (isExternalStorageDocument(uri)) {
// ExternalStorageProvider
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
filePath = Environment.getExternalStorageDirectory() + "/" + split[1];
}
}else {
Log.e("FileHandlerUtil", "路径错误");
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是 content 类型的 Uri
filePath = getDataColumn(context, uri, null, null);
} else if ("file".equals(uri.getScheme())) {
// 如果是 file 类型的 Uri,直接获取图片对应的路径
filePath = uri.getPath();
}
return filePath;
}
/**
* 适配api19以下(不包括api19),根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) {
return getDataColumn(context, uri, null, null);
}
/**
* 获取数据库表中的 _data 列,即返回Uri对应的文件路径
*
* @return
*/
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
String path = null;
String[] projection = new String[]{MediaStore.Images.Media.DATA};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
path = cursor.getString(columnIndex);
}
} catch (Exception e) {
if (cursor != null) {
cursor.close();
}
}
return path;
}
/**
* @param uri the Uri to check
* @return Whether the Uri authority is MediaProvider
*/
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri the Uri to check
* @return Whether the Uri authority is DownloadsProvider
*/
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
}
布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_take_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="拍照" />
<Button
android:id="@+id/btn_choose_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="从相册获取" />
<ImageView
android:id="@+id/img_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_background" />
</LinearLayout>
活动
MainActivity.java
package org.rydc.phototest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
import org.rydc.phototest.utils.FileProviderUtils;
/**
* 首页
* -------------------------
* 1、点击拍照,先判断是否有相机权限和文件写入读取权限,没有就请求,有就打开相机
* 2、点击选择照片,先判断是否有文件读取权限,没有就请求,有就打开图册
* 3、拿到照片返回进行剪裁
* 4、剪裁成功后显示
* @author D10NG
* @date on 2019-05-15 09:02
*/
public class MainActivity extends AppCompatActivity {
private Context mContext = this;
private MainService mService;
public static final int RC_CHOOSE_PHOTO = 10;
public static final int RC_TAKE_PHOTO = 11;
public static final int RC_CROP_PHOTO = 12;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mService = new MainService(mContext);
setContentView(mService.mView.getView());
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
Toast.makeText(mContext, "操作取消", Toast.LENGTH_SHORT).show();
return;
}
switch (requestCode) {
case RC_CHOOSE_PHOTO:
if (null == data) {
Toast.makeText(mContext, "没有拿到图片", Toast.LENGTH_SHORT).show();
return;
}
Uri uri = data.getData();
if (null == uri) {
Toast.makeText(mContext, "没有拿到图片路径", Toast.LENGTH_SHORT).show();
return;
}
// 剪裁图片
mService.cropPhoto(FileProviderUtils.getFilePathByUri(mContext, uri), 200);
break;
case RC_TAKE_PHOTO:
// 剪裁图片
mService.cropPhoto(mService.tempPhotoPath, 200);
break;
case RC_CROP_PHOTO:
// 显示图片
mService.showPhoto(mService.cropImgUri);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean allPass = true;
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
allPass = false;
}
}
if (!allPass) {
Toast.makeText(mContext, "没有获得相应权限", Toast.LENGTH_SHORT).show();
return;
}
switch (requestCode) {
case RC_CHOOSE_PHOTO:
// 继续去打开图册
mService.choosePhoto();
break;
case RC_TAKE_PHOTO:
// 继续去拍照
mService.takePhoto();
break;
}
}
}
服务类
MainService.java
package org.rydc.phototest;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import org.rydc.phototest.utils.FileProviderUtils;
import java.io.File;
/**
* @author D10NG
* @date on 2019-05-15 09:15
*/
public class MainService {
private Context mContext;
public MainView mView;
/** 拍照输出真实路径 */
public String tempPhotoPath;
/** 剪裁输出uri路径 */
public final Uri cropImgUri = Uri.parse("file:///"+Environment.getExternalStorageDirectory()+"/photo_crop.jpg");
public static final int CLICK_VIEW = 1;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case CLICK_VIEW:
// 页面控件点击事件
switch (msg.arg1) {
case R.id.btn_take_photo:
takePhoto();
break;
case R.id.btn_choose_photo:
choosePhoto();
break;
}
break;
}
}
};
public MainService(Context context) {
mContext = context;
mView = new MainView(mContext, mHandler);
}
/**
* 打开相机
*/
public void takePhoto() {
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// 未授权,申请授权
ActivityCompat.requestPermissions((Activity)mContext,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA},
MainActivity.RC_TAKE_PHOTO);
return;
}
// 已授权
Intent intentToTakePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 设置照片输出位置
File photoFile = new File(Environment.getExternalStorageDirectory(), "photo.jpg");
tempPhotoPath = photoFile.getAbsolutePath();
Uri tempImgUri = FileProviderUtils.getUriForFile(mContext, photoFile);
intentToTakePhoto.putExtra(MediaStore.EXTRA_OUTPUT, tempImgUri);
((Activity)mContext).startActivityForResult(intentToTakePhoto, MainActivity.RC_TAKE_PHOTO);
}
/**
* 选图
*/
public void choosePhoto() {
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 未授权,申请授权(从相册选择图片需要读取存储卡的权限)
ActivityCompat.requestPermissions((Activity)mContext,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MainActivity.RC_CHOOSE_PHOTO);
return;
}
// 已授权,获取照片
Intent intentToPickPic = new Intent(Intent.ACTION_PICK, null);
intentToPickPic.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
((Activity)mContext).startActivityForResult(intentToPickPic, MainActivity.RC_CHOOSE_PHOTO);
}
/**
* 剪裁图片
*
* @param path
* @param size
*/
public void cropPhoto(String path, int size) {
Intent intent = new Intent("com.android.camera.action.CROP");
FileProviderUtils.setIntentDataAndType(mContext, intent, "image/*", new File(path), true);
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", size);
intent.putExtra("outputY", size);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImgUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
((Activity)mContext).startActivityForResult(intent, MainActivity.RC_CROP_PHOTO);
}
/**
* 显示图片
*/
public void showPhoto(Uri uri) {
String path = FileProviderUtils.getFilePathByUri(mContext, uri);
Log.e("main", "path=" + path);
if (!TextUtils.isEmpty(path)) {
// 从文件路径读取文件
Bitmap bitmap = BitmapFactory.decodeFile(path);
mView.setImgPhoto(bitmap);
} else {
Log.e("main", "没有图片");
}
}
}
GitHub
demo:D10NGYANG/photoTest 欢迎提供建议或意见
完事