Android中拍照(相册中选择)并上传图片功能(包括动态获取权限)

作为新手小白,为了实现这个拍照和相册选取图片并上传功能,确实花费了很多时间,因为实现不容易,所以记录下来,一和大家分享,二为之后学习做个备忘。

一.实现效果

Android中拍照(相册中选择)并上传图片功能(包括动态获取权限)_第1张图片

二. 整体思路

  1. Android手机客户端,拍照(或从相册中选择图片),然后上传到服务器。
  2. 服务器端接收到手机端上传上来的图片并处理后返回
  3. 把从服务器获取到的图片展示在页面上

三. 实现步骤

  1. 第一步现在清单文件中把需要的权限写上
 <!--  相机-->
   <uses-permission android:name="android.permission.CAMERA" />
    <!-- 存储权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  1. 创建popupWindow弹窗,给出图片选择方式
  2. 通过点击拍摄或相册按钮进行图片拍摄和选择上传,在此处的话,会来个权限的检查和申请
    (相机拍照要有拍照权限和读内存卡权限,相册选择只需读内存卡权限)
  3. 对拍照、相册选择图片的返回结果进行处理 (这里重点是数据类型转换的问题)
    就不多说,先上代码
public class MyEvaluation<OkHttpClient, FormBody> extends AppCompatActivity implements View.OnClickListener {
   /***
   **控件定义省略。。
   **/
   
    private MyEvalImageAdapter myEvalImageAdapter;
//    private List<String> imageList=null;
    private List<Map<String,Object>> imageList = null;
    private List<ImageViewInfo> mImgList=null;

    private final int TAKE_PHOTO_PERMISSION_REQUEST_CODE = 0;  //拍照的权限处理返回码
    private final int WRITE_SDCARD_PERMISSION_REQUEST_CODE = 1; // 读储存卡内容的权限处理返回码
    private final int REQUEST_CODE_FROM_PHOTO = 2; //相册选取返回的requestCode
    private final int REQUEST_CODE_FROM_CAMERA = 1;//拍照返回的requestCode
    private String imgString = ""; //要上传的图片路径
    private String mFilePath="";  拍照得到的原图保存的图片路径

