Android7.0适配心得

Android N 适配心得

Posted by ijays on December 12, 2016

Android7.0的部分新特性

Android7.0(Nougat)正式版分布已经有一段时间了,然而并没有仔细去研究。前几天偶然在Android7.0的设备上运行我们的App,突然的闪退让我意识到应该适配Android7.0了。于是结合官方文档和实际操作,便有了此文。

文件系统权限更改

自Android6.0起,Android 对隐私问题越来越重视,于是引入了动态权限(RunTime Permission)。在此基础上,针对Android7.0及其以后的版本,Android限制了对私有目录的访问,具体体现在:

  • 私有文件的文件权限不再由其所有者自由操作,使用MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 进行的操作将触发 SecurityException。
  • 给其他应用传递file://类型的URI可能会使接受者无法访问这个路径,因此尝试传递次类型的URI会触发FileUriExposedException。官方给出的建议是使用FileProvider来传递私有文件的内容。

应用间共享文件

针对Android7.0,Android框架强制执行了StrictMode API 政策禁止在应用外部公开file://URI。 如果一项包含文件file://URI 类型的Intent离开应用,则应用则会出现FileUriExposedException异常,之前遇到的崩溃就是这个问题。

解决办法是使用content://URI,并授予URI临时访问权限。进行此授权的最简单方式是使用FileProvider 类。

FileProvider的使用

之前的崩溃是在拍照并剪切的时候,因此现在使用FileProvider来解决这个问题。

第一步:在Manifest中注册FileProvider

 <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.yunyaoinc.mocha.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/photo_path" />
        </provider>

解释下上面的代码,name就是FileProvider的包名,authorities是FileProvider的唯一标识,类似于ContentProvider的唯一标识。exported必须为false,否则会报安全异常。grantUriPermission表示授予Uri权限。

第二步:指定共享目录

在上文的meta-data中,指定了resource目录的路径,即开发者需要在res目录下创建一个xml文件,指定共享目录的文件。内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths>
        <external-path path="images/" name="camera_photos" />
    </paths>
</resources>

这个path标签下有三个选项:

  • 代表的根目录: Context.getFilesDir()

  • 代表的根目录: Environment.getExternalStorageDirectory()

  • 代表的根目录: getCacheDir()

上面的代码意思就是访问外部存储下images目录下所有文件,实际使用时,有时并不知道确切的访问目录,这是可以奖path设置为“”,即访问整个外部存储。

第三步:代码中使用FileProvider

    public static Uri getUriProvider(Context context, File uriFile) {
        if (uriFile != null) {
            Uri uri = FileProvider.getUriForFile(context, "authorities", uriFile);
            return uri;
        }
        return null;
    }

使用getUriForFIle()这个静态方法就可以创建content://URI了,其中authorities需与Manifest 文件中定义保持一致。

因此现在拍照并裁切的代码就变成了:

//拍照后裁剪
Uri uriPhoto = Utils.getFileProviderUri(this,mFileTemp);
mFileTemp = FileUtil.getFile(AppConstants.PATH_TEMPPIC + System.currentTimeMillis());
Uri uriSave = Uri.fromFile(mFileTemp);
//经过实测,拍照后裁剪不需要强制使用content://URI
PhotoUtil.cropPhoto(this, uriPhoto, uriSave, true);
...
//裁剪
Intent intent = new Intent("com.android.camera.action.CROP");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_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);

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 和传统签名方案来签署您的应用。

虽然我们建议您对您的应用采用 APK Signature Scheme v2,但这项新方案并非强制性的。如果您的应用在使用 APK Signature Scheme v2 时不能正确开发,您可以停用这项新方案。禁用过程会导致 Android Studio 2.2 和 Android Plugin for Gradle 2.2 仅使用传统签名方案来签署您的应用。要仅用传统方案签署,打开模块级 build.gradle 文件,然后将行 v2SigningEnabled false 添加到您的版本签名配置中:

  android {
    ...
    defaultConfig { ... }
    signingConfigs {
      release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
        v2SigningEnabled false
      }
    }
  }