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