    String[] permissions = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    AlertDialog alertDialog;
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
    }
    /**
     * todo 初始化控件 initView
     */
    private void initView() {
    /**省略**/
    }


  //使用相机拍摄功能的权限检查并设置
    private void checkPermission() {
        List<String> permissionList = new ArrayList<>();
        for (int i = 0; i < permissions.length; i++) {
            if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(permissions[i]);
            }
        }
        if (permissionList.size() <= 0) {
            //说明权限都已经通过,可以做你想做的事情去(调起相机拍摄)
            openCamera();
        } else {
            //对存在的未允许的权限进行申请
            ActivityCompat.requestPermissions(this, permissions, TAKE_PHOTO_PERMISSION_REQUEST_CODE);
        }
    }


    /**
     * todo 对用户权限授予结果处理
     * @param requestCode 权限要求码,即我们申请权限时传入的常量 如: TAKE_PHOTO_PERMISSION_REQUEST_CODE
     * @param permissions  保存权限名称的 String 数组,可以同时申请一个以上的权限
     * @param grantResults 每一个申请的权限的用户处理结果数组(是否授权)
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case TAKE_PHOTO_PERMISSION_REQUEST_CODE:
                boolean haspermission = false;
                for(int i=0;i<grantResults.length;i++){
                    if (grantResults[i] == -1){
                        haspermission = true;
                    }
                }
                if(haspermission){
                    //跳转到系统设置权限页面,或者直接关闭页面,不让他继续访问
                    permissionDialog();
                }else{
                 //全部权限通过,可以进行下一步操作(调起相机拍摄)
                 openCamera();
                }
                break;
            case WRITE_SDCARD_PERMISSION_REQUEST_CODE:
                if(grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                }else{
                    ToolUtils.midToast(this,"读内存卡内容权限被拒绝",1000);
                }
                break;
        }
    }

    //手动打开设置应用权限
    private void permissionDialog() {
        if (alertDialog == null) {
            alertDialog = new AlertDialog.Builder(this)
                    .setTitle("提示信息")
                    .setMessage("当前应用缺少必要权限,该拍摄功能暂时无法使用。如若需要,请单击【设置】按钮前往设置中心进行权限授权。")
                    .setPositiveButton("设置", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            cancelPermissionDialog();
                            Uri packageURI = Uri.parse("package:" + getPackageName());
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                            startActivity(intent);
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            cancelPermissionDialog();
                        }
                    })
                    .create();
        }
        alertDialog.show();
    }
    //用户取消授权,关闭对话款
    private void cancelPermissionDialog() {
        alertDialog.cancel();
    }


    /**
     * todo 点击事件
     * @param v
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.uploadImg:  //打开弹窗(上传图片方式选择)
                createPopupWindow(v);
                break;
            //拍照上传
            case R.id.camera_btn:
                popupWindow.dismiss();
                //6.0才用动态权限
                if (Build.VERSION.SDK_INT >= 23) {
                    checkPermission();
                }
                break;

            //从相册中选择
            case R.id.pic_btn:
                popupWindow.dismiss();
                //6.0才用动态权限
                if (Build.VERSION.SDK_INT >= 23) {
                    if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                            != PackageManager.PERMISSION_GRANTED) {
                        // 申请读写内存卡内容的权限
                        ActivityCompat.requestPermissions(this,
                                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_SDCARD_PERMISSION_REQUEST_CODE);
                    }else{
                        intent = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                        startActivityForResult(intent, REQUEST_CODE_FROM_PHOTO);
                    }
                }

                break;
            case R.id.cancel_btn: //点击取消按钮,关闭弹窗
                popupWindow.dismiss();
                break;
            case R.id.eval_commit_btn:
                submitComment();  //提交
                break;
        }
    }

    //打开相机拍照
    private void openCamera() {
        // 获取SD卡路径
        mFilePath = Environment.getExternalStorageDirectory().getPath();
        // 保存图片的文件名
        mFilePath = mFilePath + "/" + "IMG"+ Calendar.getInstance().getTime() +".png";
        //android7.0以上版本
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
            takePhotoBiggerThan7((new File(mFilePath)).getAbsolutePath());
        }else{
            Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            Uri mUri = Uri.fromFile(new File(mFilePath));

            openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,mUri);
            startActivityForResult(openCameraIntent,REQUEST_CODE_FROM_CAMERA);
        }
//        intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//        startActivityForResult(intent, REQUEST_CODE_FROM_CAMERA);
    }

    private void takePhotoBiggerThan7(String absolutePath) {
        Uri mCameraTempUri;
        try {
            ContentValues values = new ContentValues(1);
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
            values.put(MediaStore.Images.Media.DATA, absolutePath);
            mCameraTempUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            if (mCameraTempUri != null) {
                intent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraTempUri);
                intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
            }
            startActivityForResult(intent, REQUEST_CODE_FROM_CAMERA);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

   
    /**
     * todo 对拍照、相册选择图片的返回结果进行处理
     * @param requestCode 返回码,用于确定是哪个 Activity 返回的数据
     * @param resultCode 返回结果,一般如果操作成功返回的是 RESULT_OK
     * @param data 返回对应 activity 返回的数据
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            // 表示 调用照相机拍照返回
            case REQUEST_CODE_FROM_CAMERA:
                if(resultCode == RESULT_OK){
                    try {
                        // 获取输入流
                        FileInputStream is = new FileInputStream(mFilePath);
                        // 把流解析成bitmap,此时就得到了清晰的原图
                        Bitmap imageBitmap = BitmapFactory.decodeStream(is);
                        Bitmap newImageBitmap = scaleBitmap(imageBitmap,(float)0.5); //压缩图片
                        Uri imageUri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(),newImageBitmap, "IMG"+ Calendar.getInstance().getTime(),null));
                        imgString = UriToFile(imageUri);
                        upLoadImg(); //调用接口把图片上传到服务器
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
                //从相册中选择图片返回
            case REQUEST_CODE_FROM_PHOTO:
                if(resultCode == RESULT_OK){
                    try {
                        Uri uri = data.getData();
                        Bitmap imageBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(),uri);
                        Bitmap newImageBitmap = scaleBitmap(imageBitmap,(float)0.5); //压缩图片
                        Uri newUri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(),newImageBitmap,"IMG"+ Calendar.getInstance().getTime(),null));
                        imgString = UriToFile(newUri);
                        upLoadImg();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                break;
        }
    }


    /**
     * todo  uri 转 file
     * @param uri
     * @return
     */
    public String UriToFile(Uri uri) {
        String[] filePc = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(uri, filePc, null, null, null);
        cursor.moveToFirst();
        Log.i(TAG, "UriToFile: 22"+cursor);
        int col = cursor.getColumnIndex(filePc[0]);
        String pic = cursor.getString(col);
        cursor.close();
        return pic;
    }

    /**
     * todo 压缩图片
     * @param origin
     * @param ratio
     * @return
     */
    public Bitmap scaleBitmap(Bitmap origin, float ratio) {
        if (origin == null) {
            return null;
        }
        int width = origin.getWidth();
        int height = origin.getHeight();
        Matrix matrix = new Matrix();
        matrix.preScale(ratio, ratio);
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        return newBM;
    }

    /**
     * todo 创建弹窗(用于上传图片方式选择)
     * author wang
     * @param view
     */
    private void createPopupWindow(View view) {
        if(popupView==null){
            popupView = getLayoutInflater().inflate(R.layout.popup_unload_image,null);
        }
        popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT,true);
