浅谈Android的文件存储

7,641 阅读5分钟

因为项目需要,最近学了很多数据本地持久化的知识。有很多情景我们都需要遇到文件存储:从保存用户的登录状态到记录浏览信息,从保存图片到下载大型文件。所以有必要了解下Android的文件存储系统,从而轻松地去管理我们应用平常产生的数据。

内部存储

这是按存储的位置来分的。应用对内部存储操作不需要任何的权限,这就意味着不需要在AndroidManifest.xml里声明权限,也不需要在Android6.0上进行权限适配。其实做过权限适配同学都清楚,只有外部存储的权限:Manifest.permission.READ_EXTERNAL_STORAGEManifest.permission.WRITE_EXTERNAL_STORAGE。个人理解:权限是为了系统保证应用安全和用户安全而设的,既然系统对内部存储有着最高的管理权限,应用只能在系统分配的空间存储文件,就没必要有这个内部存储权限了。

我们App在内部存储都有自己的专有目录: /data/data/PackageName。存放了 WebView 缓存页面信息,SharedPreferences 和 数据库数据等。不过没root的是看不到的.当然我们可以通过Android Studio主界面右下处的Device File Exploer来查看:

预建工作目录

创建一个新应用,包名:com.renny.storage,系统在内部存储默认创建了三个空文件夹,通过Context来获取下:

  • File getFilesDir():返回内部存储的Files文件夹
  • File getCacheDir():返回内部存储的cache文件夹
  • File getCodeCacheDir() :返回内部存储的code_cache文件夹,要求Android5.0+
  • File getDataDir() : 返回内部存储的根文件夹,要求Android7.0+

通常我们将文件存放在的Files文件夹,通过fileList()可以获取该文件夹下的全部文件的文件名。openFileInput()openFileOutput() 方法也是在该目录下操作文件。这些方法都是Context里的,在activity中可以直接调用。举个例子:

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try{
            FileOutputStream outputStream = openFileOutput("hello.text", Context.MODE_PRIVATE);
            outputStream.write( "Hello world!".getBytes());
            outputStream.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
       String[] files = fileList();
        for (String file : files) {
            Log.d("file--",file);
        }
    }

这样就会在Files文件夹下面生成一个hello.text文件了,打印出的信息就是 hello.text

其他两个是缓存文件夹,当内部存储空间不足时,系统会自动优先删除 cache和 code_cache文件夹里的内容。code_cache文件夹在运行时存放应用产生的编译或者优化的代码,感觉平常用不到。

其他工作目录

上面仅仅是一个全新应用预先创建的内部存储目录。其他相关的子目录如下(使用时创建):

  • /data/data/PackageName/shared_prefs/ :
    用来存储 SharedPreference,相关函数为:getSharedPreferences(String fileName, int mode);

  • /data/data/PackageName/databases/

    用来存储数据库文件,相关函数: getDatabasePath()

  • /data/data/PackageName/app_webview

  • /data/data/PackageName/xxxwebviewcachexxx 存储应用内置 webview 所产生的 cache 和 cookies 等,该目录由于 android 版本不同名字和位置也可能不同;

  • /data/data/PackageName/lib

    用来存储该应用的 .so 静态库文件;

小结

内部存储空间较小,因为只对应用本身可见,适合保存一些私密信息,比如登录信息,需要加密的数据等等,当一个应用卸载之后,内部存储中的这些文件也会被删除。另外内部存储的文件读取速度一般也是高于外部存储的。

外部存储

外部存储空间就非常大了,大家买的手机32Gb,64Gb容量,还有还有XXGb的SD卡,都是外部存储。外部存储可以直接在手机的文件管理应用中浏览。外部存储中的文件是可以被用户或者其他应用程序修改的(系统不管了),所以读写也需要Manifest.permission.READ_EXTERNAL_STORAGEManifest.permission.WRITE_EXTERNAL_STORAGE权限,来让用户自己管理。 有了权限,外部存储的所有目录我们都能访问到。 比如根目录: Environment.getExternalStorageDirectory() 下面我们按照存储内容的用途来介绍:

公共文件目录

Environment.getExternalStoragePublicDirectory(int type) 根据type会返回对应的文件目录。这些文件目录就是用来存放所有App都可能会用到的一些文件。type有很多种,比如: Environment.DIRECTORY_DCIM,得到的就是外部存储根文件夹下的DCIM文件夹,也就是手机存储照片的地方。常用的还有:

  • Environment.DIRECTORY_DOWNLOADS Download文件夹
  • Environment.DIRECTORY_MUSIC Music文件夹
  • Environment.DIRECTORY_MOVIES Movies文件夹

等等等等。一般把需要和其他应用共享的文件都可以放这里(不过情况好像不是很多)。

应用专属文件目录

虽然在外部存储管不了应用在哪存取文件,但也不能任由应用胡乱在任何位置存文件,而且这些文件在应用卸载了以后也要删除吧,不然到后来外部存储全是卸载后应用留下的垃圾文件。所以Android有个目录用来专门给应用存文件(当然想不想在那存,系统管不了)。目录结构类似于内部存储:

/storage/emulated/0/Android/data/PackageName/

方法也和内部存储很类似:

  • Context.getExternalFilesDir(String type):Android/data/PackageName/files文件夹

  • Context.getExternalCacheDir():Android/data/PackageName/cache文件夹 上面的type和Environment.getExternalStoragePublicDirectory(int type)用法是类似的,有一点不同:

    可以为空:getContext().getExternalFilesDir(null):此时就是files根文件夹 Android/data/packageName/files

    也可以传一个值: getContext().getExternalFilesDir("apple"):此时就是files文件夹下的子文件夹:Android/data/packageName/files/apple

一些总结

  • 一般情况下有包名的路径我们都是调用Context中的方法来获得,没有包名的路径,我们直接调用Environment中的方法获得。
  • 使用外部存储前要检测权限和检测状态是否可用(通常对于sd卡来说),如果不能用就只能用内部存储了:
state = Environment.getExternalStorageState();
if(state.equals(Environment.MEDIA_MOUNTED)){
    //todo
}
  • 关于设置->应用->应用详情里面的”清除数据“与”清除缓存“选项,前者会删除所有的内部存储和外部存储的应用专属文件目录的文件夹;后者只会清楚内部存储和外部存储专属文件目录下的缓存文件夹。