一、拍照
1.首先看build.gradle里的配置
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.donkingliang.photograph"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
目标版本为29,即Android10;
2.先看一段关键代码:
private void openCamera() {
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 判断是否有相机
if (captureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
Uri photoUri = null;
if (isAndroidQ) {
// 适配android 10
photoUri = createImageUri();
} else {
try {
photoFile = createImageFile();
} catch (IOException e) {
e.printStackTrace();
}
if (photoFile != null) {
mCameraImagePath = photoFile.getAbsolutePath();
System.out.println("path = "+mCameraImagePath);
if (/*Build.VERSION.SDK_INT >= Build.VERSION_CODES.N*/true) {//7.0到9.0
//适配Android 7.0文件权限,通过FileProvider创建一个content类型的Uri 如:content://
photoUri = FileProvider.getUriForFile(MainActivity.this,
"com.donkingliang.photograph.fileprovider", photoFile);
} else {//7.0以下, 如:file://
photoUri = Uri.fromFile(photoFile);
}
}
}
System.out.println("photoUri = "+photoUri);
mCameraUri = photoUri;
if (photoUri != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
List<ResolveInfo> resInfoList = getPackageManager()
.queryIntentActivities(captureIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, photoUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(captureIntent, CAMERA_REQUEST_CODE);
}
}
}
/**
* 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法
* @return 图片的uri
*/
private Uri createImageUri() {
//设置保存参数到ContentValues中
ContentValues contentValues = new ContentValues();
//设置文件名
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, System.currentTimeMillis()+"");
//兼容Android Q和以下版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//android Q中不再使用DATA字段,而用RELATIVE_PATH代替
//TODO RELATIVE_PATH是相对路径不是绝对路径;照片存储的地方为:内部存储/Pictures/preventpro
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/preventpro");
}
//设置文件类型
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG");
//执行insert操作,向系统文件夹中添加文件
//EXTERNAL_CONTENT_URI代表外部存储器,该值不变
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
return uri;
}
/**
* 创建保存图片的文件
* @return
* @throws IOException
*/
private File createImageFile() throws IOException {
String imageName = new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date()) +".jpg";
// File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
// File storageDir = Environment.getExternalStoragePublicDirectory(
// Environment.DIRECTORY_PICTURES);
File storageDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+File.separator+"Pictures"+File.separator+"abc");
if (!storageDir.exists()) storageDir.mkdirs();
File tempFile = new File(storageDir, imageName);
if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
return null;
}
return tempFile;
}
通过隐士的方式调用系统相机;关键在于指定好图片的uri,然后通过intent的captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(captureIntent, CAMERA_REQUEST_CODE);来跳转到系统相机界面;
3.Android6.0及以下怎么获取uri: 格式为 file://
photoUri = Uri.fromFile(photoFile);
只需这句话即可;
4.Android7.0到Android9.0
photoUri = FileProvider.getUriForFile(MainActivity.this,
"com.donkingliang.photograph.fileprovider", photoFile);
第一个参数是上下文;第三个参数是图片的file对象;第二个参数是签名,要和AndroidManifest.xml文件中的写法一致:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.donkingliang.photograph.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
android:authorities就是签名,一般就是包名+provider,根据自己需要来写吧,没啥要求;可以看到下面的android:resource="@xml/file_paths"是个资源文件,它在res目录下,自己建一个xml文件夹,新建一个file_paths.xml文件;
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!-- 该方式提供在应用的内部存储区的文件/子目录的文件。它对应Context.getFilesDir返回的路径:-->
<!-- eg:"/data/data/com.jph.simple/files"。-->
<!-- <files-path path="" name="camera_photos" />-->
<!-- 该方式提供在应用的内部存储区的缓存子目录的文件。它对应getCacheDir返回的路径:-->
<!-- eg:“/data/data/com.jph.simple/cache”;-->
<!-- <cache-path name="name" path="path" />-->
<!-- 该方式提供在外部存储区域根目录下的文件。它对应Environment.getExternalStorageDirectory返回的路径:-->
<!-- eg:"/storage/emulated/0";-->
<external-path name="my_images" path="Pictures" />
<!-- 这个是保存拍照图片的路径,必须配置。 -->
<!-- 该方式提供在应用的外部存储区根目录的下的文件。它对应Context#getExternalFilesDir(String) -->
<!-- Context.getExternalFilesDir(null)返回的路径:-->
<!-- eg:"/storage/emulated/0/Android/data/com.jph.simple/files"。-->
<external-files-path name="images" path="Pictures" />
<!-- 该方式提供在应用的外部缓存区根目录的文件。它对应Context.getExternalCacheDir()返回的路径。-->
<!-- eg:"/storage/emulated/0/Android/data/com.jph.simple/cache"。-->
<!-- <external-cache-path name="name" path="path" />-->
<!-- 以上便是Android官方文档上介绍的FileProvider所有支持的所以path类型,这些类型在Android手机内部存储区-->
<!-- 文件共享是可以行的通的,但对于外置SD卡是不行的,如果你想通过FileProvider.getUriForFile()获取一个-->
<!-- 外置SD卡的Uri则会报出如下异常:-->
<!-- Caused by- java.lang.IllegalArgumentException- Failed to find configured root that contains-->
<root-path path="." name="root_path" />
</paths>
这里面有不同的标签,可以根据自己需要去设置;具体就读一下注释吧,很好理解。
利用FileProvider来获取uri的方法在6.0上同样适用,所以上面的openCamera方法中我把判断条件注释了直接写为true;开发中直接写即可。
5.Android10获取uri
使用方法createImageUri来做的,androidQ中不再使用DATA字段,而用RELATIVE_PATH代替;ContentResolver().insert来得到uri;具体看方法中的注释。里面可以自定义图片名字,存放的地址,图片类型等等;这里存到了 根目录/Pictures/preventpro 下。
6.拍照完成后的回调
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
System.out.println("==================resultCode = "+resultCode);
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
ivPhoto.setImageURI(mCameraUri);
} else {
Toast.makeText(this,"取消",Toast.LENGTH_LONG).show();
}
}
}
直接通过拍照时获取的uri来填充控件显示图片。同时拍的照片也在系统的相册和文件管理中都可看到。
7.下面是完整的代码:
public class MainActivity extends AppCompatActivity {
private ImageView ivCamera;
private ImageView ivPhoto;
// 拍照的requestCode
private static final int CAMERA_REQUEST_CODE = 0x00000010;
// 申请相机权限的requestCode
private static final int PERMISSION_CAMERA_REQUEST_CODE = 0x00000012;
/**
* 用于保存拍照图片的uri
*/
private Uri mCameraUri;
/**
* 用于保存图片的文件路径,Android 10以下使用图片路径访问图片
*/
private String mCameraImagePath;
/**
* 是否是Android 10以上手机
*/
private boolean isAndroidQ = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ivCamera = findViewById(R.id.ivCamera);
ivPhoto = findViewById(R.id.ivPhoto);
ivCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
checkPermissionAndCamera();
}
});
}
/**
* 检查权限并拍照。
* 调用相机前先检查权限。
*/
private void checkPermissionAndCamera() {
int hasCameraPermission = ContextCompat.checkSelfPermission(getApplication(),
Manifest.permission.CAMERA);
if (hasCameraPermission == PackageManager.PERMISSION_GRANTED) {
//有权限,调起相机拍照。
openCamera();
} else {
//没有权限,申请权限。
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA},
PERMISSION_CAMERA_REQUEST_CODE);
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_CAMERA_REQUEST_CODE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
System.out.println("==================resultCode = "+resultCode);
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
ivPhoto.setImageURI(mCameraUri);
} else {
Toast.makeText(this,"取消",Toast.LENGTH_LONG).show();
}
}
}
/**
* 处理权限申请的回调。
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSION_CAMERA_REQUEST_CODE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//允许权限,有调起相机拍照。
openCamera();
} else {
//拒绝权限,弹出提示框。
Toast.makeText(this,"拍照权限被拒绝",Toast.LENGTH_LONG).show();
}
}
}
/**
* 调起相机拍照
*/
private void openCamera() {
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 判断是否有相机
if (captureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
Uri photoUri = null;
if (isAndroidQ) {
// 适配android 10
photoUri = createImageUri();
} else {
try {
photoFile = createImageFile();
} catch (IOException e) {
e.printStackTrace();
}
if (photoFile != null) {
mCameraImagePath = photoFile.getAbsolutePath();
System.out.println("path = "+mCameraImagePath);
if (/*Build.VERSION.SDK_INT >= Build.VERSION_CODES.N*/true) {//7.0到9.0
//适配Android 7.0文件权限,通过FileProvider创建一个content类型的Uri 如:content://
photoUri = FileProvider.getUriForFile(MainActivity.this,
"com.donkingliang.photograph.fileprovider", photoFile);
} else {//7.0以下, 如:file://
photoUri = Uri.fromFile(photoFile);
}
}
}
System.out.println("photoUri = "+photoUri);
mCameraUri = photoUri;
if (photoUri != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
List<ResolveInfo> resInfoList = getPackageManager()
.queryIntentActivities(captureIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, photoUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(captureIntent, CAMERA_REQUEST_CODE);
}
}
}
/**
* 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法
* @return 图片的uri
*/
private Uri createImageUri() {
//设置保存参数到ContentValues中
ContentValues contentValues = new ContentValues();
//设置文件名
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, System.currentTimeMillis()+"");
//兼容Android Q和以下版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//android Q中不再使用DATA字段,而用RELATIVE_PATH代替
//TODO RELATIVE_PATH是相对路径不是绝对路径;照片存储的地方为:内部存储/Pictures/preventpro
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/preventpro");
}
//设置文件类型
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG");
//执行insert操作,向系统文件夹中添加文件
//EXTERNAL_CONTENT_URI代表外部存储器,该值不变
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
return uri;
}
/**
* 创建保存图片的文件
* @return
* @throws IOException
*/
private File createImageFile() throws IOException {
String imageName = new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date()) +".jpg";
// File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
// File storageDir = Environment.getExternalStoragePublicDirectory(
// Environment.DIRECTORY_PICTURES);
File storageDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+File.separator+"Pictures"+File.separator+"abc");
if (!storageDir.exists()) storageDir.mkdirs();
File tempFile = new File(storageDir, imageName);
if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
return null;
}
return tempFile;
}
}
二、显示图库照片
1.因为Android10中废除了通过文件路径的读取图片,所以只能使用uri来读取图片;
先看一个方法:
//在公共文件夹下查询图片
//这里的filepath在androidQ中表示相对路径
//在androidQ以下是绝对路径
public static Map<String, List<String>> querySignImage(String filePath, Context context, int typeShow) {
// 图片列表
Map<String, List<String>> dayMap = new HashMap<>();
try {
//兼容androidQ和以下版本
String queryPathKey = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q ?
MediaStore.Images.Media.RELATIVE_PATH : MediaStore.Images.Media.DATA;
//查询的条件语句
String selection = queryPathKey + "=? ";
//查询的sql
//Uri:指向外部存储Uri
//projection:查询那些结果
//selection:查询的where条件
//sortOrder:排序
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media._ID,
queryPathKey,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.DISPLAY_NAME},
selection,
new String[]{filePath},
null);
//是否查询到了
if (cursor != null && cursor.moveToFirst()) {
//循环取出所有查询到的数据
do {
//一张图片的基本信息
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID));//uri的id,用于获取图片
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH));//图片的相对路径
String type = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE));//图片类型
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));//图片名字
System.out.println("id="+id+", path="+path+", type="+type+", name="+name);
//根据图片id获取uri,这里的操作是拼接uri
Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id);
long value = Long.valueOf(name.replace(".jpg", ""));
String dayPic = null;
switch (typeShow) {
case DAY:
dayPic = getDayPic(value);
break;
case WEEK:
dayPic = getWeekPic(value);
break;
case MONTH:
dayPic = getMonthPic(value);
break;
default:
break;
}
if (dayMap.containsKey(dayPic)) {
Objects.requireNonNull(dayMap.get(dayPic)).add(uri.toString());
}else {
List<String> imagePathList = new ArrayList<>();
imagePathList.add(uri.toString());
dayMap.put(dayPic, imagePathList);
}
//官方代码:
// Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
// if (uri != null) {
// //通过流转化成bitmap对象
// InputStream inputStream = context.getContentResolver().openInputStream(uri);
// Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// }
} while (cursor.moveToNext());
}
if (cursor != null)
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
return dayMap;
}
这是在公共文件夹下查找图片的方法;使用ContentResolver来查询图片;条件里面适配Android10和以下版本;查到的是传入的路径下的图片,Android10传入的相对路径,就是上面拍照时传进去的路径;其它版本传的是绝对路径;查到的结果如:
id=377383, path=Pictures/preventpro/, type=image/jpeg, name=1577084770593.jpg
我们知道了id,path等信息,就可以获取uri了:
//根据图片id获取uri,这里的操作是拼接uri
Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id);
有了uri就可展示图片了。
2.其它版本还是可以使用绝对路径来获取图片并展示。这里不作展示了。
3.删除图片
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
context.getContentResolver().delete(Uri.parse(path), null, null);
}else {
File file = new File(path);
if(file.exists()){
deleteImage(context, path);
}
}
public static boolean deleteImage(Context context, String imgPath) {
ContentResolver resolver = context.getContentResolver();
Cursor cursor = MediaStore.Images.Media.query(resolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=?",
new String[] { imgPath }, null);
boolean result = false;
if (null != cursor && cursor.moveToFirst()) {
long id = cursor.getLong(0);
Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri uri = ContentUris.withAppendedId(contentUri, id);
int count = context.getContentResolver().delete(uri, null, null);
result = count == 1;
} else {
File file = new File(imgPath);
result = file.delete();
}
return result;
}
AndroidQ时,传过来的都是uri,如:content://media/external/images/media/377490 所以直接使用ContentResolver().delete;
而Android9及以下版本,我用的是path路径,所以就需要先通过ContentResolver,路径,Cursor查询到id,然后构建uri,再去删除了。ContentUris.withAppendedId(contentUri, id)是Android自带的拼接uri方法和Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id);效果一样。