//        popupWindow.showAsDropDown(view, view.getWidth(),view.getHeight());
        popupWindow.showAtLocation(findViewById(R.id.layout_parent), Gravity.BOTTOM,0,0);  //底部显示弹窗
        popupWindow.setBackgroundDrawable(getResources().getDrawable(R.color.white));

        setAlpha(0.3f);
        //把背景还原
        popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                setAlpha(1.0f);
            }
        });

        initPopupView();
    }

    /**
     * todo 初始化弹窗的控件
     */
    private void initPopupView() {
        Button camera_btn = popupView.findViewById(R.id.camera_btn);
        Button pic_btn = popupView.findViewById(R.id.pic_btn);
        Button cancel_btn = popupView.findViewById(R.id.cancel_btn);
        camera_btn.setOnClickListener(this);
        pic_btn.setOnClickListener(this);
        cancel_btn.setOnClickListener(this);
    }

    /**
     * todo 自定义方法,遮罩层
     * @param f
     */
    private void setAlpha(float f) {
        WindowManager.LayoutParams lp =getWindow().getAttributes();
        lp.alpha = f;
        getWindow().setAttributes(lp);
    }

    /**
     * todo handler
     */
    @SuppressLint("HandlerLeak")
    Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    try{
                        JSONObject resObj = (JSONObject) msg.obj;
                        if(resObj !=null && resObj.getInt("status")==1000){
                            String imgUrl = Helper.fixImgUrl(resObj.getString("data"));
                            Map<String,Object> imageMap = new HashMap<>();
                            imageMap.put("url",imgUrl);
                            imageList.add(imageMap);
                            Log.i(TAG, "handleMessage:ist "+imageList);
                            myEvalImageAdapter.setData(imageList);
                        }
                    }catch (JSONException je){
                        je.printStackTrace();
                    }
                    break;
                case 2:
                    break;

            }
        }
    };

    /**
     * todo 上传图片(api)
     */
    private void upLoadImg() {
        try{
            new Thread(){
                @Override
                public void run() {
                    super.run();
                        JSONObject retObj = Helper.imgUpload(imgString,userToken);

                        msg = handler.obtainMessage();
                        msg.what=1;
                        msg.obj = retObj;
                        handler.sendMessage(msg);
                }
            }.start();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * todo 创建适配器
     */
    private void createAdapter(){
        GridLayoutManager gridManager = new GridLayoutManager(MyEvaluation.this,3);
        eval_image_rv.setLayoutManager(gridManager);
        myEvalImageAdapter = new MyEvalImageAdapter(getApplicationContext());
        eval_image_rv.setAdapter(myEvalImageAdapter);
        itemClick();
    }
    /**
     * todo 点击图片进行放大预览
     */
    private void itemClick() { 
    /**省略**/
    }

    /**
     * todo 点击提交评价
     */
    private void submitComment(){
     /**省略**/
     } 
  }

上面给出的代码 基本是用到的,大家也去试试,如果不出意外,是可以运行的了

四. 重点

不过接下来我要提下的就是大家调用相机拍照获取的图片不清晰的问题,当然我上面写出的代码,获取到的图片是清晰,但还是要说下:

   //打开相机拍照
    private void openCamera() {
       intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
       startActivityForResult(intent, REQUEST_CODE_FROM_CAMERA);
    }

对返回结果进行处理

  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            // 表示 调用照相机拍照
            case REQUEST_CODE_FROM_CAMERA:
                if(resultCode == RESULT_OK){
                    Bundle bundleData = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) bundleData.get("data");
                    Log.i(TAG, "onActivityResult: ff"+imageBitmap);
                    Bitmap newImageBitmap = scaleBitmap(imageBitmap,(float)0.5); //压缩图片
                    Uri imageUri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(),newImageBitmap, "IMG"+ Calendar.getInstance().getTime(),null));
                    imgString = UriToFile(imageUri);
                    upLoadImg();
                }
                break;

