经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
适配Android10 拍照,相册,裁剪,上传图片
来源:cnblogs  作者:星怀蓝梦  时间:2021/3/1 13:42:57  对本文有异议

  这篇文章主要介绍了适配Android 10(Q)后,调用系统拍照,系统相册,系统裁剪和上传问题,这是一个很常用的功能,但是在Android 6.0,Android 7.0和Android 10.0以上版本的实现都有所不同,这篇文章从Android 4适配到Android 10。

  之前写毕设的时候,在写上传头像的功能时,参考网上的方法写了一大堆,在我的手机(Android 9)上可以正常运行,当时没多想,以为高版本可以向下兼容,后来我把程序发给同学去试验,结果都告诉我上传头像用不了,一问才知道他们用的是Android 10的手机,于是只能上网查找原因,然后发现Android 10的存储方式发生了变化,Android 10的文件系统采用了沙盒文件系统,最显著的变化就是文件系统变安全了,于是app也没办法拿到外部文件的绝对路径了,网上给出的方法就是将共享文件复制到沙盒目录下,然后再进行文件操作。话不多说,上代码。

  demo源码

在文件清单AndroidManifest.xml中添加权限:

  1. 1 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 储存卡的读权限 -->
  2. 2 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 储存卡的写权限 -->
  3. 3 <uses-permission android:name="android.permission.CAMERA" /><!-- 调用相机权限 -->

在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException,使用FileProvider来共享文件,AndroidManifest.xml:

  1. <application
  2. ...
  3. <!-- 兼容Android7.0拍照闪退 -->
  4. <provider
  5. android:name="androidx.core.content.FileProvider"
  6. android:authorities="com.example.camera.test"
  7. android:exported="false"
  8. android:grantUriPermissions="true">
  9. <meta-data
  10. android:name="android.support.FILE_PROVIDER_PATHS"
  11. android:resource="@xml/file_paths" />
  12. </provider>
  13. </application>

在主界面放一个ImageView和两个按钮,activity_main.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical"
  7. tools:context=".MainActivity">
  8.  
  9. <ImageView
  10. android:id="@+id/image"
  11. android:layout_width="250dp"
  12. android:layout_height="250dp"
  13. android:layout_marginTop="20dp"
  14. android:layout_gravity="center_horizontal"/>
  15.  
  16. <TextView
  17. android:id="@+id/tv_camera"
  18. android:layout_width="match_parent"
  19. android:layout_height="wrap_content"
  20. android:layout_marginTop="50dp"
  21. android:text="相机"
  22. android:textSize="18sp"
  23. android:textColor="#FFF"
  24. android:padding="10dp"
  25. android:background="#1878FF"
  26. android:layout_marginHorizontal="20dp"
  27. android:gravity="center_horizontal"/>
  28.  
  29. <TextView
  30. android:id="@+id/tv_album"
  31. android:layout_width="match_parent"
  32. android:layout_height="wrap_content"
  33. android:layout_marginTop="50dp"
  34. android:text="相册"
  35. android:textSize="18sp"
  36. android:textColor="#FFF"
  37. android:padding="10dp"
  38. android:background="#1878FF"
  39. android:layout_marginHorizontal="20dp"
  40. android:gravity="center_horizontal"/>
  41.  
  42. </LinearLayout>

接下来是主页面的代码:

获取控件,对两个按钮添加点击监听,判断权限:


  1. private ImageView image;
    private TextView tvCamera, tvAlbum;
  1. @Override
    protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_main);
  4. init();
  5. }
  6. private void init(){
  7. image = findViewById(R.id.image);
  8. tvCamera = findViewById(R.id.tv_camera);
  9. tvAlbum = findViewById(R.id.tv_album);
  10. tvCamera.setOnClickListener(new View.OnClickListener() {
  11. @Override
  12. public void onClick(View v) {
  13. //相机
  14. if ((ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
  15. == PackageManager.PERMISSION_GRANTED)
  16. && (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
  17. == PackageManager.PERMISSION_GRANTED)
  18. && (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
  19. == PackageManager.PERMISSION_GRANTED)) {
  20. //权限都齐的情况下,跳转相机
  21. openCamera();
  22. } else {
  23. if (activity != null) {
  24. //请求权限
  25. ActivityCompat.requestPermissions(activity, new String[]{
  26. Manifest.permission.CAMERA,
  27. Manifest.permission.WRITE_EXTERNAL_STORAGE,
  28. Manifest.permission.READ_EXTERNAL_STORAGE
  29. }, PHOTO_REQUEST_CAMERA);
  30. }
  31. }
  32. }
  33. });
  34. tvAlbum.setOnClickListener(new View.OnClickListener() {
  35. @Override
  36. public void onClick(View v) {
  37. //相册
  38. if ((ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
  39. == PackageManager.PERMISSION_GRANTED)
  40. && (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
  41. == PackageManager.PERMISSION_GRANTED)) {
  42. //权限都齐的情况下,跳转相册
  43. openAlbum();
  44. } else {
  45. if (activity != null) {
  46. //请求权限
  47. ActivityCompat.requestPermissions(activity, new String[]{
  48. Manifest.permission.WRITE_EXTERNAL_STORAGE,
  49. Manifest.permission.READ_EXTERNAL_STORAGE
  50. }, PHOTO_REQUEST_ALBUM);
  51. }
  52. }
  53. }
  54. });
  55. }

权限申请回调:

  1. @Override
  2. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  3. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  4. switch (requestCode) {
  5. case PHOTO_REQUEST_CAMERA:
  6. //相机权限请求回调
  7. if (grantResults.length > 0) {
  8. if (grantResults[0] == PackageManager.PERMISSION_GRANTED
  9. && grantResults[1] == PackageManager.PERMISSION_GRANTED
  10. && grantResults[2] == PackageManager.PERMISSION_GRANTED) {
  11. //跳转相机
  12. openCamera();
  13. } else {
  14. //无权限提示
  15. Toast.makeText(context, "权限未通过", Toast.LENGTH_SHORT).show();
  16. }
  17. }
  18. break;
  19. case PHOTO_REQUEST_ALBUM:
  20. if (grantResults.length > 0) {
  21. if (grantResults[0] == PackageManager.PERMISSION_GRANTED
  22. && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
  23. //跳转相册
  24. openAlbum();
  25. } else {
  26. //无权限提示
  27. Toast.makeText(context, "权限未通过", Toast.LENGTH_SHORT).show();
  28. }
  29. }
  30. break;
  31. }
  32. }

跳转相机:

  1. private void openCamera(){
  2. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  3. //判断是否有相机
  4. if (activity != null && context != null && intent.resolveActivity(activity.getPackageManager()) != null){
  5. File file;
  6. Uri uri = null;
  7. if (isAndroidQ){
  8. //适配Android10
  9. uri = createImageUri(context);
  10. } else {
  11. //Android10以下
  12. file = createImageFile(context);
  13. if (file != null){
  14. //Android10以下
  15. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
  16. //适配Android7.0文件权限
  17. uri = FileProvider.getUriForFile(context, "com.example.camera.test", file);
  18. } else {
  19. uri = Uri.fromFile(file);
  20. }
  21. }
  22. }
  23. imageUri = uri;
  24. Log.e(TAG, "相机保存的图片Uri:" + imageUri);
  25. if (uri != null){
  26. intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
  27. intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  28. startActivityForResult(intent, CAMERA_REQUEST_CODE);
  29. }
  30. }
  31. }

Android 10以上的创建Uri,Uri创建在沙盒内:

  1. contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/0/");

以上设置的保存路径为:".../包名/files/Pictures/0",可按需更改

用于保存拍照之后的照片:

  1. private Uri createImageUri(@NonNull Context context){
  2. String status = Environment.getExternalStorageState();
  3. ContentValues contentValues = new ContentValues();
  4. contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, SAVE_AVATAR_NAME);
  5. contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
  6. contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/0/");
  7. //判断是否有SD卡
  8. if (status.equals(Environment.MEDIA_MOUNTED)){
  9. return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
  10. } else {
  11. return context.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, contentValues);
  12. }
  13. }

