最近对以前的Android项目进行开发时,因为新的平板Android中系统版本为10,将旧项目的Android API升级为26。然后旧代码中的拍照权限失效,经过查询资料,发现权限并未打开,因此需要提醒用户,手动打开权限。 

// 拍照的requestCode
private static final int CAMERA_REQUEST_CODE = 0x00000010;
// 申请相机权限的requestCode
private static final int PERMISSION_CAMERA_REQUEST_CODE = 0x00000012;

/**
 * 检查权限并拍照。
 * 调用相机前先检查权限。
 */
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);
    }
}

/**
 * 处理权限申请的回调。
 *
 * @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();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    //适配Android 7.0文件权限,通过FileProvider创建一个content类型的Uri
                    photoUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile);
                } else {
                    photoUri = Uri.fromFile(photoFile);
                }
            }
        }
        mCameraUri = photoUri;
        if(mCameraUri == null)
            Log.e("Test", "mCameraUri 获取为空");
        else
            Log.e("Test", "mCameraUri 获取不为空");
        if (photoUri != null) {
            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
            captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            captureIntent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            try {
                startActivityForResult(captureIntent, CAMERA_REQUEST_CODE);
            }catch (Exception e){
                System.out.println(e.getMessage());
                sendMsg(handler,error,e.getMessage());
            }
        }

    }
}

/**
 * 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法
 *
 * @return 图片的uri
 */
private Uri createImageUri() {
    String status = Environment.getExternalStorageState();
    // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
    if (status.equals(Environment.MEDIA_MOUNTED)) {
        return getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
    } else {
        return getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new ContentValues());
    }
}

/**
 * 创建保存图片的文件
 * @return
 * @throws IOException
 */
private File createImageFile() throws IOException {
    String imageName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    if (!storageDir.exists()) {
        storageDir.mkdir();
    }
    File tempFile = new File(storageDir, imageName);
    if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
        return null;
    }
    return tempFile;
}

相机拍照成功后的处理

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {

        case CAMERA_REQUEST_CODE:
            if (resultCode == RESULT_OK) {
                if (isAndroidQ) {
                    imgPhoto.setImageURI(mCameraUri);//imgPhoto 为界面中ImageView控件
                } else {
                    // 使用图片路径加载
                    imgPhoto.setImageBitmap(BitmapFactory.decodeFile(mCameraImagePath));
                }
            } else {
                Toast.makeText(this,"取消",Toast.LENGTH_LONG).show();
            }
            break;
    }
}

至此,已经可以成功调用相机进行拍照。但是在程序的测试过程中,发现并非每次都能够将照片展示出来,且伴随着程序的崩溃,但本地却以将照片存储。且在Android Studio中并未爆出错误,同过不断的进行Log日志的输出,发现mCameraUri有时会null。在Logcat中也无法捕获程序异常。但在每次调用相机时,可看到Logcat中线程标注为DEAD。通过网络查询资料(Android onSaveInstanceState()和onRestoreInstanceState()调用时机_此去正年少的博客onsaveinstancestate),发现是Activity被回收导致mCameraUri置为空。

根据资料可得:

总结下,onSaveInstanceState(Bundle outState)会在以下情况被调用:
1、当用户按下HOME键时。
2、从最近应用中选择运行其他的程序时。
3、按下电源按键(关闭屏幕显示)时。
4、从当前activity启动一个新的activity时。
5、屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)。

本项目将布局一直固定为横屏,在调用相机时,相机方向可横屏或竖屏,会触发上述第五种情况。

因此在当前Activity中重写 onSaveInstanceState()与 onRestoreInstanceState()两个方法即可。

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String imgUri= mCameraUri.toString();
    outState.putString("imgUri",imgUri);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    if (savedInstanceState!=null){
        String imgUri=savedInstanceState.getString("imgUri");
        mCameraUri = Uri.parse(imgUri);
    }
}

同样,网上还有一种解决方案,是针对Activity没有被回收,但是成员变量被回收的情况,解决方案为重写onRestoreInstanceState()方法,暂时未遇到,先留存记录。

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    if (mCameraUri == null) {
        String imgUri=savedInstanceState.getString("imgUri");
        mCameraUri = Uri.parse(imgUri);
    }
    super.onRestoreInstanceState(savedInstanceState);
}