Android——数据和文件存储

279 阅读4分钟

数据和文件存储概览

Android 使用的文件系统类似于其他平台上基于磁盘的文件系统。该系统为您提供了以下几种保存应用数据的选项:

  • 应用专属存储空间:存储仅供应用使用的文件,可以存储到内部存储卷中的专属目录或外部存储空间中的其他专属目录。使用内部存储空间中的目录保存其他应用不应访问的敏感信息。
  • 共享存储:存储您的应用打算与其他应用共享的文件,包括媒体、文档和其他文件。
  • 偏好设置:以键值对形式存储私有原始数据。
  • 数据库:使用 Room 持久性库将结构化数据存储在专用数据库中。

下表汇总了这些选项的特点:

内容类型访问方法所需权限其他应用是否可以访问?卸载应用时是否移除文件?
应用专属文件仅供您的应用使用的文件从内部存储空间访问,可以使用 getFilesDir() 或 getCacheDir() 方法 从外部存储空间访问,可以使用 getExternalFilesDir() 或 getExternalCacheDir() 方法从内部存储空间访问不需要任何权限 如果应用在搭载 Android 4.4(API 级别 19)或更高版本的设备上运行,从外部存储空间访问不需要任何权限
媒体可共享的媒体文件(图片、音频文件、视频)MediaStore API在 Android 11(API 级别 30)或更高版本中,访问其他应用的文件需要 READ_EXTERNAL_STORAGE 在 Android 10(API 级别 29)中,访问其他应用的文件需要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 在 Android 9(API 级别 28)或更低版本中,访问所有文件均需要相关权限是,但其他应用需要 READ_EXTERNAL_STORAGE 权限
文档和其他文件其他类型的可共享内容,包括已下载的文件存储访问框架是,可以通过系统文件选择器访问
应用偏好设置键值对Jetpack Preferences 库
数据库结构化数据Room 持久性库
您应根据自己的具体需求选择解决方案:
  • 您的数据需要占用多少空间?

    内部存储空间中用于存储应用专属数据的空间有限。如果您需要保存大量数据,请使用其他类型的存储空间。

  • 数据访问需要达到怎样的可靠程度?

    如果应用的基本功能需要某些数据(例如应用启动时需要的数据),可以将相应数据存放到内部存储目录或数据库中。存储在外部存储空间中的应用专属文件并非一直可以访问,因为有些设备允许用户移除提供外部存储空间的实体设备。

  • 您需要存储哪类数据?

    如果数据仅供您的应用使用,应使用应用专属存储空间。对于可分享的媒体内容,应使用共享的存储空间,以便其他应用可以访问相应内容。对于结构化数据,应使用偏好设置(适合键值对数据)或数据库(适合包含 2 个以上列的数据)。

  • 数据是否应仅供您的应用使用?

    在存储敏感数据(不可通过任何其他应用访问的数据)时,应使用内部存储空间、偏好设置或数据库。内部存储空间的一个额外优势是用户无法看到相应数据。

应用专属的存储库

完整代码

import android.content.Context
import java.io.*

class StorageManageClass {
    //将数据储存到文件中去 只能存储基本数据类型 int String Array
    fun savaData(name:String, outext:String, context: Context){
        try {
            val puttex= context.openFileOutput(name,Context.MODE_PRIVATE)
            val writer = BufferedWriter(OutputStreamWriter(puttex))
            writer.use {
                it.write(outext)
            }
        }catch (e: IOException){
            e.printStackTrace()
        }
    }
    //读取文件中的数据,只能存储基本数据类型 int String Array
    fun loadData(name:String, context: Context):String{
        val content=StringBuilder()
        try {
            val intput=context.openFileInput(name)
            val read= BufferedReader(InputStreamReader(intput))
            read.use {
                read.forEachLine {
                    content.append(it)
                }
            }
        }catch (e:IOException){
            e.printStackTrace()
        }
        return content.toString()
    }
}

这种存储方式,适合存放文本文件,或者把json转成jsonStr后存放。

可以存储的类型。实际运用可能比较少。 image.png

存储文件到指定目录(例如图片)

全部代码

fun getImageStoragePath(context: Context):String{
    return "${context.externalCacheDir}/imageCacheFile"
}

