老司机淡飙车技术:Android7.0适配心得

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之前,如果你想调用系统相机拍照可以通过以下代码来进行:


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

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


  1. 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. <provider 
  2.  
  3.     android:name="android.support.v4.content.FileProvider" 
  4.  
  5.     android:authorities="com.jph.takephoto.fileprovider" 
  6.  
  7.     android:grantUriPermissions="true" 
  8.  
  9.     android:exported="false"> 
  10.  
  11.     <meta-data 
  12.  
  13.         android:name="android.support.FILE_PROVIDER_PATHS" 
  14.  
  15.         android:resource="@xml/file_paths" /> 
  16.  
  17. </provider>  

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

第二步:指定共享的目录

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


  1. <?xml version="1.0" encoding="utf-8"?> 
  2.  
  3. <resources> 
  4.  
  5.     <paths> 
  6.  
  7.         <external-path path="" name="camera_photos" /> 
  8.  
  9.     </paths> 
  10.  
  11. </resources>  
  • 代表的根目录: Context.getFilesDir()
  • 代表的根目录: Environment.getExternalStorageDirectory()
  • 代表的根目录: getCacheDir()

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

第三步:使用FileProvider

上述准备工作做完之后,现在我们就可以使用FileProvider了。

还是以调用系统相机拍照为例,我们需要将上述拍照代码修改为如下:


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

另外,推荐大家使用开源工具库TakePhoto, TakePhoto是一款在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库。

裁切照片

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


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

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

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


  1. 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类型的UriIntent 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 detectionstartActivityForResult(intent,1008); 

另外,裁切照片推荐大家使用开源工具库TakePhoto, TakePhoto是一款在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库。

电池和内存

Android 6.0(API 级别 23)引入了低电耗模式,Android7.0在电池和内存上又做了进一步优化, 来减少Android应用对电量的消耗以及对内存的占用。这些优化所带来的一些规则的变更可能会影响你的应用访问系统资源,以及你的系统通过特定隐式 Intent 与其他应用互动的方式。 所以开发人员需要特别注意这些改变。

低电耗模式

在低电耗模式下,当用户设备未插接电源、处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。 Android7.0通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设备装在口袋里)时应用部分 CPU 和网络限制,进一步增强了低电耗模式。

也就是说,Android7.0会在手机屏幕关闭的状态下,限时应用对CPU以及网络的使用。

具体规则如下:

  1. 当设备处于充电状态且屏幕已关闭一定时间后,设备会进入低电耗模式并应用第一部分限制: 关闭应用网络访问、推迟作业和同步。
  2. 如果进入低电耗模式后设备处于静止状态达到一定时间,系统则会对 PowerManager.WakeLock、AlarmManager 闹铃、GPS 和 Wi-Fi 扫描应用余下的低电耗模式限制。 无论是应用部分还是全部低电耗模式限制,系统都会唤醒设备以提供简短的维护时间窗口,在此窗口期间,应用程序可以访问网络并执行任何被推迟的作业/同步。

后台优化

小伙伴们都知道在Android中有一些隐式广播,使用这些隐式广播可以做一些特定的功能,如,当手机网络变成WiFi时自动下载更新包等。 但,这些隐式广播会在后台频繁启动已注册侦听这些广播的应用,从而带来很大的电量消耗,为缓解这一问题来提升设备性能和用户体验,在Android 7.0中删除了三项隐式广播,以帮助优化内存使用和电量消耗。

Android 7.0 应用了以下优化措施:

  1. 在 Android 7.0上 应用不会收到 CONNECTIVITY_ACTION 广播,即使你在manifest清单文件中设置了请求接受这些事件的通知。 但,在前台运行的应用如果使用BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE。
  2. 在 Android 7.0上应用无法发送或接收 ACTION_NEW_PICTURE 或ACTION_NEW_VIDEO 类型的广播。

应对策略:Android 框架提供多个解决方案来缓解对这些隐式广播的需求。 例如,JobScheduler API 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。 您甚至可以使用 JobScheduler API 来适应内容提供程序变化。

另外,大家如果想了解更多关于后台的优化可查阅后台优化。

移动设备会经历频繁的连接变更,例如在 Wi-Fi 和移动数据之间切换时。 目前,可以通过在应用清单中注册一个接收器来侦听隐式 CONNECTIVITY_ACTION 广播, 让应用能够监控这些变更。 由于很多应用会注册接收此广播,因此单次网络切换即会导致所有应用被唤醒并同时处理此广播。

作者:贾鹏辉

来源:51CTO

时间: 2024-10-06 20:20:44

老司机淡飙车技术:Android7.0适配心得的相关文章

下载安装APK(兼容Android7.0)

我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载. 一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和本地的版本号比较,来判断是否需要弹出提示框下载,当然也可以通过推送的自定义消息来实现. 我们这里主要讨论的是应用程序下载,并在通知栏提醒下载完成. 实现过程大致分为三步: 创建一个service 在service启动的时候创建一个广播接受者,用于接受下载完成的广播 当BroadcastReceive

【技术干货】Docker精华学习资料集锦,老司机快上车

