Android知识Android开发Android技术知识

Java - Android N中Uri的坑,注意别掉下去了!

2017-04-19  本文已影响234人  Cosecant

<p>        如今,安卓的老东家谷歌开始慢慢的对安卓的文件管理权限越来越重视了,从Android M开始,一些涉及到个人隐私的权限都需要显示的进行声明,那么AndroidN又做了哪些变化呢?<ol><li>APP应用程序的私有文件不再向使用者放宽</li><li>Intent组件传递 file://URI 的方式可能给接收器留下无法访问的路径,触发 FileUriExposedException 异常,推荐使用FileProvider</li><li>DownloadManager不再按文件名分享私人存储的文件。旧版应用在访COLUMN_LOCAL_FILENAME时可能出现无法访问的路径。面向 Android 7.0 或更高版本的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException</li></ol></p>
<p>        在苹果上面,个人隐私权限一直都是备受重视的,尤其是文件的读写。至此,谷歌也开始对Android N的Uri进行了一些列的管理。下面我们来看看,Uri上到底发生了什么变化?
</p>
<p>        在Android中,我们最常用到的就是图片截图的功能,往往我们最常使用的就是系统提供的截图功能,如下:
</p>

Android裁图代码

 Intent intent = new Intent("com.android.camera.action.CROP");
 intent.setDataAndType(uri, "image/*");
 intent.putExtra("crop", "true");
 intent.putExtra("aspectX", 2);
 intent.putExtra("aspectY", 1);
 intent.putExtra("outputX", outputX);
 intent.putExtra("outputY", outputY);
 intent.putExtra("scale", true);
 intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
 intent.putExtra("return-data", false);
 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
 intent.putExtra("noFaceDetection", true); // no face detection
 startActivityForResult(intent, requestCode);

<p>        但是在Android N中,我们需要对Uri路径,尤其是带 file:// 的Uri要进行处理。
首先,我们需要在App的 Manifest.xml 中声明权限的 Provider
</p>
Manifest.xml

<application ...>
   ...
   <!-- 针对android N配置文件夹读取 -->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${application_id}.fileProvider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        ...
</application>

特别说明
authorities:app的包名.fileProvider
grantUriPermissions:必须是true,表示授予 URI 临时访问权限
exported:必须是false
resource:中的@xml/file_paths是我们接下来要添加的文件

<p>        注意到配置中的一个xml文件 file_paths 了吗?这里是我们需要分配给应用读取文件路径的配置文件。首先,我们应该在Res目录下创建xml文件夹,然后创建file_paths.xml文件。
</p>
file_paths.xml 配置如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!--<external-path path="Android/data/app的包名/" name="root_path" /> -->
    <external-path path="." name="external_storage_root" />
</paths>

其他说明:
path:需要临时授权访问的路径(.代表所有路径)
name:就是你给这个访问路径起个名字
<paths>根标签下可以添加的子标签也是有限的,参考官网的开发文档,除了上述的提到的<files-path>这个子标签外,还包括下面几个:<ol><li><cache-path>,表示应用默认缓存根目录,对应根目录等价于getCacheDir(),查看完整路径:/data/user/0/cn.teachcourse.demos/cache</li><li><external-path>,表示外部内存卡根目录,对应根目录等价于
Environment.getExternalStorageDirectory(),
查看完整路径:/storage/emulated/0</li><li><external-files-path>,表示外部内存卡根目录下的APP公共目录,对应根目录等价于
Context#getExternalFilesDir(String) Context.getExternalFilesDir(null),
查看完整路径:
/storage/emulated/0/Android/data/cn.teachcourse.demos/files/Download</li><li><external-cache-path>,表示外部内存卡根目录下的APP缓存目录,对应根目录等价于Context.getExternalCacheDir(),查看完整路径:
/storage/emulated/0/Android/data/cn.teachcourse.demos/cache</li></ol>最终,在file_provider.xml文件中,添加上述5种类型的临时访问权限的文件目录,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!--
    1、name对应的属性值,开发者可以自由定义;
    2、path对应的属性值,当前external-path标签下的相对路径
    比如:/storage/emulated/0/92Recycle-release.apk
    sdcard路径:/storage/emulated/0(WriteToReadActivity.java:176)
                      at cn.teachcourse.nougat.WriteToReadActivity.onClick(WriteToReadActivity.java:97)
                      at android.view.View.performClick(View.java:5610)
                      at android.view.View$PerformClick.run(View.java:22265)
    相对路径:/
    -->
    <!--1、对应内部内存卡根目录:Context.getFileDir()-->
    <files-path
        name="int_root"
        path="/" />
    <!--2、对应应用默认缓存根目录:Context.getCacheDir()-->
    <cache-path
        name="app_cache"
        path="/" />
    <!--3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()-->
    <external-path
        name="ext_root"
        path="pictures/" />
    <!--4、对应外部内存卡根目录下的APP公共目录:Context.getExternalFileDir(String)-->
    <external-files-path
        name="ext_pub"
        path="/" />
    <!--5、对应外部内存卡根目录下的APP缓存目录:Context.getExternalCacheDir()-->
    <external-cache-path
        name="ext_cache"
        path="/" />
</paths>

<p>        配置说明完了,那么我们应该如何使用这一临时权限呢?请继续往下翻页。。。</P>

 Uri uri;
 bool isSDKIntBigThanN = Build.VERSION.SDK_INIT >= Build.VERSION_CODES.N;
 if(isSDKIntBigThanN){
      uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
 }else{
      uri = Uri.from(file);
 }
 Intent intent = new Intent("com.android.camera.action.CROP");
 if(isSDKIntBiggerThanN){            
     intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加相对应的读写权限的标识
 }
 intent.setDataAndType(uri, "image/*");
 intent.putExtra("crop", "true");
 intent.putExtra("aspectX", 2);
 intent.putExtra("aspectY", 1);
 intent.putExtra("outputX", outputX);
 intent.putExtra("outputY", outputY);
 intent.putExtra("scale", true);
 intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
 intent.putExtra("return-data", false);
 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
 intent.putExtra("noFaceDetection", true); // no face detection
 startActivityForResult(intent, requestCode);

Ps: 裁图如果需要裁剪更清晰的图片,需要注释一上多个参数:
outputX, outputY, scale。因为需要更清晰的图片,又因考虑到图片占用内存较大,所以return-data必须写为false。

大致的问题都可以解决了,注意Uri的使用需要临时访问权限。如还不够明白,请参考:更详细的文档

上一篇 下一篇

猜你喜欢

热点阅读