Android 6.0/7.0权限问题

Android6.0动态权限控制

Android6.0系统

在Android6.0(M)之前,在用户安装应用的时候会产生一个权限列表,只有用户允许这些权限后,应用才可以正常的安装,这就会产生一个问题,这些权限对用户是不具有感知性的,也就是说用户都不知道你要这些权限干什么,我明明装的是一个阅读类型的应用,你却要我拨打电话的权限,你想干嘛呢?当然绝大部分的开发者是善意的,但也避免不了一些特殊人群利用这些“漏洞”做一些不好的事情。

而在Android6.0(M)之后,用户是可以不管权限直接安装应用的,当应用需要调用某些权限的时候,会给予用户一个通知与说明,我要这些权限干什么,这样下来可以让用户有更加清醒的权限分配意识,也在一定程度上更加人性化的保护了用户的隐私,避免了“权限一刀切”。

权限分组

在Android6.0(M)之后,对权限进行了分类,大致有这三种:

普通权限
也就是正常权限,是对手机的一些正常操作,对用户的隐私没有太大影响的权限,比如手机的震动,网络访问,蓝牙等权限,这些权限会在应用被安装的时候默认授予,用户不能拒绝,也不能取消。

详细请参考:https://developer.android.com/guide/topics/permissions/normal-permissions.html

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS

对于上面这些权限,和Android6.0(M)之前的系统一样,在AndroidManifest.xml声明即可。

1
2
3
4
5
6
7
8
9
10
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">

<uses-permission android:name="android.permission.SEND_SMS"/>


<application ...>
...
</application>

</manifest>

危险权限

详细请参考文档:https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous

其实就是运行中需要处理的权限,也是我们最需要注意的权限,这些权限会关系到用户的隐私或影响到其他应用的运行,这些危险权限,谷歌还做了一个权限组,以分组的形式来呈现:

Android危险权限分组

由于运行权限机制的出现,变得我们需要对新开发的应用去做适配,如果是6.0之前开发的谷歌做了良好的适配:当你的应用targetSdkVersion小于23的时候,就算你运行在Android6.0系统上,它也会默认采用以前的权限管理机制,也就是一刀切。当你的targetSdkVersion大于等于23的时候且在Andorid6.0(M)系统上,它才会采用新的这套权限管理机制。

所以如果你想逃开这个“麻烦”,只要把targetSdkVersion的版本设置为低于23就可以了,不过不建议采用这种方案,该来的总是要来的,随着国产手机ROM的更新,比如小米,华为等也开始有部分机型进行了系统升级,所以这是种趋势。
说了这么多,那么来看下怎么进行Android6.0(M)的权限管理适配吧,其实很简单,只需要记住下面几个API方法就可以:(API23之后提供)

1
2
3
4
5
6
7
8
//用来检测应用是否已经具有权限
int checkSelfPermission(String permission)

//进行请求单个或多个权限
void requestPermissions(String[] permissions, int requestCode)

//请求权限结果回调
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)

运行时权限请求

如果您的应用需要危险权限,则每次执行需要这一权限的操作时您都必须检查自己是否具有该权限。用户始终可以自由调用此权限,因此,即使应用昨天使用了相机权限,它不能确定今天仍具有该权限。

检查权限

要检查您是否具有某项权限,请调用 ContextCompat.checkSelfPermission() 方法。例如,以下代码段显示了如何检查 Activity 是否具有在日历中进行写入的权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
View.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//判断当前系统是否高于或等于6.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//当前系统大于等于6.0
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
//具有日历写入权限
//具体调用代码
} else {
//不具有日历写入权限,需要进行权限申请
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}
} else {
//当前系统小于6.0,不用动态申请

}
}
});

如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。

请求权限

如果您的应用需要应用清单中列出的危险权限,那么,它必须要求用户授予该权限。Android 为您提供了多种权限请求方式。调用这些方法将显示一个标准的 Android 对话框,不过,您不能对它们进行自定义。

Android动态申请权限

如果应用尚无所需的权限,则应用必须调用一个 requestPermissions() 方法,以请求适当的权限。应用将传递其所需的权限,以及您指定用于识别此权限请求的整型请求代码。此方法异步运行:它会立即返回,并且在用户响应对话框之后,系统会使用结果调用应用的回调方法,将应用传递的相同请求代码传递到 requestPermissions()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {

// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.

} else {

// No explanation needed, we can request the permission.

ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);

// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}

处理权限请求的响应

当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应。您的应用必须替换该方法,以了解是否已获得相应权限。回调会将您传递的相同请求代码传递给 requestPermissions()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// permission was granted, yay! Do the
// contacts-related task you need to do.

} else {

// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}

// other 'case' lines to check for other
// permissions this app might request
}
}

不再提醒勾选

上面的那张图,有的朋友应该已经留意到了有个不再提醒的勾选框,如果用户勾选了不再提醒,然后把你拒绝了,那你的应用就GG了,其实这里还有一个API方法:

1
2
3
4
if(!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){
//如果用户勾选了不再提醒,则返回false
//给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限打开
}

当用户勾选了不再提醒的框并把你拒之门外了,这个方法的返回值是true,它可以帮助你再一次的提醒用户,你需要这个权限,也是你最后翻身的方法了。但是在实际开发中,不得不说的一个坑,由于国内第三方ROM对系统改造的太严重,比如小米,亲测有些机型的这个方法是不起作用的,永远的是返回false,这个时候该怎么办,就要另外想解决方案了。

权限分组的意义

需要注意的一点是上面的权限分组,比如读写文件权限它们是属于同一个权限组的,你如果拿到了他们其中的一个权限,那么也同时会有另一个权限,同理,如果你拿到读取通讯录的权限,那么你同事也会拥有写入通讯录的权限,这样就避免了我们在申请相关权限的时候需要些老长老长的权限代码了。

WRITE_EXTERNAL_STORAGE
READ_EXTERNAL_STORAGE

特殊权限

特殊权限,比如:
系统级别对话框:SYSTEM_ALERT_WINDOW
修改系统设置:WRITE_SETTINGS
这2个特殊权限,我们需要在startActivityForResult里调用即可,这2个权限一般是不会用到,会用到的地方要么是黑科技或者是反用户体验的场景,这里就不再做过多描述,有兴趣的朋友自己探索吧。

这里需要另外提到的一个权限:READ_PHONE_STATE
我们可以通过这个权限来获取机器的唯一标识码,很多第三方统计是基于这个标识码来完成统计的,但是在我们应用一开始运行的时候,这个运行权限我们是没有的,在Application里我们也不能对权限进行获取,所以这点也需要我们去注意。

6.0权限的最佳使用方法

应用如果一味要求用户提供授权,可能会让用户无所适从。如果用户发现应用难以使用,或者担心应用会滥用其信息,他们可能不愿意使用该应用,甚至会将其完全卸载。以下最佳做法有助于避免此类糟糕的用户体验。

考虑使用intent

许多情况下,您可以使用以下两种方式之一来让您的应用执行某项任务。您可以将应用设置为要求提供权限才能执行操作。或者,您可以将应用设置为使用 intent,让其他应用来执行任务。

例如,假设应用需要使用设备相机才能够拍摄照片。应用可以请求 CAMERA 权限,以便允许其直接访问相机。然后,应用将使用 Camera API 控制相机并拍摄照片。利用此方法,您的应用能够完全控制摄影过程,并支持您将相机 UI 整合至应用中。

不过,如果您无需此类完全控制,则可以使用 ACTION_IMAGE_CAPTURE intent 来请求图像。发送该 intent 时,系统会提示用户选择相机应用(如果没有默认相机应用)。用户使用选定的相机应用拍摄照片,该相机应用会将照片返回给应用的 onActivityResult() 方法。

同样,如果您需要拨打电话、访问用户的联系人或要执行其他操作,可以通过创建适当的 intent 来完成,或者您也可以请求相应的权限并直接访问相应的对象。每种方法各有优缺点。

仅要求您需要的权限,在需要的时候才请求

每次您要求权限时,实际上是在强迫用户作出决定。您应尽量减少提出这些请求的次数。如果用户运行的是 Android 6.0(API 级别 23)或更高版本,则每次用户尝试要求提供权限的新应用功能时,应用都必须中断用户的操作并发起权限请求。如果用户运行的是较早版本的 Android,则在安装应用时需要为应用的每一权限请求给予授权;如果列表过长或看起来不合适,用户可能会决定不安装该应用。为此,您应尽量减少应用需要的权限数。

