This article was adapted from Hansion’s blog


One of the StrictMode API restrictions on Android 7.0 is directory access.

This change means that data stored on the phone cannot be accessed through the File API. That is, passing the File :// URI URI to another app may prevent the recipient from accessing the path and trigger a FileUriExposedException.

StrictMode API policy prohibits sharing files between applications in response to the above restriction. It specifies that when sharing files between applications, we can send URI of type Content :// URI and grant temporary access to the URI using FileProvider

Next, we use The FileProvider to call the system camera, album, cropping picture function compatible with Android 7.0

Step 1: Prepare for FileProvider

  • Add the provider node to androidmanifest.xml as follows:
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.hansion.chosehead"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>Copy the code

Among them: Android :exported indicates whether a ContentProvider can be used by third-party application components. The specified ContentProvider must be set to false. Otherwise you will quote exception: ava. Lang. RuntimeException: Unable to get the provider android. Support. The v4. Content. FileProvider: java.lang.SecurityException: The Provider must not be exported android: grantUriPermissions = “true” URI said granted temporary access android: resource attribute points to our path and create an XML file, File name whatever you want

  • Next, we need to create an XML directory under the resources (RES) directory and create an XML file with the above name as the name, with the following contents:

       
<paths>
    <external-path path="" name="image" />
</paths>Copy the code

External-path indicates that the root directory is: Environment. External.getexternalstoragedirectory (), also can write other, such as: Files-path indicates the root directory: context.getFilesdir () Cache-path indicates the root directory :getCacheDir() The value of the path attribute indicates the name of the hierarchy after the path. If it is empty, it indicates the root directory. If it is “Pictures”, it represents the pictures directory in the corresponding root directory

Step 2: Use a FileProvider

  • To do this, we need to add the necessary read and write permissions to androidmanifest.xml:
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />Copy the code

1. Obtain pictures from the camera

If the Intent is above Android7.0, use the FileProvider to retrieve the Uri as follows:

    /** * Get the image from the camera */
    private void getPicFromCamera() {
        // Save the file generated after calling the camera
        tempFile = new File(Environment.getExternalStorageDirectory().getPath(), System.currentTimeMillis() + ".jpg");
        // Jump to call system camera
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // Determine the version
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {   // on Android7.0 or above, use FileProvider to get the Uri
            intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(MainActivity.this."com.hansion.chosehead", tempFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
        } else {    // Otherwise use the uri.fromfile (file) method to get the Uri
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
        }
        startActivityForResult(intent, CAMERA_REQUEST_CODE);
    }Copy the code

If you’re curious about what a Uri looks like via a FileProvider, you can print it out and have a look, for example:

content://com.hansion.chosehead/image/1509356493642.jpgCopy the code

Among them: Image is the value of the name attribute in the XML file above. 1509356493642.jpg is the name of the image I created. In other words, The content: / / com. Hansion. Chosehead/image/representative is the root directory

2. Obtain pictures from the album

    /** * get pictures from albums */
    private void getPicFromAlbm() {
        Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
        photoPickerIntent.setType("image/*");
        startActivityForResult(photoPickerIntent, ALBUM_REQUEST_CODE);
    }Copy the code

3. Crop the image

    /**
     * 裁剪图片
     */
    private void cropPhoto(Uri uri) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop"."true");
        intent.putExtra("aspectX".1);
        intent.putExtra("aspectY".1);

        intent.putExtra("outputX".300);
        intent.putExtra("outputY".300);
        intent.putExtra("return-data".true);

        startActivityForResult(intent, CROP_REQUEST_CODE);
    }Copy the code

As you can see from above, we only do special processing for urIs. Yes, that’s the core change

Step 3: Receive picture information

  • In the onActivityResult method, we get the returned image information. In this case, we call Crop to crop the image, and then set, save, and upload the returned image
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        switch (requestCode) {
            case CAMERA_REQUEST_CODE:   // Returns after calling the camera
                if (resultCode == RESULT_OK) {
                    // Calling clipping with the photo returned by the camera also requires Uri processing
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        Uri contentUri = FileProvider.getUriForFile(MainActivity.this."com.hansion.chosehead", tempFile);
                        cropPhoto(contentUri);
                    } else{ cropPhoto(Uri.fromFile(tempFile)); }}break;
            case ALBUM_REQUEST_CODE:    // Returns after calling the album
                if (resultCode == RESULT_OK) {
                    Uri uri = intent.getData();
                    cropPhoto(uri);
                }
                break;
            case CROP_REQUEST_CODE:     // Call clipping and return
                Bundle bundle = intent.getExtras();
                if(bundle ! =null) {
                    // Here we get the cropped Bitmap, which can be uploaded
                    Bitmap image = bundle.getParcelable("data");
                    // Set to ImageView
                    mHeader_iv.setImageBitmap(image);
                    // You can also perform some operations such as saving and compression before uploading
// String path = saveImage("crop", image);
                }
                break; }}Copy the code
  • Save Bitmap to local:
    public String saveImage(String name, Bitmap bmp) {
        File appDir = new File(Environment.getExternalStorageDirectory().getPath());
        if(! appDir.exists()) { appDir.mkdir(); } String fileName = name +".jpg";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }Copy the code

