Android开发者之数据存储,你真的会存储数据吗?

【版权申明】非商业目的可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/102684695
出自:shusheng007

前言

现在大家都在说:数据就是未来世界的黄金,其实个人觉得应该说被处理过的数据才是,而这个处理技术就被我们亲切的称呼为大数据,就是在海量数据中淘金。
在Android的日常开发过程中,我们经常会遇到将数据存储到本地的需求,但做了很多这样的工作,却不了解各种存储方式的适用场景,导致各种安全及性能问题,我自己最开始也有这方面的问题。

选择存储方式时要考虑:数据的尺寸、类型、特征、安全性等问题,下面记录一下目前android系统提供的各种存储方式。

文件存储系统

Android的文件系统继承至Linux系统。早期的划分更侧向于物理划分,即可卸载的外部存储,例如外插sd卡,U盘等,不可卸载的为内部存储。发展到目前,这个概念已经淡化了,现在主流设备都没有sd卡插槽了,但是设备的存储仍然被划分为内部与外部。现在内外存储的最大区别集中在读写权限上。

内部存储系统

Android会为每个App分配一块私有的存储空间,存储在这里的数据只有此App自己可以访问到,用户及其他App均不可访问,被root的设备除外

特点:

  1. 数据随着App的卸载而自动删除
  2. 不需要申请存储读写权限
  3. 数据私有,除自己外不可见

内部存储又分为一个持久目录与一个缓存目录。当设备存储不足时,系统会删除缓存目录的数据以释放空间,而且不会通知App,因此在使用缓存中的数据时,原则上应该先检查其状态。为了节约空间,我们应该限制缓存目录的空间,例如2M, 然后定期主动清除。

获取内部存储目录

getFilesDir()

在我的Mix2上的路径为

/data/user/0/app的包名/files

获取内部存储缓存目录

getCacheDir() 

在我的Mix2上的路径为

/data/user/0/app的包名/cache

外部存储系统

外部存储就比较复杂了,最初的Android设备外部存储就是指外部接入的存储,例如sd卡,U盘,硬盘。现在除了被操作系统划分为内部存储的部分,其余的都叫外部存储。

外部存储甚至可以使用内部存储模拟,所以说外部还是内部就是系统根据一些标准人为划分的。

adb shell sm set-virtual-disk true

外部存储根据功能又可以进一步分为公开文件目录与私有文件目录

公开文件

指主观上希望App产生的数据可以供其他app访问,例如相机拍摄的照片希望让别的App使用,浏览器下载的文件也希望让其他App访问,即使是在用户卸载了这些App之后。

此类文件Google推荐使用如下两种方式处理

  1. MediaStore
  2. Storage Access Framework

上面两种方式均是利用content provider 来进行交互,有时间再详细写一下关于这两个方面的内容。

其实还有一种Android10以后不推荐文件目录,就是设备存储的根目录,但是要注意,访问这些目录是需要存储读写权限的。

获取根目录(Android 10 以后废弃)

Environment.getExternalStorageDirectory()

在Mix2的目录为

/storage/emulated/0

另一种获取获取根目录下一级目录的方法为

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)

在Mix2的目录为

/storage/emulated/0/Pictures

注意那个入参,Environment.DIRECTORY_PICTURES 就是“Pictures”,我们也可以传入其他的参数,例如

public static String DIRECTORY_MOVIES = "Movies";
public static String DIRECTORY_DOWNLOADS = "Download";
public static String DIRECTORY_SCREENSHOTS = "Screenshots";

第二种方式可以看做的第一种方式的特例,如果你想要在根目录下面建立一个你自己的文件夹,那就需要使用第一种方式了。

特点:

  1. 需要存储读写权限
  2. 不跟随App的卸载而删除
  3. 容量巨大
  4. 不可靠,使用时需要确保存储可用

私有文件

指主观上不希望App产生的数据被别的App访问。

是一个包含App包名的路径,与内部存储一样,存在一个持久目录和一个缓存目录

获取外部存储目录

getExternalFilesDir(String type)

当参数传入null时,在我的Mix2上的路径为

/storage/emulated/0/Android/data/app的包名/files

