Android 兼容Android 7拍摄照片/打开相册/选择照片/剪裁照片/显示照片 带demo

  • 前言
  • 效果
  • 上代码
  • 共享文件夹
  • 配置
  • 声明权限
  • 创建文件管理工具
  • 布局文件
  • 活动
  • 服务类
  • GitHub
  • 完事


前言

项目里需要给用户修改头像的功能。就需要能做到标题的功能。
先拟定需要完成的任务

/**
 * 首页
 * -------------------------
 * 1、点击拍照,先判断是否有相机权限和文件写入读取权限,没有就请求,有就打开相机
 * 2、点击选择照片,先判断是否有文件读取权限,没有就请求,有就打开图册
 * 3、拿到照片返回进行剪裁
 * 4、剪裁成功后显示
 * @author D10NG
 * @date on 2019-05-15 09:02
 */

先上参考文章表示感谢
@那一夜_ ------ 适配Android7.0应用间文件共享FileProvider@那一夜_ ------ android从相册选择图片和拍照选择图片

效果

GIF图

android存储照片到相册_android

上代码

共享文件夹

首先在项目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 欢迎提供建议或意见

完事