fun saveImagePath(context: Context, bitmap: Bitmap?): String? {
    var bitmap: Bitmap? = bitmap
    val file = File(getImageStoragePath(context))

    // 判断当前目录是否存在,不存在就创建
    if (!file.exists()) {
        file.mkdir()
    }
    val imageFile = File(file.path.toString() + "/" + System.currentTimeMillis() + ".jpg")
    try {
        val os: OutputStream = FileOutputStream(imageFile)
        bitmap?.compress(Bitmap.CompressFormat.JPEG, 100, os)
        //释放缓存
        os.flush()
        os.close()
        bitmap?.recycle()
        return imageFile.absolutePath
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}

使用方式

context?.let {
    val bitmap: Bitmap =
        BitmapFactory.decodeStream(it.contentResolver.openInputStream(uri))
    var saveImagePath = StorageManageClass().saveImagePath(it, bitmap)
    Log.i("save_succeed", "$saveImagePath")
}

偏好设置

使用 SharedPreferences

完整的代码

    /*
    * 这个 "name" 是文件名称,你也可以理解为存储数据的key值.
    * 实际运用场景,App登录了两个账户,需要记住这两个账户的偏好设置,
    * */
fun writerUserDefault(name:String){
    val edior = context?.getSharedPreferences(name,Context.MODE_PRIVATE)?.edit()
    edior?.putFloat("height", 175.0F)
    edior?.putInt("age",28)
    edior?.putFloat("weight", 90.0F)
    //添加数据提交,完成数据库储存操作
    edior?.apply()
}

//读取根据
fun readUserDefault(name:String){
    val read = context?.getSharedPreferences(name,Context.MODE_PRIVATE)
    //这里调用他们getString,getInt方法去获取前面储存的值
    val height = read?.getFloat("height", 0f)
    val age = read?.getInt("age",18)
    val weight = read?.getFloat("weight",0f)
    Log.i("readUserDefault", "height=$height age=$age weight=$weight")
}

函数传入的 "name" 是文件名称,你也可以理解为存储数据的key值。
实际运用场景: App登录了两个账户,需要记住这两个账户的偏好设置。

可存储的数据类型有:

image.png

数据库 greenDAO(Sqlite管理)

Github可能会打不开,我下面会把按照过程写下来。

官方使用教程文档

将 greenDAO 添加到您的项目中

greenDAO 在 Maven Central 上可用。请确保您使用的是最新版本的greendaogreendao-gradle-plugin工件。

将以下 Gradle 配置添加到您的 Android 项目中。在您的根build.gradle文件中:

buildscript {
    repositories {
        jcenter()
        mavenCentral() // add repository
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' // add plugin
    }
}

在您的应用模块app/build.gradle文件中:

plugins {
    id 'com.android.application'
    id 'org.greenrobot.greendao'//新增
    id 'org.jetbrains.kotlin.android'

}
 
dependencies {
    implementation 'org.greenrobot:greendao:3.3.0' // add library
}

请注意,这会将 greenDAO Gradle 插件连接到您的构建过程。当你构建你的项目时,它会生成像 DaoMaster、DaoSession 和 DAOs 这样的类。

image.png

他一定要在第二行,不然会有意想不到的错误发生。(我编译时未通过)

id 'org.greenrobot.greendao'

具体文件内容:

image.png image.png

然后Sync Now

开始使用

简介

核心内容

一旦您定义了至少一个实体并构建了您的项目,您就可以开始在您的 Android 项目中使用 greenDAO。

以下核心类是 greenDAO 的基本接口:

DaoMaster 使用greenDAO的入口。DaoMaster 拥有数据库对象(SQLiteDatabase)并管理特定模式的 DAO 类(而非对象)。它具有创建或删除表的静态方法。它的内部类 OpenHelper 和 DevOpenHelper 是在 SQLite 数据库中创建模式的 SQLiteOpenHelper 实现。

DaoSession 管理特定模式的所有可用 DAO 对象,您可以使用其中一种 getter 方法获取这些对象。DaoSession 还提供了一些通用的持久化方法,例如实体的插入、加载、更新、刷新和删除。最后,DaoSession 对象还跟踪身份范围。有关更多详细信息,请查看会话文档

DAO 数据访问对象 (DAO) 对实体进行持久化和查询。对于每个实体,greenDAO 都会生成一个 DAO。它比 DaoSession 拥有更多的持久化方法,例如:count、loadAll 和 insertInTx。

实体: 可持久化的对象。通常,实体是使用标准 Java 属性(如 POJO 或 JavaBean)表示数据库行的对象。

创建实体模型

greenDAO 3 使用注解来定义模式和实体。这是一个简单的例子:

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    @Transient
    private int tempUsageCount; // not persisted
   // getters and setters for id and user ...
}

@Entity注释将Java 类User转换为数据库支持的实体。这也将指示 greenDAO 生成必要的代码(例如 DAO)。

注意:仅支持 Java 类。如果您更喜欢 Kotlin 等其他语言,您的实体类仍必须是 Java
注意:仅支持 Java 类。如果您更喜欢 Kotlin 等其他语言,您的实体类仍必须是 Java
注意:仅支持 Java 类。如果您更喜欢 Kotlin 等其他语言,您的实体类仍必须是 Java

接下来-----------------

image.png

然后工具为你自动生成,所需要的函数方法。
就像下面这样:

@Entity
public class User {
    @Id(autoincrement = true)
    private Long id;
    @Property(nameInDb = "USERNAME")
    private String name;

    @Transient
    private int tempUsageCount; // not persis

    @Generated(hash = 873297011)
    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    @Generated(hash = 586692638)
    public User() {
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 主键限制

      目前,实体必须有一个longLong属性作为它们的主键。这是Android和SQLite的推荐做法。
    
      要解决此问题,请将您的 key 属性定义为附加属性,但为其创建唯一索引:
    

对数据库进行初始化。

/**
 * 初始化GreenDao,直接在Application中进行初始化操作,或者一个其他合适的地方
 */
private fun initGreenDao() {
    val helper: DaoMaster.DevOpenHelper = DaoMaster.DevOpenHelper(this, "test.db")
    val db: SQLiteDatabase = helper.getWritableDatabase()
    val daoMaster = DaoMaster(db)
    daoSession = daoMaster.newSession()
}

private var daoSession: DaoSession? = null
fun getDaoSession(): DaoSession? {
    return daoSession
}

我是放在了 MainActivity

数据库存储与查询

fun sqliteWriter(){
    var daoSession  = (activity as MainActivity).getDaoSession()
    var user = User();
    user.setName("刘亦菲");
    user.id = 1
    daoSession?.insert(user)
}

fun sqliteRead(){
    var  daoSession = (activity as MainActivity).getDaoSession()
   var tempList = daoSession?.userDao?.queryBuilder()?.where(UserDao.Properties.Id.eq(1))?.list()
    Log.i("sqliteRead", "${tempList?.first()?.name}")
}

以上就是 greenDAO 的简单使用。