6. Android - 更换头像及图片裁剪(适配Android7.0)
一、概述
相信大家都用过 Android 应用中更换头像的功能,在这个功能中,用户可以拍照或者选择相册图片,然后裁剪出头像所需要的图案。
那么你们有没有考虑过这个功能怎么实现的呢?今天就让我们一步步搞定这个功能,先看运行效果,这里选择了相册图片并设置头像。
点这里下载 Demo 的 src 文件
下载之后,把里面的文件复制到你自己的项目中即可。
二、PopUpWindow 设计及弹出效果
1. 布局
优雅简洁的用户界面是吸引用户的开端,那先让我们设计一个漂亮的 PopUpWindow,如下所示:
这个 PopUpWindow 里共有3个按钮,分别为“拍照”,“从相册选择”,以及“取消”。上面两个按钮连接在了一起,下方的“取消”与它们分开,那么这里就需要3种按钮样式:“拍照”按钮只有上方是圆角,“从相册选择”按钮只有下方是圆角,“取消”按钮四个角都是圆角。
在 drawable 文件夹中新建下方3个 shape 文件。
1. white_btn
- 1
- 2
- 3
- 4
- 5
- 6
2. white_btn_top
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3. white_btn_bottom
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
有了这3个按钮样式,就可以写出 PopUpWindow 的布局了。
在 colors.xml 中添加字体颜色 <color name="colorMainGreen">#40cab3</color>
在 layout 中新建 pop_item.xml 布局,因为 PopUpWindow 弹出时,屏幕的背景会变灰,因此需要将布局的背景颜色设置为半透明灰色,颜色代码 #66000000
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
2. 动画效果
这里 PopUpWindow 的出现和消失使用淡入淡出的动画效果。
在 res 中新建 anim 文件夹,在其中新建两个动画效果。
popup_show.xml
- 1
- 2
- 3
- 4
- 5
- 6
- 7
popup_gone.xml
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在 styles.xml 中设置 PopUpWindow 整体的动画效果。
- 1
- 2
- 3
- 4
3. PopupWindow 类
有了布局和动画效果,接下来就可以写 PopUpWindow 的工具类了,这个工具类负责接收外部的点击监听器,并设置点击弹窗外关闭弹窗。
新建 PhotoPopupWindow 类,它的构造函数需要传入 “拍照” 和 “相册” 两个按钮的点击监听,具体代码如下:
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
三、在 MainActivity 中设置头像
1. activity_main 布局
- 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
- 27
- 28
- 29
2. 弹出 PopUpWindow
回顾之前的 PopUpWindow 工具类,它的构造方法需要上下文以及两个点击事件的监听器,新建 PopUpWindow 之后就可以让它显示在屏幕下方中间。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
3. 拍照或选择图片并切割
在 MainActivity 中添加如下常量:
- 1
- 2
- 3
- 4
- 5
先看在相册中选择图片,点击进入相册选择图片的按钮后,系统应该使用 startActivityForResult() 调用选择图片的 intent 并返回一个结果。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
返回的结果在 onActivityResult() 中处理,先通过 data.getData() 获取选择到的图片的 Uri,再通过 startSmallPhotoZoom() 对该图片进行裁剪。
- 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
- 27
- 28
- 29
startSmallPhotoZoom() 方法如下,它会启动系统的裁剪界面进行裁剪并返回结果。它启动的 intent 中 “return-data” 为 true,意味着它裁剪完图片会直接将图片作为 bitmap 在内存中返回。
如果你够细心,你就会发现它返回的结果也在上面 onActivityResult() 中调用 setPicToView() 方法处理了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
setPicToView() 方法如下,它将裁剪后的图片保存到指定文件夹并设置到 ImageView 中。
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
解决了相册选图,再来看拍照。
点击拍照的按钮,即调用系统的拍照功能。
- 1
- 2
- 3
- 4
- 5
修改 onActivityResult() 函数,增加拍照返回的处理,最后同样调用 startSmallPhotoZoom() 函数进行裁剪。
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
四、大图片裁剪
设置完头像,再看之前保存的图片,你会发现它们都很模糊,那如果想裁剪出清晰的图片,该怎么做呢?
还记得裁剪图片 Intent 中的这两个参数吗,它们就代表了输出图片的大小。
- 1
- 2
那么想提高图片的质量,是不是把这两个值加大就可以了呢?
在回答这个问题之前,让我们先来了解一下裁剪后的图片是怎么返回的。
假设现在有一张图片尺寸为 3200*2400px。也许你觉得返回这张图没什么问题,大不了耗1-2M的内存。不错,这个尺寸的图片确实只有1.8M左右的大小。但是你想不到的是,这个尺寸对应的 Bitmap 会耗光你应用程序的所有内存。Android出于安全性考虑,只会给你一个寒碜的缩略图。
Android 中,默认 Bitmap 为 32 位,也就是说,一个像素点占用 4 个字节,那么之前我们说的图片需要占用多大的内存呢?3200*2400*4 bytes = 30M。
整整30M!即使你想为一张只会存在几秒钟的图片消耗这么大的内存,Android 也不会答应的。
所以如果我们想提高裁剪图片的质量,可不是只加大输出的图片像素大小就可以的。那我们还应该做什么呢?先来看看裁剪图片的 Intent 可附带的参数,看看它们为我们提供了什么信息。
附带参数 | 数据类型 | 描述 |
---|---|---|
crop | String | 发送裁剪信号 |
aspectX | int | X方向上的比例 |
aspectY | int | Y方向上的比例 |
outputX | int | 裁剪区的宽 |
outputY | int | 裁剪区的高 |
scale | boolean | 是否保留比例 |
return-data | boolean | 是否将数据保留在Bitmap中返回 |
data | Parcelable | 相应的Bitmap数据 |
circleCrop | String | 圆形裁剪区域? |
MediaStore.EXTRA_OUTPUT (“output”) | Uri | 将URI指向相应的file:///… |
在小图返回模式中,我们将 return-data 设置为了“true”,因此会在内存中直接返回一个 Bitmap,由于内存的原因,它将会是一个模糊的缩略图。
如果将 return-data 设置为“false”,那么在 onActivityResult() 的 Intent 数据中你将不会接收到任何 Bitmap,相反,我们需要将 MediaStore.EXTRA_OUTPUT 关联到一个 Uri,此 Uri 是用来存放 Bitmap 的,那么裁剪后的图片就会保存到 sd 卡中。
具体代码如下:
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
之后根据需求将图片设置到 ImageView 中或者上传服务器即可,这里不再赘述。
五、Android 新版本适配
上面的程序在 Android5.X 及以下可以正常运行,但是在 Android6.0 和 Android7.0 下运行时会崩溃。这是因为 Android6.0 需要程序动态申请权限,而 Android7.0 对 Uri 添加了保护。
1. 动态权限
在 Android6.0 之后,拍照和读取本地文件都需要在运行时动态申请权限。
申请之前需要检查用户之前是否已经同意该权限。如果已经同意,则直接进行下一步操作。如果没有,则进行申请,成功后在回调方法 onRequestPermissionsResult() 中进行后续处理。
修改头像按钮的点击事件
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
权限申请的回调
- 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
- 27
- 28
- 29
- 30
- 31
2. Uri 保护
Android7.0 的官方文档是这么说的:
Passing file://URIs outside the package domain may leave the receiver with an unaccessible path. Therefore, attempts to pass a file:// URI trigger a FileUriExposedException. The recommended way to share the content of a private file is using the FileProvider.
什么意思呢?就是说,file:// 这样的 Uri 不能附着在 Intent 上,否则会引发 FileUriExposedException,官方建议使用 FileProvider 改变 Uri 的传递方式。
在这个应用中,我们在调用相机并把拍摄的照片保存到手机本地时,如果传入 file:// 这样的 Uri 就会造成应用崩溃,因此需要使用 FileProvider,步骤如下。
注:选择图片不会崩溃,因为选择图片后传入的 Uri 本身就是 Content Uri。
1. 在 res 下新建 xml 文件夹,其中新建 provider_paths.xml,代码如下
- 1
- 2
- 3
- 4
- 5
2. 在 manifest 中进行声明
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3. 将拍照行为封装成 imageCapture() 方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
4. 图片裁剪
因为裁剪图片时也会用到 Intent,所以也要对 Uri 记性处理,我们也可以使用上面的方法进行处理。具体如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这样程序就可以在 Android7.0 下正常运行。
一、概述
相信大家都用过 Android 应用中更换头像的功能,在这个功能中,用户可以拍照或者选择相册图片,然后裁剪出头像所需要的图案。
那么你们有没有考虑过这个功能怎么实现的呢?今天就让我们一步步搞定这个功能,先看运行效果,这里选择了相册图片并设置头像。
点这里下载 Demo 的 src 文件
下载之后,把里面的文件复制到你自己的项目中即可。
二、PopUpWindow 设计及弹出效果
1. 布局
优雅简洁的用户界面是吸引用户的开端,那先让我们设计一个漂亮的 PopUpWindow,如下所示:
这个 PopUpWindow 里共有3个按钮,分别为“拍照”,“从相册选择”,以及“取消”。上面两个按钮连接在了一起,下方的“取消”与它们分开,那么这里就需要3种按钮样式:“拍照”按钮只有上方是圆角,“从相册选择”按钮只有下方是圆角,“取消”按钮四个角都是圆角。
在 drawable 文件夹中新建下方3个 shape 文件。
1. white_btn
- 1
- 2
- 3
- 4
- 5
- 6
2. white_btn_top
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3. white_btn_bottom
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
有了这3个按钮样式,就可以写出 PopUpWindow 的布局了。
在 colors.xml 中添加字体颜色 <color name="colorMainGreen">#40cab3</color>
在 layout 中新建 pop_item.xml 布局,因为 PopUpWindow 弹出时,屏幕的背景会变灰,因此需要将布局的背景颜色设置为半透明灰色,颜色代码 #66000000
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
2. 动画效果
这里 PopUpWindow 的出现和消失使用淡入淡出的动画效果。
在 res 中新建 anim 文件夹,在其中新建两个动画效果。
popup_show.xml
- 1
- 2
- 3
- 4
- 5
- 6
- 7
popup_gone.xml
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在 styles.xml 中设置 PopUpWindow 整体的动画效果。
- 1
- 2
- 3
- 4
3. PopupWindow 类
有了布局和动画效果,接下来就可以写 PopUpWindow 的工具类了,这个工具类负责接收外部的点击监听器,并设置点击弹窗外关闭弹窗。
新建 PhotoPopupWindow 类,它的构造函数需要传入 “拍照” 和 “相册” 两个按钮的点击监听,具体代码如下:
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
三、在 MainActivity 中设置头像
1. activity_main 布局
- 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
- 27
- 28
- 29
2. 弹出 PopUpWindow
回顾之前的 PopUpWindow 工具类,它的构造方法需要上下文以及两个点击事件的监听器,新建 PopUpWindow 之后就可以让它显示在屏幕下方中间。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
3. 拍照或选择图片并切割
在 MainActivity 中添加如下常量:
- 1
- 2
- 3
- 4
- 5
先看在相册中选择图片,点击进入相册选择图片的按钮后,系统应该使用 startActivityForResult() 调用选择图片的 intent 并返回一个结果。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
返回的结果在 onActivityResult() 中处理,先通过 data.getData() 获取选择到的图片的 Uri,再通过 startSmallPhotoZoom() 对该图片进行裁剪。
- 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
- 27
- 28
- 29
startSmallPhotoZoom() 方法如下,它会启动系统的裁剪界面进行裁剪并返回结果。它启动的 intent 中 “return-data” 为 true,意味着它裁剪完图片会直接将图片作为 bitmap 在内存中返回。
如果你够细心,你就会发现它返回的结果也在上面 onActivityResult() 中调用 setPicToView() 方法处理了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
setPicToView() 方法如下,它将裁剪后的图片保存到指定文件夹并设置到 ImageView 中。
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
解决了相册选图,再来看拍照。
点击拍照的按钮,即调用系统的拍照功能。
- 1
- 2
- 3
- 4
- 5
修改 onActivityResult() 函数,增加拍照返回的处理,最后同样调用 startSmallPhotoZoom() 函数进行裁剪。
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
四、大图片裁剪
设置完头像,再看之前保存的图片,你会发现它们都很模糊,那如果想裁剪出清晰的图片,该怎么做呢?
还记得裁剪图片 Intent 中的这两个参数吗,它们就代表了输出图片的大小。
- 1
- 2
那么想提高图片的质量,是不是把这两个值加大就可以了呢?
在回答这个问题之前,让我们先来了解一下裁剪后的图片是怎么返回的。
假设现在有一张图片尺寸为 3200*2400px。也许你觉得返回这张图没什么问题,大不了耗1-2M的内存。不错,这个尺寸的图片确实只有1.8M左右的大小。但是你想不到的是,这个尺寸对应的 Bitmap 会耗光你应用程序的所有内存。Android出于安全性考虑,只会给你一个寒碜的缩略图。
Android 中,默认 Bitmap 为 32 位,也就是说,一个像素点占用 4 个字节,那么之前我们说的图片需要占用多大的内存呢?3200*2400*4 bytes = 30M。
整整30M!即使你想为一张只会存在几秒钟的图片消耗这么大的内存,Android 也不会答应的。
所以如果我们想提高裁剪图片的质量,可不是只加大输出的图片像素大小就可以的。那我们还应该做什么呢?先来看看裁剪图片的 Intent 可附带的参数,看看它们为我们提供了什么信息。
附带参数 | 数据类型 | 描述 |
---|---|---|
crop | String | 发送裁剪信号 |
aspectX | int | X方向上的比例 |
aspectY | int | Y方向上的比例 |
outputX | int | 裁剪区的宽 |
outputY | int | 裁剪区的高 |
scale | boolean | 是否保留比例 |
return-data | boolean | 是否将数据保留在Bitmap中返回 |
data | Parcelable | 相应的Bitmap数据 |
circleCrop | String | 圆形裁剪区域? |
MediaStore.EXTRA_OUTPUT (“output”) | Uri | 将URI指向相应的file:///… |
在小图返回模式中,我们将 return-data 设置为了“true”,因此会在内存中直接返回一个 Bitmap,由于内存的原因,它将会是一个模糊的缩略图。
如果将 return-data 设置为“false”,那么在 onActivityResult() 的 Intent 数据中你将不会接收到任何 Bitmap,相反,我们需要将 MediaStore.EXTRA_OUTPUT 关联到一个 Uri,此 Uri 是用来存放 Bitmap 的,那么裁剪后的图片就会保存到 sd 卡中。
具体代码如下:
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
之后根据需求将图片设置到 ImageView 中或者上传服务器即可,这里不再赘述。
五、Android 新版本适配
上面的程序在 Android5.X 及以下可以正常运行,但是在 Android6.0 和 Android7.0 下运行时会崩溃。这是因为 Android6.0 需要程序动态申请权限,而 Android7.0 对 Uri 添加了保护。
1. 动态权限
在 Android6.0 之后,拍照和读取本地文件都需要在运行时动态申请权限。
申请之前需要检查用户之前是否已经同意该权限。如果已经同意,则直接进行下一步操作。如果没有,则进行申请,成功后在回调方法 onRequestPermissionsResult() 中进行后续处理。
修改头像按钮的点击事件
- 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
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
权限申请的回调
- 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
- 27
- 28
- 29
- 30
- 31
2. Uri 保护
Android7.0 的官方文档是这么说的:
Passing file://URIs outside the package domain may leave the receiver with an unaccessible path. Therefore, attempts to pass a file:// URI trigger a FileUriExposedException. The recommended way to share the content of a private file is using the FileProvider.
什么意思呢?就是说,file:// 这样的 Uri 不能附着在 Intent 上,否则会引发 FileUriExposedException,官方建议使用 FileProvider 改变 Uri 的传递方式。
在这个应用中,我们在调用相机并把拍摄的照片保存到手机本地时,如果传入 file:// 这样的 Uri 就会造成应用崩溃,因此需要使用 FileProvider,步骤如下。
注:选择图片不会崩溃,因为选择图片后传入的 Uri 本身就是 Content Uri。
1. 在 res 下新建 xml 文件夹,其中新建 provider_paths.xml,代码如下
- 1
- 2
- 3
- 4
- 5
2. 在 manifest 中进行声明
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3. 将拍照行为封装成 imageCapture() 方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
4. 图片裁剪
因为裁剪图片时也会用到 Intent,所以也要对 Uri 记性处理,我们也可以使用上面的方法进行处理。具体如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这样程序就可以在 Android7.0 下正常运行。