【转】Android7.0适配心得
本文出自:贾鹏辉的技术博客(http://www.devio.org)
http://www.devio.org/2016/09/28/Android7.0%E9%80%82%E9%85%8D%E5%BF%83%E5%BE%97
Android7.0发布已经有一个多月了,Android7.0在给用户带来一些新的特性的同时,也给开发者带来了新的挑战,这几天我将应用适配到Android7.0,其中也遇到了不少问题也踩了一些坑,在这里就把我在Android7.0适配上的一些心得分享给大家,让大家的应用能早一天跑在Android7.0上。
权限更改
随着Android版本越来越高,Android对隐私的保护力度也越来越大。从Android6.0引入的动态权限控制(Runtime Permissions)到Android7.0的“私有目录被限制访问”,“StrictMode API 政策”。这些更改在为用户带来更加安全的操作系统的同时也为开发者带来了一些新的任务。如何让你的APP能够适应这些改变而不是cash,是摆在每一位Android开发者身上的责任。
目录被限制访问
一直以来,在目录及文件的访问保护方面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之前,如果你想调用系统相机拍照可以通过以下代码来进行:
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上使用上述方式调用系统相拍照会抛出如下异常:
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
<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保持一致即可)的资源文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path path="" name="camera_photos" />
</paths>
</resources>
代表的根目录: Context.getFilesDir()
代表的根目录: Environment.getExternalStorageDirectory()
代表的根目录: getCacheDir()
心得:上述代码中
path="",是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了,如果你将path设为path="pictures", 那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
第三步:使用FileProvider
上述准备工作做完之后,现在我们就可以使用FileProvider了。
还是以调用系统相机拍照为例,我们需要将上述拍照代码修改为如下:
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);
上述代码中主要有两处改变:
- 将之前Uri的scheme类型为file的Uri改成了有FileProvider创建一个content类型的Uri。
- 添加了
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打印出来如下:
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。
另外,推荐大家使用开源工具库TakePhoto, TakePhoto是一款在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库。
裁切照片
在Android7.0之前,你可以通过如下方法来裁切照片:
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。
然后,将上述代码改为如下即可:
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);
另外,裁切照片推荐大家使用开源工具库TakePhoto, TakePhoto是一款在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库。
电池和内存
Android 6.0(API 级别 23)引入了低电耗模式,Android7.0在电池和内存上又做了进一步优化, 来减少Android应用对电量的消耗以及对内存的占用。这些优化所带来的一些规则的变更可能会影响你的应用访问系统资源,以及你的系统通过特定隐式 Intent 与其他应用互动的方式。 所以开发人员需要特别注意这些改变。
低电耗模式
在低电耗模式下,当用户设备未插接电源、处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。 Android7.0通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设备装在口袋里)时应用部分 CPU 和网络限制,进一步增强了低电耗模式。
也就是说,Android7.0会在手机屏幕关闭的状态下,限时应用对CPU以及网络的使用。
具体规则如下:
- 当设备处于充电状态且屏幕已关闭一定时间后,设备会进入低电耗模式并应用第一部分限制: 关闭应用网络访问、推迟作业和同步。
- 如果进入低电耗模式后设备处于静止状态达到一定时间,系统则会对 PowerManager.WakeLock、AlarmManager 闹铃、GPS 和 Wi-Fi 扫描应用余下的低电耗模式限制。 无论是应用部分还是全部低电耗模式限制,系统都会唤醒设备以提供简短的维护时间窗口,在此窗口期间,应用程序可以访问网络并执行任何被推迟的作业/同步。
后台优化
小伙伴们都知道在Android中有一些隐式广播,使用这些隐式广播可以做一些特定的功能,如,当手机网络变成WiFi时自动下载更新包等。 但,这些隐式广播会在后台频繁启动已注册侦听这些广播的应用,从而带来很大的电量消耗,为缓解这一问题来提升设备性能和用户体验,在Android 7.0中删除了三项隐式广播,以帮助优化内存使用和电量消耗。
Android 7.0 应用了以下优化措施:
- 在 Android 7.0上 应用不会收到
CONNECTIVITY_ACTION广播,即使你在manifest清单文件中设置了请求接受这些事件的通知。 但,在前台运行的应用如果使用BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE。 - 在 Android 7.0上应用无法发送或接收
ACTION_NEW_PICTURE或ACTION_NEW_VIDEO类型的广播。
应对策略:Android 框架提供多个解决方案来缓解对这些隐式广播的需求。 例如,JobScheduler API 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。 您甚至可以使用 JobScheduler API 来适应内容提供程序变化。
另外,大家如果想了解更多关于后台的优化可查阅后台优化。
移动设备会经历频繁的连接变更,例如在 Wi-Fi 和移动数据之间切换时。 目前,可以通过在应用清单中注册一个接收器来侦听隐式 CONNECTIVITY_ACTION 广播, 让应用能够监控这些变更。 由于很多应用会注册接收此广播,因此单次网络切换即会导致所有应用被唤醒并同时处理此广播。
【转】Android7.0适配心得的更多相关文章
- fir.im Weekly - 关于 Log Guru 开源、Xcode 探索和 Android7.0 适配
本期 fir.im Weekly 整理了最近的一些技术分享,包括关于 Log Guru 开源.Xcode 探索. Android7.0 适配等等 iOS/Android 相关的工具.源码分享和技术文章 ...
- Android7.0适配APK安装
Android7.0适配APK安装 适配的原因 对于面向Android7.0的应用,Android框架执行的StrictMode API政策禁止在您的应用外部公开file://URL.如果一项包含文件 ...
- 下载安装APK(兼容Android7.0)
我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载. 一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和 ...
- Android7.0新特性,及Android N适配
新特性部分 Android 7.0 Nougat 提供新功能以提升性能.生产效率和安全性,主要新增了下面的新特性和优化: 一.新的Notification Android N 添加了很多新的notif ...
- Appium适配Android7.0以上版本
Appium适配Android7.0以上版本 测试机型: 华为荣耀V9 安卓版本: Android7.0 appium版本: 1.65 说明: 公司新采购了一批安卓机器,拿了其中一台华为荣耀V9跑之前 ...
- Android7.0 多窗口你值得拥有
Android7.0 多窗口你值得拥有 什么是多窗口分屏? 多窗口分屏其实在国内并不陌生,已经有一些手机和平板搭载了"分屏多任务"和"APP窗口化"功能,但这些 ...
- Appium在Android7.0及以上系统运行时报错的解决方案
背景:在使用Samsung S系列手机进行自动化测试时,发现同样脚本的情况下华为荣耀系列可以正常运行,最终发现差异在于Android7.0及以上系统和appium版本不匹配,需要升级appium.但需 ...
- Android5.0和Android6.0适配
gradle配置项 compileSdkVersion 用哪个 Android SDK 版本编译你的应用.因此我们强烈推荐总是使用最新的 SDK 进行编译.在现有代码上使用新的编译检查可以获得很多好处 ...
- Android权限管理之RxPermission解决Android 6.0 适配问题
前言: 上篇重点学习了Android 6.0的运行时权限,今天还是围绕着Android 6.0权限适配来总结学习,这里主要介绍一下我们公司解决Android 6.0权限适配的方案:RxJava+RxP ...
随机推荐
- 【转载】Nginx+Tomcat 动静分离实现负载均衡
0.前期准备 使用Debian环境.安装Nginx(默认安装),一个web项目,安装tomcat(默认安装)等. 1.一份Nginx.conf配置文件 1 # 定义Nginx运行的用户 和 用户组 如 ...
- Android-View的绘制源码学习总结
##前言 算是第一篇正式的github博文,回顾了一下之前看过的view源码解析,做一个对目前为止View学习小的总结. 我觉得对于源码的解析和学习,把所有流程记下来意义并不是很大,最关键的是: 1. ...
- 自定义MVC框架之工具类-模型类
截止目前已经改造了5个类: ubuntu:通过封装验证码类库一步步安装php的gd扩展 自定义MVC框架之工具类-分页类的封装 自定义MVC框架之工具类-文件上传类 自定义MVC框架之工具类-图像处理 ...
- PHP定界符<<<eof 使用
PHP是一个Web编程语言,在编程过程中难免会遇到用echo来输出大段的html和javascript脚本的情况,如果用传统的输出方法 ——按字符串输出的话,肯定要有大量的转义符来对字符串中的引号等特 ...
- 查找具有特定属性的元素 (XPath-LINQ to XML)
XElement doc = XElement.Parse( @"<Root> <Child1>1</Child1> <Child2 Select= ...
- 设计模式原则(7)--Composition&AggregationPrinciple(CARP)--合成&聚合复用原则
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.定义: 要尽量使用合成和聚合,尽量不要使用继承. 2.使用场景: 要正确的选择合成/复用和继承,必须透彻地理 ...
- 【CDQ分治】[HNOI2010]城市建设
题目链接 线段树分治+LCT只有80 然后就有了CDQ分治的做法 把不可能在生成树里的扔到后面 把一定在生成树里的扔到并查集里存起来 分治到l=r,修改边权,跑个kruskal就行了 由于要支持撤销, ...
- flutter .g文件生成不了
[SEVERE] Conflicting outputs were detected and the build is unable to prompt for permission to remov ...
- Flutter Widgets (Container/Row/Column/Image)
俗话说知己知彼百战百胜,如果对Flutter 里面的各种Widgets不了解,那你就别想将它们组合成你想要的效果.从今天开始.会把一个一个的widget 撸一遍..知道它大概的用法.效果.当你想做某个 ...
- Android Application中的Context和Activity中的Context的异同
一.Context是什么: 1.Context是维持Android程序中各组件能够正常工作的一个核心功能类,我们选中Context类 ,按下快捷键F4,右边就会出现一个Context类的继承结构图啦, ...