Android 7.0适配

869 阅读2分钟

一、FileUriExposedException

问题描述

Android7.0 (N) 开始,严格执行 StrictMode 模式,对安全做更严格的校验。不允许在 App 间,使用file://的方式,传递一个 File ,否者会抛出 FileUriExposedException的异常,会直接引发 Crash。这是由于谷歌认为目标app可能不具有文件权限,会造成潜在的问题。有2种方式来解决这个问题,谷歌官方推荐的方案是FileProvider ,通过FileProvider生成一个 content:// 格式的URI。

FileProvider是android support v4包提供的,继承ContentProvider,便于将自己app的数据提供给其他app访问。在app开发过程中需要用到FileProvider的主要有:

  1. 相机拍照以及图片裁剪
  2. 调用系统应用安装器安装apk(应用升级)

FileProvider具体使用流程

1、配置AndroidManifest.xml文件

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

authorities:一个标识,在当前系统内必须是唯一值,一般用包名。

exported:表示该 FileProvider 是否需要公开出去。

granUriPermissions:是否允许授权文件的临时访问权限。

2、创建paths配置文件

在res下新建xml目录,创建一个file_paths.xml文件,文件名可以随意写,和AndroidManifest中配置的一致即可。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_storage_root"
        path="." />
    <files-path
        name="files-path"
        path="." />
    <cache-path
        name="cache-path"
        path="." />
    <!--/storage/emulated/0/Android/data/...-->
    <external-files-path
        name="external_file_path"
        path="." />
    <!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的目录-->
    <external-cache-path
        name="external_cache_path"
        path="." />
    <!--配置root-path。这样子可以读取到sd卡和一些应用分身的目录,否则微信分身保存的图片,就会导致 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/export1544062754693.jpg,在小米6的手机上微信分身有这个crash,华为没有
-->
    <root-path
        name="root-path"
        path="" />
/paths>

root-path 对应DEVICE_ROOT,也就是File DEVICE_ROOT = new File("/"),即根目录,一般不需要配置。 files-path对应 context.getFilesDir() 获取到的目录。 cache-path对应 context.getCacheDir() 获取到的目录 external-path对应 Environment.getExternalStorageDirectory() 指向的目录。 external-files-path对应 Context.getExternalFilesDir() 获取到的目录。 external-cache-path对应 Context.getExternalCacheDir() 获取到的目录。 external-media-path对应context.getExternalMediaDirs()获取到的目录。

tagvaluepath
TAG_ROOT_PATHroot-path/
TAG_FILES_PATHfiles-path/data/data/<包名>/files
context.getFilesDir()
TAG_CACHE_PATHcache-path/data/data/<包名>/cache
context.getCacheDir()
TAG_EXTERNALexternal-path/storage/emulate/0
Environment.getExternalStorageDirectory()
TAG_EXTERNAL_FILESexternal-files-path/storage/emulate/0/Android/data/<包名>/files
Context.getExternalFilesDir()
TAG_EXTERNAL_CACHEexternal-cache-path/storage/emulate/0/Android/data/<包名>/cache
Context.getExternalCacheDir()
TAG_EXTERNAL_MEDIAexternal-media-path/storage/emulate/0/Android/media/<包名>
context.getExternalMediaDirs()

3、注册FileProvider

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

authorities: 一个标识,在当前系统内必须是唯一值,一般使用包名

exported: 表示该FileProvider是否公开出去

grantUriPermissions: 是否允许授权文件的临时访问权限

绕过Uri检测

在application中添加如下代码,也可以实现绕过Uri检测,在7.0以上的系统通过file:// 的形式共享

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());
  	builder.detectFileUriExposure();
}

二、APK signature scheme v2

Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。

img

说明**

  • 只勾选V1签名就是传统方案签署,但是在 Android 7.0 上不会使用V2安全的验证方式。

  • 只勾选V2签名7.0以下会显示未安装,Android 7.0 上则会使用了V2安全的验证方式。

  • 同时勾选V1和V2则所有版本都没问题。

三、org.apache不支持问题

build.gradle里面加上

defaultConfig {
    useLibrary 'org.apache.http.legacy'
}

或者在AndroidManifest.xml添加下面的配置

<uses-library
    android:name="org.apache.http.legacy"
    android:required="false" />

四、SharedPreferences问题

从Android 7.0开始不能使用MODE_WORLD_READABLE模式创建SharedPreferences文件

SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);
/**
* File creation mode: allow all other applications to have read access to
* the created file.
* <p>
* Starting from {@link android.os.Build.VERSION_CODES#N}, attempting to use this
* mode throws a {@link SecurityException}.
*
* @deprecated Creating world-readable files is very dangerous, and likely
*             to cause security holes in applications. It is strongly
*             discouraged; instead, applications should use more formal
*             mechanism for interactions such as {@link ContentProvider},
*             {@link BroadcastReceiver}, and {@link android.app.Service}.
*             There are no guarantees that this access mode will remain on
*             a file, such as when it goes through a backup and restore.
* @see android.support.v4.content.FileProvider
* @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
*/
@Deprecated
public static final int MODE_WORLD_READABLE = 0x0001;