入参为子目录,如果传入“Pictures”,那么就会在上面的目录下面增加一级“/Pictures”
获取外部存储缓存目录

getExternalCacheDir()

在我的Mix2上的路径为

/storage/emulated/0/Android/data/app的包名/cache

一般情况下,我应该将APP的非敏感数据存放在此处。

特点:

  1. Android 4.4 (API level 19)之后不需要存储读写权限
  2. 跟随App的卸载而删除
  3. 技术上仍然是全局可访问的,只要某个应用知道具体文件的目录,且具有读写存储的权限

将图片插入相册中

有时我们会有在相册中显示APP保存的图片这样的需求,那如何做呢?

思路:使用 'MediaScannerConnection’将要展示的图片扫描一下,让系统生成一个代表这张图片的Uri并插入一个系统维护的列表中,相册会去读取这个Uri然后展示。

下面的代码的功能为:保存一张图片并扫描到媒体库中。打开系统的相册后,会看到我们保存的照片

    private fun saveImageOfRaw(resId: Int) {
        val inputStream = resources.openRawResource(resId)
        //路径必须是存储在外部存储的非私有路径下才能在相册里面展示
//        val dir = [email protected](Environment.DIRECTORY_PICTURES) 不可以
//        val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) 可以
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
        val dirPath = Environment.getExternalStorageDirectory().path
        val dir = File("$dirPath/AndroidDevMemo")
        if (dir.exists().not()) {
            dir.mkdir()
        }

        val file = File(dir, "mySon.jpg")
        val data = ByteArray(inputStream.available())
        inputStream.read(data)
        FileOutputStream(file).use {
            it.write(data)
        }
        MediaScannerConnection.scanFile(this@StorageActivity, arrayOf(file.toString()), null,
            object : MediaScannerConnection.OnScanCompletedListener {
                override fun onScanCompleted(path: String?, uri: Uri?) {
                    printResult("Scanned $path:->uri=$uri\n\n")
                }
            })
    }

图片的路径为:

/storage/emulated/0/AndroidDevMemo/mySon.jpg

扫描后生成的Uri为:

content://meida/external/images/meida/44152

值得一提的是,当图片的存储目录是私有文件目录时,无法显示在相册中,但是可以生成Uri ,需要手动保存到媒体库中吧。
请参考MediaStore

SharedPreferences

原理

底层为一个xml文件,存储路径随着不同的设备可能会不一致,此处给出获取其路径的方法

 val dataPath= getDatabasePath("app包名_preferences.xml").absolutePath

上面是获取默认文件的方式,如果自己定义了shared preferences 文件的名称,则传入自定义名称。

SharedPreferences 主要用于存储键值对

使用方法

  1. 获取一个SharedPreferences 实例
    一般会生成一个APP共享的实例
    val sharePreferences = getSharedPreferences("$PACKAGE_ID _preference", Context.MODE_PRIVATE)
    
  2. 保存数据
    保存数据有同步和异步两种方式,不过每一种方式都是先获取一个Eidtor,然后压入要保存的数据,然后提交.
    同步方式使用commit(),异步方式使用apply()
    with(sharePreferences.edit()) {
         putString("name", "ShuSheng007")
         commit()
         //commit() 同步写入
         //apply() 异步写入
     }
    
  3. 提取数据
val result = sharePreferences.getString("name", "")

数据库 (sqlite)

Android 支持 sqlite 数据库,而且提供了一套使用sqlite数据的Api,但是目前 google 推荐使用 Room这个库来代替直接操sqlite数据库Api.

概述

好知识需要及时总结,积极传播,自己就是从一个什么都不懂的人自学过来的,深知随便一个小问题对于入门的人来说就是一座大山,希望本文可以给翻山的同学一点帮助,也是对自己过往的一个记录,也许几年后咱就离开这个行业了。

不知不觉10月也过去了,突然感觉自己的人生活成了那种 “20岁就死了,80岁才埋”的样子了,对未来没有了兴奋感,没有了期待。有人说,如果感到迷惘就去读书和健身,我决定试一试,尝试做一些打破日复一日的常规的事情。。。

本文源码下载地址

发布了88 篇原创文章 · 获赞 279 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/ShuSheng0007/article/details/102684695