Android M、N适配踩坑

   2016-11-23 0
核心提示:我们上个月才决定开始进行Android M、N的集中适配,发现很多问题,在此一起进行总结。首先我们把buildToolsVersion和compileSdkVersion都改为24,相关support的lib也都改为24.*,以此放开了适配,遇上了很多坑。这里不是一个大而全的适配方案,仅仅是一个小ap

我们上个月才决定开始进行Android M、N的集中适配,发现很多问题,在此一起进行总结。

首先我们把buildToolsVersion和compileSdkVersion都改为24,相关support的lib也都改为24.*,以此放开了适配,遇上了很多坑。

这里不是一个大而全的适配方案,仅仅是一个小app(好奇心日报)的适配总结。

Android N的适配主要为组内 同事 操刀,所以文内部分内容源于该同事的总结。

ps:此后统一博客文章的路由命名方式,改为文章创见时间命名,如“2016-11-20”,若当天有第二篇则顺序命名为“2016-11-20-1”,以此来统一化,避免未来路由失效问题。

一、权限适配 – Android M

作为一个新闻类app,适配的最主要的部分应该就是权限了。

Android6.0引入了动态权限控制,7.0使用了 私有目录被限制访问Strict Mode API 政策

因此权限适配包含app权限获取部分和私有目录访问部分。

1、权限申请

在这里,我们采用的适配方案是 关键权限预申请次要权限动态获取 的方式。至于为什么要两者结合,你自己去体会原因``。

先说关键权限预申请

这里我们学习了支付宝和饿了吗针对权限的处理方式,开启app就申请两个一定要拿到的权限:本地文件读写权限和手机识别标识的权限,如下图所示:

Android M、N适配踩坑

如果权限没有获取成功,或者后来被用户自己关掉,那则弹窗提示用户进行手动权限打开,否则app不允许进入试用,如下图,点击后跳转app的权限设定界面:

Android M、N适配踩坑

权限判断及申请代码如下所示,所有activity的onCreate都判断是否获取了必须权限:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // android 6.0及以上版本
            if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED
                    || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                // 有权限没有授予
                Intent intent = new Intent();
                intent.setClass(this, CheckPermissionActivity.class);
                startActivity(intent);
                finish();
                return;
            }
        }

动态权限申请

关键是一下几个api

  • int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
  • void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
  • void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调

聊一聊Android 6.0的运行时权限 这篇文章写的已经非常好了,不需要我继续做总结了。

2、私有目录目录访问 – Android N

####目录被限制访问

在Android中应用可以读写手机存储中任何一个目录和文件,这给系统安全带来了很多问题。

7.0中为了提高私有文件的安全性,面向7.0及更高版本的应用私有目录将被限制访问。

经测试,File api在应用内读取文件存储依然可以继续使用,应用间(主要指调用部分系统应用)进行共享会直接报错。

  • 私有文件的文件权限不在放权给所有的应用,在manifest里使用 MODE_WORLD_READABLEMODE_WORL_WRITEABLE 进行的操作将触发 SecurityException。

  • 给其他应用传递file://URI这种URI类型,可能导致接收者无法访问该路径。因此,在7.0中尝试传递file://URI会触发 FileUriExposedException。

###应用间共享文件

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

####在Android 7.0系统调用相机拍照,裁剪照片

在7.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);

在7.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)

这是因为7.0执行了“StrictMode API 政策”。

应对策略:使用FilrProvider来解决这一个问题

####使用FileProvider

  1. 在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临时访问权限。

  2. 指定共享目录

    为了指定共享的目录我们需要在资源目录下(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="pictures",它代表着根目录下的pictures目录,那么你想向其他应用共享pictures目录范围之外的文件是不可行的。_
  1. 使用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);
    

    上面的代码有两处改变:

    1. 将之前Uri的scheme类型为file的Uri改成了有FileProvider创建一个content类型的Uri。
    2. 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。

      通过FileProvider的getUriForFile(Context context, String authority, File file)静态方法来获取URI,方法中的authority就是manifest里注册provider使用的authority。

      getUriForFile方法返回的Uri为:

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

      其中 camera_photos 就是file_paths文件中paths的name。

二、support lib 24下RecyclerView的一些适配

在23及以前的RecyclerView,会默认忽略ViewHolder的ItemView的layoutParams,直接用wrap_content进行处理;但在24下,默认layoutParams会对其进行支持,如果在写item的xml时使用了match_parent,会让该item的width(height)等于RecyclerView的对应width(height),写死的dp值也会被优先读取。这当然是一种好的优化,使得过去很多需要嵌套来实现的一些“撑开”item的操作直接写在root view即可,但由于机制的修改,不可避免会出现适配的问题(宽高与想象中严重不一致)。

_无脑的适配方案就是将所有item的xml文件的root的layout_width和layout_height都改为wrap content,就变成了之前一模一样的效果

