android文件储存

  • 应用中最通用的功能就是头像拍照,应用下载升级等。关于android系统6.0,7.0的新特性如果不是很清楚的话就会遇到很多坑。包括使用蒲公英升级,在7.0手机上也发生了解析包失败。下面我就从文件存储的内部存储,外部存储,到动态权限,7.0文件的适配进行介绍。

1.内部存储和外部存储

  1. 内部存储 位于系统中很特殊的一个位置,如果你想将文件存储于内部存储中,那么文件默认只能被你的应用访问到,且一个应用所创建的所有文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。当一个应用卸载之后,内部存储中的这些文件也被删除。我们使用SharePreference保存配置参数,使用数据库都是保存在内部存储。
  1. 对应的文件夹是 data/data/包名/…
  1. 外部存储 是可以被用户或者其他应用程序修改的,有两种类型的文件(或者目录):
  1. 公共文件Public files:文件是可以被自由访问,且文件的数据对其他应用或者用户来说都是有意义的,当应用被卸载之后,其卸载前创建的文件仍然保留。比如camera应用,生成的照片大家都能访问,而且camera不在了,照片仍然在。如果你想在外存储上放公共文件你可以使用 getExternalStoragePublicDirectory()
  2. 私有文件Private files:其实由于是外部存储的原因即是是这种类型的文件也能被其他程序访问,只不过一个应用私有的文件对其他应用其实是没有访问价值的(恶意程序除外)。外部存储上,应用私有文件的价值在于卸载之后,这些文件也会被删除。类似于内部存储。创建应用私有文件的方法是 Context.getExternalFilesDir()
  3. 注意:外部存储之前,你必须要先检查外部存储的当前状态,以判断是否可用。

2.各个目录的获取

  1. Environment.getExternalStorageDirectory();
  1. 问题:Environment.getExternalStorageDirectory() 这个方法,然后在获取的目录中新建一个hkapp文件夹,用来存放下载的apk文件。那么,这个hkapp文件到底是在那块存储区域呢?
  2. 当手机有SD卡时,getExternalStorageDirectory方法确实是获取了SD卡,而不是手机自带的存储
  3. 当手机没有SD卡时,获取的是手机的外部存储(这里再次解释下内部存储和外部存储:内部存储指的是/data/data/包名/…路径下的文件,其他的都是外部存储)
  4. 2.3之前是/sdcard,4.3之前是在/mnt/sdcard,4.3之后是在/storage/sdcard

  1. sdCard的路径为mnt/sdcard/ 即为SD卡根路径,我们可以指定访问的文件夹名

  1. File sdCard = Environment.getExternalStorageDirectory();
    File directory_pictures = new File(sdCard, “Pictures”);
    Log.i(TAG,”directory_pictures=”+directory_pictures);
    输出:directory_pictures=/mnt/sdcard/Pictures
  1. getFilesDir():得到的是/data/data/包名/files,这里存放app的私有文件
  2. getExternalFilesDir(null):mnt/sdcard/Android/data/包名/files/Caches

3.android7.0文件操作适配

  1. 在Android7.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);
  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)
  1. 使用FileProvider
  • 在AndroidManifest.xml清单文件中application节点下配置
<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>
  • ${applicationId}引用项目的包名
  • exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限。
  1. 指定共享的目录
  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>
  • files-path 代表的根目录: Context.getFilesDir()
  • external-path 代表的根目录: Environment.getExternalStorageDirectory()
  • cache-path 代表的根目录: getCacheDir()
  • 心得:上述代码中path=”“,是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了,如果你将path设为path=”pictures”,
    那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
  1. 修改之前的代码
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);
  • 上述代码中主要有两处改变:
  • 将之前Uri的scheme类型为file的Uri改成了有FileProvider创建一个content类型的Uri。
  • 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。
  • 将getUriForFile方法获取的Uri打印出来如下:
  • 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
  1. 3.

备注

  • 开发中具体具体文件怎么存储推荐博客