Docker是一个开源的应用容器引擎,提供了一种在安全.可重复的环境中自动部署软件的方式,允许开发者将他们的应用和依赖包打包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化.容器完全使用沙箱机制,相互之间不会有任何接口.几乎没有性能开销,可以很容易地在机器和数据中心中运行.重要的是,它不依赖于任何语言.框架或包装系统. 正如Docker的logo一样,Docker的思想就是源于集装箱,集装箱解决了各种型号.规格的货物在各种运输工具上进行运输的问题,而集装箱和集装箱之间

韩国学车归来 搜狗地图“随身老司机”让你更安心

近日,一则关于"韩国学车快速又便宜"的消息在网络中热传,引来各方广泛关注.据统计,在过去3年里,近7万名中国公民成为韩国驾照的持有者.不过,有媒体和专家指出,这种"多快省"的模式可能会塑造一批"马路杀手",因为中国城市道路和路况等与韩国差异甚大.各位即便轻松拿本,回国上路也要多下一番苦工,除了驾驶技术的训练,选择一款合适的导航工具来帮助认路也十分重要.新手们可选择更新快更易上手的手机地图类软件,这里推荐具有出色导航服务的搜狗手机地图,其可根据实时

IT老司机福利 | 这十多个新姿势你知道吗?

1024这个数字, 相信不少纵横IT界的老司机都十分熟悉 1024=2^10 1024字节=1KB +1024还是网络流行语 -- 然而 每天早出晚归的IT老司机 只掌握一个知识点是不行的 不upgrade自己 学习新姿势怎么棒棒? 3月24日 本周五 BingoDay2017 暨第二届品高云技术大会 品高云就要为小伙伴们展示新姿势啦! 我们精心准备了十多个技术演讲 憋问为什么揍是这么任性! 赶紧来看看有什么可听可学的吧! 演讲主题:认知数据湖 数据湖是近年热门的概念.数据湖能够帮助企业实现数据

杨曦:老司机谈混合云的接入正确姿势

混合云的接入正确姿势 在今年的双11场景下,混合云产品很好的支撑集团大促业务,而在混合云的实践过程中,如何将传统的IDC机房接入公共云,并使两者非常好的配合起来工作,这是一个非常重要的话题,可以说,关系到混合云的成败. 不仅仅是双11,混合云未来会应用在各种各样的场景中,今天的老司机是阿里云的网络大牛杨曦,今年已经是杨曦参加的第五个双11,老司机将和大家谈谈混合云接入的正确姿势和那些年遇到过的坑. 谈谈混合云的两个关键: 第一个坑是安全,业务搬到公共云上第一个遇到挑战的就是安全问题,如果没有正确

安全圈老司机为什么会在这个游戏里翻车?(内附详细解谜攻略)

       第一波 中秋节这一天,安全圈的小伙伴收到了一封SOBUG寄来的带有警告提醒的复古信件! 这究竟是一场恶作剧还是一封走心的中秋礼物?管不了那么多了,先一起来看看小伙伴们的反应. 拆开之后,都玩大啦! 经过各种烧脑玩火,只有少数智商突破天际的小伙伴通关成功了,拿到了游戏的彩蛋--冷式红包! 在大家还没从领到红包的激动中平复下来的时候,冷老板来了这么一句↓ 没错!这仅仅是个开始,还没结束,也许几周之后你就会收到一封来自悉尼的邮件,这次会是另一个宝藏吗,还是一个黑暗故事的开始,敬请期待吧.

码云周一见 | 老司机教你如何麻溜地搭建网站

Hello,大家好,小一又和大家见面了,今天小一想要跟大家聊聊网站开发的那些事儿.从近期业内动向来看,不少网站搭建团队开始向着高端网站建设.品牌网站建设.响应式网站建设等领域迈进,掀起了新一轮的"网站搭建"潮流.作为新一代的技术"弄潮儿",小一也为大家带来了码云上各位开源老司机分享出来的优秀项目,希望能够让大家畅快的享受这顿"饕餮大餐". 一.项目名称:基于 EasyFrameWork 的内容管理软件 项目简介:ZKEACMS 是基于 EasyF

老司机的双11手记:这么牛的阿里云数据库,你造怎么用吗?

老司机的双11手记 2016年天猫双11购物狂欢节已经完美落下帷幕,高峰期间订单创建每秒达到了XX万笔,总订单量达到了XX亿,技术指标再次刷新世界纪录.其中XX%的订单通过聚石塔订单推送,并在阿里云云数据库服务(AliCloudDB,曾称RDS)中完成存储和处理.在持续高压力冲击下,整个双11期间AliCloudDB表现坚如磐石: 高峰期间集群的总QPS达到了近XX每秒: 单个商家最高处理订单的能力超过XXX万单: XX万商家在AliCloudDB上稳定运行,全网实现了0故障,0丢单. 华丽数字

“精灵学院”正式开课!老司机带你领略容器编排的魅力

"BuildShip & Run anywhere",是Docker技术的口号.这个问世没有多久却有革新整个IT行业之势的技术如今被越来越多的企业和个人所重视. 但是如何成为一个善用Docker的高手成为一个又快又稳的老司机呢 最直接的方法就是由另一个老司机来带带你 谢斌精灵云布道师致力于虚拟化和云平台研究已十余年不折不扣的Docker老司机.如今我们特意邀请他跟广大技术爱好者们好好聊聊Docker实践和容器编排并分享精灵云在容器.DevOps和微服务等领域多年的实践经验&qu