对于上面这种方法是不是很熟悉,因为我一开始来就是这样子写的,图片是可以获取到了,但是我这边的需求是要点击可预览大图,一看大图,被吓到了,根本看不清楚大图中的具体内容。
然后我就又去查了一下,原来调用系统相机去获取data时获取到的只是缩略图,如果想要查看大图,需要将拍照得到的原图则保存到手机中,然后再去读取。


    //打开相机拍照
    private void openCamera() {
        // 获取SD卡路径
        mFilePath = Environment.getExternalStorageDirectory().getPath();
        // 保存图片的文件名
        mFilePath = mFilePath + "/" + "IMG"+ Calendar.getInstance().getTime() +".png";
        //android7.0以上版本
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
            takePhotoBiggerThan7((new File(mFilePath)).getAbsolutePath());
        }else{
            Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            Uri mUri = Uri.fromFile(new File(mFilePath));

            openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,mUri);
            startActivityForResult(openCameraIntent,REQUEST_CODE_FROM_CAMERA);
        }
    }

    private void takePhotoBiggerThan7(String absolutePath) {
        Uri mCameraTempUri;
        try {
            ContentValues values = new ContentValues(1);
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
            values.put(MediaStore.Images.Media.DATA, absolutePath);
            mCameraTempUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            if (mCameraTempUri != null) {
                intent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraTempUri);
                intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
            }
            startActivityForResult(intent, REQUEST_CODE_FROM_CAMERA);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            // 表示 调用照相机拍照
            case REQUEST_CODE_FROM_CAMERA:
                if(resultCode == RESULT_OK){
                      try {
                        // 获取输入流
                        FileInputStream is = new FileInputStream(mFilePath);
                        // 把流解析成bitmap,此时就得到了清晰的原图
                        Bitmap imageBitmap = BitmapFactory.decodeStream(is);
                        Bitmap newImageBitmap = scaleBitmap(imageBitmap,(float)0.5); //压缩图片
                        Uri imageUri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(),newImageBitmap, "IMG"+ Calendar.getInstance().getTime(),null));
                        imgString = UriToFile(imageUri);
                        upLoadImg(); //调用接口把图片上传到服务器
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                break;

这样子就可以获取到清晰的图片啦

注意:
在Uri imageUri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(),newImageBitmap, “IMG”+ Calendar.getInstance().getTime(),null))中使用MediaStore.Images.Media.insertImage方法会使相册中生成两张一模一样的图,所以有必要的话在用完后把它給删除
this.getContentResolver().delete(uri, null, null);

五. 出错排查

以下问题是我在开发过程中遇到的,然后我自己的一个解决方案,供大家参考下,具体如何还得根据自己的情况来

  1. 问题一
    在这里插入图片描述
    解决方法:在清单文件中添加 android:requestLegacyExternalStorage="true"就可以了
    Android中拍照(相册中选择)并上传图片功能(包括动态获取权限)_第2张图片

  2. 问题二
    在这里插入图片描述
    出错原因:
    在这里插入图片描述
    解决方案:
    在这里插入图片描述

你可能感兴趣的:(Android,进阶,android,拍照上传图片)