不过这里我还是建议针对item进行一些优化,将原来在ViewHolder地方进行的尺寸计划重新赋予ItemView layoutParent和通过嵌套来实现的“撑开”操作都改为在root view上进行,可以减少代码逻辑和UI层级。

RecyclerView的修改代码如下:

三、support lib 24下Notification的一点适配

SDK 24 下的NotificationManager.java的notifyAsUser出现了以下的修改,强迫6.0及以上系统下在使用notification时一定要传入small icon。我们app中仅在小米手机中用了这里的api进行打开app清理所有通知的操作,导致了非常隐蔽的crash,我们app差一点点就携带这个致命crash上线了,特此标记。

出错堆栈:

Caused by:
java.lang.IllegalArgumentException:Invalid notification (no valid small icon): Notification(pri=0 contentView=com.qdaily.ui/0x1090090 vibrate=null sound=null tick defaults=0x4 flags=0x11 color=0x00000000 vis=PRIVATE)
android.app.NotificationManager.notify(NotificationManager.java:222)
android.app.NotificationManager.notify(NotificationManager.java:194)
...

源代码如下,google在这里直接采用throw new IllegalArgumentException的方式实在太危险了,感觉这种代码都有点无语,应该让app可以设置在DEBUG模式下才throw的…

NotificationManager.java

public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        ...
        ...
        ...
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
        
        ...
        ...
        ...
    }
 
标签: 安卓开发
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 安卓中通知功能的具体实现
    安卓中通知功能的具体实现
    通知[Notification]是Android中比较有特色的功能,当某个应用程序希望给用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知实现。使用通知的步骤1、需要一个NotificationManager来获得NotificationManager manager = (NotificationManager
    02-05 安卓开发
  • Android view系统分析-setContentView
    Android view系统分析-setContentView
    第一天上班,列了一下今年要学习的东西。主要就是深入学习Android相关的系统源代码,夯实基础。对于学习Android系统源代码,也没什么大概,就从我们平常使用最基础的东西学起,也就是从view这个切入点开始学习Android的源码,在没分析源码之前,我们有的时候
    02-05 安卓开发
  • 如何进行网络视频截图/获取视频的缩略图
    如何进行网络视频截图/获取视频的缩略图
    小编导读:获取视频的缩略图,截图正在播放的视频某一帧,是在音视频开发中,常遇到的问题。本文是主要用于点播中截图视频,同时还可以获取点播视频的缩略图进行显示,留下一个问题,如下图所示, 如果要获取直播中节目视频缩略图,该怎么做呢?(ps:直播是直
  • Android NDK 层发起 HTTP 请求的问题及解决
    Android NDK 层发起 HTTP 请求的问题及解决
    前言新的一年,大家新年快乐~~鸡年大吉!本次给大家带来何老师的最新文章~虽然何老师还在过节,但依然放心不下广大开发者,在此佳节还未结束之际,给大家带来最新的技术分享~ 事件的起因不说了,总之是需要实现一个 NDK 层的网络请求。为了多端适用,还是选择
  • Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突
    Android插件化(六): OpenAtlasの改写aapt以防
    引言Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名
    02-05 安卓开发
  • Android架构(一)MVP架构在Android中的实践
    Android架构(一)MVP架构在Android中的实践
    为什么要重视程序的架构设计 对程序进行架构设计的原因,归根结底是为了 提高生产力 。通过设计是程序模块化,做到模块内部的 高聚合 和模块之间的 低耦合 (如依赖注入就是低耦合的集中体现)。 这样做的好处是使得程序开发过程中,开发人员主需要专注于一点,
    02-05 安卓开发
  • 安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件 作者: 飞龙 这个教程中我们要分析一个锁机软件。像这种软件都比较简单,完全可以顺着入口看下去,但我这里还是用关键点来定位。首先这个软件的截图是这样,进入这个界面之后,除非退出模拟器,否则没办法回到桌面。上面那
    02-05 安卓开发
  • Android插件化(二):OpenAtlas插件安装过程分析
    Android插件化(二):OpenAtlas插件安装过程分析
    在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,我们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,我们要深入到OpenAtlas的源码中进行插件安装过程的分析。 插件的安装分为3种:宿主启动时立
    02-05 安卓开发
  • [译] Android API 指南
    [译] Android API 指南
    众所周知,Android开发者有中文网站了,API 指南一眼看去最左侧的菜单都是中文,然而点进去内容还是很多是英文,并没有全部翻译,我这里整理了API 指南的目录,便于查看,如果之前还没有通读,现在可以好好看一遍。注意,如果标题带有英文,说明官方还没有翻
  • 使用FileProvider解决file:// URI引起的FileUriExposedException
    使用FileProvider解决file:// URI引起的FileUri
    问题以下是一段简单的代码,它调用系统的相机app来拍摄照片:void takePhoto(String cameraPhotoPath) {File cameraPhoto = new File(cameraPhotoPath);Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);takePhotoIntent.putExtra(Medi
    02-05 安卓开发
点击排行