某些情况下,一项或多项权限可能是应用所必需的。在这种情况下,合理的做法是,在应用启动之后立即要求提供这些权限。例如,如果您运行摄影应用,应用需要访问设备的相机。在用户首次启动应用时,他们不会对提供相机使用权限的要求感到惊讶。但是,如果同一应用还具备与用户联系人共享照片的功能,您不应在应用首次启动时要求用户提供 READ_CONTACTS 权限,而应等到用户尝试使用“共享”功能之后,再要求提供该权限。

解释需要权限的原因

系统在您调用 requestPermissions() 时显示的权限对话框将说明应用需要的权限,但不会解释为何需要这些权限。某些情况下,用户可能会感到困惑。因此,最好在调用 requestPermissions() 之前向用户解释应用需要相应权限的原因。

例如,摄影应用可能需要使用位置服务,以便能够为照片添加地理标签。通常,用户可能不了解照片能够包含位置信息,并且对摄影应用想要了解具体位置感到不解。因此在这种情况下,应用最好在调用 requestPermissions() 之前告知用户此功能的相关信息。

自定义权限

要自定义权限必须使用一个或多个元素在 AndroidManifest.xml 中声明它们。

1
2
3
4
5
6
7
8
9
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp" >
<permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY"
android:label="@string/permlab_deadlyActivity"
android:description="@string/permdesc_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous" />
...
</manifest>

注:系统不允许多个软件包使用同一名称声明权限,除非所有软件包都使用同一证书签署。如果软件包声明权限,则系统不允许用户安装具有相同权限名称的其他软件包,除非这些软件包使用与第一个软件包相同的证书签署。为避免命名冲突,建议对自定义权限使用相反域名样式命名,例如 com.example.myapp.ENGAGE_HYPERSPACE。

protectionLevel 属性是必要属性,用于指示系统如何向用户告知需要权限的应用,或者谁可以拥有该权限,具体如链接的文档中所述。

android:permissionGroup 属性是可选属性,只是用于帮助系统向用户显示权限。大多数情况下,您要将此设为标准系统组(列在 android.Manifest.permission_group 中),但您也可以自己定义一个组。建议使用现有的组,因为这样可简化向用户显示的权限 UI。

Android7.0系统权限更改

随着Android版本越来越高,Android对隐私的保护力度也越来越大。从Android6.0引入的动态权限控制(Runtime Permissions)到Android7.0的“私有目录被限制访问”,“StrictMode API 政策”。这些更改在为用户带来更加安全的操作系统的同时也为开发者带来了一些新的任务。如何让你的APP能够适应这些改变而不是crash,是摆在每一位Android开发者身上的责任。

Android 7.0系统

目录被限制访问

一直以来,在目录及文件的访问保护方面iOS做的是很到位的,如:iOS的沙箱机制。但,Android在这方面的保护就有些偏弱了,在Android中应用可以读写手机存储中任何一个目录及文件,这也带来了很多的安全问题。现在Android也在着力解决这一问题。

在Android7.0中为了提高私有文件的安全性,面向 Android N 或更高版本的应用私有目录将被限制访问。对于这个权限的更改开发者需要留意一下改变:

私有文件的文件权限不在放权给所有的应用,使用 MODE_WORLD_READABLE 或 MODE_WORLD_WRITEABLE 进行的操作将触发 SecurityException。

应对策略:这项权限的变更将意味着你无法通过File API访问手机存储上的数据了,基于File API的一些文件浏览器等也将受到很大的影响,看到这大家是不是惊呆了呢,不过迄今为止,这种限制尚不能完全执行。 应用仍可能使用原生 API 或 File API 来修改它们的私有目录权限。 但是,Android官方强烈反对放宽私有目录的权限。可以看出收起对私有文件的访问权限是Android将来发展的趋势。

给其他应用传递 file:// URI 类型的Uri,可能会导致接受者无法访问该路径。 因此,在Android7.0中尝试传递 file:// URI 会触发 FileUriExposedException。

应对策略:大家可以通过使用FileProvider来解决这一问题。

DownloadManager 不再按文件名分享私人存储的文件。COLUMN_LOCAL_FILENAME在Android7.0中被标记为deprecated ,
旧版应用在访问 COLUMN_LOCAL_FILENAME时可能出现无法访问的路径。 面向 Android N 或更高版本的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException。

应对策略:大家可以通过ContentResolver.openFileDescriptor()来访问由 DownloadManager 公开的文件。