Android 10以下的返回一个file来保存拍照后的图片:

  1. private File createImageFile(@NonNull Context context){
  2. File file = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
  3. if (file != null && !file.exists()){
  4. if (file.mkdir()){
  5. Log.e(TAG, "文件夹创建成功");
  6. } else {
  7. Log.e(TAG, "file为空或者文件夹创建失败");
  8. }
  9. }
  10. File tempFile = new File(file, SAVE_AVATAR_NAME);
  11. Log.e(TAG, "临时文件路径:" + tempFile.getAbsolutePath());
  12. if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))){
  13. return null;
  14. }
  15. return tempFile;
  16. }

跳转相册:

  1. private void openAlbum(){
  2. Intent intent = new Intent(Intent.ACTION_PICK, null);
  3. intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
  4. startActivityForResult(intent, ALBUM_REQUEST_CODE);
  5. }

跳转裁剪,裁剪在相机拍照后跳转,用一个file来加载:

  1. private void openCrop(Uri uri){
  2. if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && context != null){
  3. file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES + "/0"), SAVE_AVATAR_NAME);
  4. }
  5. Intent intent = new Intent("com.android.camera.action.CROP");
  6. intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  7. intent.setDataAndType(uri, "image/*");
  8. // 设置裁剪
  9. intent.putExtra("crop", "true");
  10. // aspectX aspectY 是宽高的比例
  11. intent.putExtra("aspectX", 1);
  12. intent.putExtra("aspectY", 1);
  13. // 裁剪后输出图片的尺寸大小
  14. intent.putExtra("outputX", 250);
  15. intent.putExtra("outputY", 250);
  16. //适配Android10,存放图片路径
  17. intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
  18. // 图片格式
  19. intent.putExtra("outputFormat", "PNG");
  20. intent.putExtra("noFaceDetection", true);// 取消人脸识别
  21. intent.putExtra("return-data", true);// true:不返回uri,false:返回uri
  22. startActivityForResult(intent, TAILOR_REQUEST_CODE);
  23. }

跳转相机、相册和裁剪的回调,如果有上传需求的,直接上传代码中的file即可:

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  3. super.onActivityResult(requestCode, resultCode, data);
  4. if (resultCode == -1){
  5. //回调成功
  6. switch (requestCode) {
  7. case CAMERA_REQUEST_CODE:
  8. //相机回调
  9. Log.e(TAG, "相机回调");
  10. if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
  11. //照片裁剪
  12. openCrop(imageUri);
  13. } else {
  14. Toast.makeText(context, "未找到存储卡", Toast.LENGTH_SHORT).show();
  15. }
  16. break;
  17. case ALBUM_REQUEST_CODE:
  18. //相册回调
  19. Log.e(TAG, "相册回调");
  20. if (data != null && data.getData() != null) {
  21. image.setImageURI(data.getData());
  22. //如果需要上传操作的可以使用这个方法
  23. File file = FileUtils.uriToFile(data.getData(), context);
  24. //这里的file就是需要上传的图片了
  25. }
  26. break;
  27. case TAILOR_REQUEST_CODE:
  28. //图片剪裁回调
  29. Log.e(TAG, "图片剪裁回调");
  30. // Glide.with(context).load(file).into(image);
  31. Uri uri = Uri.fromFile(file);
  32. image.setImageURI(uri);
  33. //如果需要上传全局的这个file就是需要上传的图片了
  34. File file = this.file;
  35. break;
  36. }
  37. } else {
  38. Toast.makeText(context, "取消", Toast.LENGTH_SHORT).show();
  39. }
  40. }

以上,如果有改进的建议的,欢迎骚扰

QQ:1336140321

原文链接:http://www.cnblogs.com/xhlm001/p/14448371.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号