At this point, compatibility with Android7.0 is over. In summary, when calling camera and clipping, the Uri passed in needs to be retrieved using the FileProvider


Complete code:

  • MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView mHeader_iv;

    // Album request code
    private static final int ALBUM_REQUEST_CODE = 1;
    // Camera request code
    private static final int CAMERA_REQUEST_CODE = 2;
    // Clipping the request code
    private static final int CROP_REQUEST_CODE = 3;

    // Call the camera to return the image file
    private File tempFile;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        mHeader_iv = (ImageView) findViewById(R.id.mHeader_iv);
        Button mGoCamera_btn = (Button) findViewById(R.id.mGoCamera_btn);
        Button mGoAlbm_btn = (Button) findViewById(R.id.mGoAlbm_btn);
        mGoCamera_btn.setOnClickListener(this);
        mGoAlbm_btn.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.mGoCamera_btn:
                getPicFromCamera();
                break;
            case R.id.mGoAlbm_btn:
                getPicFromAlbm();
                break;
            default:
                break; }}/** * Get the image from the camera */
    private void getPicFromCamera() {
        // Save the file generated after calling the camera
        tempFile = new File(Environment.getExternalStorageDirectory().getPath(), System.currentTimeMillis() + ".jpg");
        // Jump to call system camera
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // Determine the version
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {   // on Android7.0 or above, use FileProvider to get the Uri
            intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(MainActivity.this."com.hansion.chosehead", tempFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
            Log.e("dasd", contentUri.toString());
        } else {    // Otherwise use the uri.fromfile (file) method to get the Uri
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
        }
        startActivityForResult(intent, CAMERA_REQUEST_CODE);
    }

    /** * get pictures from albums */
    private void getPicFromAlbm() {
        Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
        photoPickerIntent.setType("image/*");
        startActivityForResult(photoPickerIntent, ALBUM_REQUEST_CODE);
    }


    /**
     * 裁剪图片
     */
    private void cropPhoto(Uri uri) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop"."true");
        intent.putExtra("aspectX".1);
        intent.putExtra("aspectY".1);

        intent.putExtra("outputX".300);
        intent.putExtra("outputY".300);
        intent.putExtra("return-data".true);

        startActivityForResult(intent, CROP_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        switch (requestCode) {
            case CAMERA_REQUEST_CODE:   // Returns after calling the camera
                if (resultCode == RESULT_OK) {
                    // Calling clipping with the photo returned by the camera also requires Uri processing
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        Uri contentUri = FileProvider.getUriForFile(MainActivity.this."com.hansion.chosehead", tempFile);
                        cropPhoto(contentUri);
                    } else{ cropPhoto(Uri.fromFile(tempFile)); }}break;
            case ALBUM_REQUEST_CODE:    // Returns after calling the album
                if (resultCode == RESULT_OK) {
                    Uri uri = intent.getData();
                    cropPhoto(uri);
                }
                break;
            case CROP_REQUEST_CODE:     // Call clipping and return
                Bundle bundle = intent.getExtras();
                if(bundle ! =null) {
                    // Here we get the cropped Bitmap, which can be uploaded
                    Bitmap image = bundle.getParcelable("data");
                    // Set to ImageView
                    mHeader_iv.setImageBitmap(image);
                    // You can also perform some operations such as saving and compression before uploading
// String path = saveImage("crop", image);
                }
                break; }}public String saveImage(String name, Bitmap bmp) {
        File appDir = new File(Environment.getExternalStorageDirectory().getPath());
        if(! appDir.exists()) { appDir.mkdir(); } String fileName = name +".jpg";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null; }}Copy the code
  • activity_main.xml

       
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="5dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="30dp"
        android:text="Choose your avatar."
        android:textSize="18sp" />

    <ImageView
        android:id="@+id/mHeader_iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="50dp"
        android:src="@mipmap/ic_launcher" />

    <android.support.v4.widget.Space
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <Button
        android:id="@+id/mGoCamera_btn"
        android:text="Photo selection"
        android:layout_marginBottom="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/mGoAlbm_btn"
        android:text="Local Album Selection"
        android:layout_marginBottom="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />


</LinearLayout>Copy the code

There are a number of areas that can be optimized, such as obtaining dynamic permissions above Android 6.0, determining whether an SD card is available before saving images, fault tolerance measures, and more. These are not the main content of this article, this article will not go into more. In fact, there are some partial methods, but I do not advocate, such as StrictMode StrictMode, or change targetSdkVersion to 24 or less methods, these are against the development philosophy of the method, it is best not to use.

// It is recommended to call if (Build) in the application onCreate() method.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(a);
            StrictMode.setVmPolicy(builder.build());
        }Copy the code

Download the source code


Reference article: taking photos on Android7.0 with selecting photo Crash issues