应用间共享文件

在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照,或裁切照片。

应对策略:若要在应用间共享文件,可以发送 content:// URI类型的Uri,并授予 URI 临时访问权限。 进行此授权的最简单方式是使用 FileProvider类。 如需有关权限和共享文件的更多信息,请参阅共享文件。

在Android7.0上调用系统相机拍照,裁切照片

调用系统相机拍照

在Android7.0之前,如果你想调用系统相机拍照可以通过以下代码来进行:

1
2
3
4
5
6
7
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri imageUri = Uri.fromFile(file);
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI
startActivityForResult(intent,1006);

在Android7.0上使用上述方式调用系统相拍照会抛出如下异常:

1
2
3
4
5
6
7
8
9
android.os.FileUriExposedException: file:////storage/emulated/0/temp/1474956193735.jpg exposed beyond app through Intent.getData()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8933)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
at android.app.Activity.startActivityForResult(Activity.java:4223)
...
at android.app.Activity.startActivityForResult(Activity.java:4182)

这是由于Android7.0执行了“StrictMode API 政策禁”的原因,不过小伙伴们不用担心,上文讲到了可以用FileProvider来解决这一问题,
现在我们就来一步一步的解决这个问题。

使用FileProvider

使用FileProvider的大致步骤如下:
第一步:在manifest清单文件中注册provider

1
2
3
4
5
6
7
8
9
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.jph.takephoto.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

心得:exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限。

第二步:指定共享的目录

为了指定共享的目录我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,内容如下:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path path="" name="camera_photos" />
</paths>
</resources>

  • <\files-path/>代表的根目录: Context.getFilesDir()
  • <\external-path/>代表的根目录: Environment.getExternalStorageDirectory()
  • <\cache-path/>代表的根目录: getCacheDir()

心得:上述代码中path=””,是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了,如果你将path设为path=”pictures”,
那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。

第三步:使用FileProvider

上述准备工作做完之后,现在我们就可以使用FileProvider了。
还是以调用系统相机拍照为例,我们需要将上述拍照代码修改为如下:

1
2
3
4
5
6
7
8
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri imageUri = FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider", file);//通过FileProvider创建一个content类型的Uri
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI
startActivityForResult(intent,1006);

上述代码中主要有两处改变:

  1. 将之前Uri的scheme类型为file的Uri改成了有FileProvider创建一个content类型的Uri。

  2. 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。

心得:上述代码通过FileProvider的Uri getUriForFile (Context context, String authority, File file)
静态方法来获取Uri,该方法中authority参数就是清单文件中注册provider的android:authorities=”com.jph.takephoto.fileprovider”。
对Web服务器如tomcat,IIS比较熟悉的小伙伴,都只知道为了网站内容的安全和高效,Web服务器都支持为网站内容设置一个虚拟目录,其实FileProvider也有异曲同工之处。

将getUriForFile方法获取的Uri打印出来如下:

1
content://com.jph.takephoto.fileprovider/camera_photos/temp/1474960080319.jpg。

其中camera_photos就是file_paths.xml中paths的name。

因为上述指定的path为path=””,所以content://com.jph.takephoto.fileprovider/camera_photos/代表的真实路径就是根目录,即:/storage/emulated/0/。
content://com.jph.takephoto.fileprovider/camera_photos/temp/1474960080319.jpg代表的真实路径是:/storage/emulated/0/temp/1474960080319.jpg。

裁切照片

在Android7.0之前,你可以通过如下方法来裁切照片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri outputUri = Uri.fromFile(file);
Uri imageUri=Uri.fromFile(new File("/storage/emulated/0/temp/1474960080319.jpg"));
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent,1008);

和拍照一样,上述代码在Android7.0上同样会引起android.os.FileUriExposedException异常,解决办法就是上文说说的使用FileProvider。

然后,将上述代码改为如下即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri outputUri = FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider",file);
Uri imageUri=FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider", new File("/storage/emulated/0/temp/1474960080319.jpg");//通过FileProvider创建一个content类型的Uri
Intent intent = new Intent("com.android.camera.action.CROP");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent,1008);

参考文章:

https://developer.android.com/about/versions/marshmallow/index.html
https://developer.android.com/about/versions/marshmallow/index.html
http://www.jianshu.com/p/a37f4827079a
http://www.jianshu.com/p/56b9fb319310