Android Studio —— 数据存储

447 阅读10分钟

第一行代码读书笔记

持久化技术

  • 文件存储
  • SharedPreference存储
  • 数据库存储

文件存储

  • 不对存储的内容进行任何格式化处理
  • 适合用于存储一些简单的文本数据或二进制数据

数据存储到文件中

  • Context类提供了openFileOutput()方法,用于将数据存储到指定的文件中
  • 接收两个参数
    • 第一个参数是文件名
      不可以包含路径,所有文件默认存储到/data/data/<package name>/files/目录下
    • 第二个参数是文件的操作模式,两种可选
      MODE_PRIVATE:默认的操作模式,存在同名文件时,写入的内容覆盖原文件的内容
      MODE_APPEND:如存在同名文件,追加在源文件内容后,不存在则创建新文件
  • 返回一个FileOutputStream对象
  • FileOutputStream->OutputStreamWriter->BufferedWriter
    通过BufferWriter将文本内容写入到文件中
public void save(String inputText) {
    FileOutputStream out = null;
    BufferedWriter writer = null;
    try {
        out = openFileOutput("data", Context.MODE_PRIVATE);
        writer = new BufferedWriter(new OutputStreamWriter(out));
        writer.write(inputText);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (writer != null) {
                writer.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • Tools->Android->Android Device Monitor查看/导出文件

从文件中读取数据

  • Context类提供了openFileInput()方法,用于从文件中读取数据
  • 只接收一个参数:要读取的文件名
    系统会自动到/data/data/<package name>/files/目录下加载文件
  • 返回一个FileInputStream对象
  • FileInputStream->InputStreamReader->BuffereedReader
  • 可以通过BufferedReader一行行读取,把文本内容读取到StringBuilder对象中
public String load() {
    FileInputStream in = null;
    BufferedReader reader = null;
    StringBuilder content = new StringBuilder();
    try {
        in = openFileInput("data");
        reader = new BufferedReader(new InputStreamReader(in));
        String line = "";
        while ((line = reader.readLine()) != null) {
            content.append(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return content.toString();
}
  • 将文件中文本加载到EditText中
String inputText = load();
if (!TextUtils.isEmpty(inputText)) {
    edit.setText(inputText);
    edit.setSelection(inputText.length());
    Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show();
}
  • setText():将字符串填充到EditText里
  • SetSelection():将输入光标移动到文本的末尾位置
  • TextUtils.isEmpty():当传入的字符串等于null或者等于空字符串时都会返回true;

SharedPreferences存储

  • 使用键值对来存储数据的
  • 支持多种不同的数据类型存储

将数据存储到SharedPreferences中

  • 获取SharedPreferences对象的3种方式
    1. Context类的getSharedPreferences()方法
      • 接收两个参数
        第一个参数用于指定SharedPreferences文件的名称,如指定文件不存在则创建一个
        SharedPreferences文件都存放在/data/data/<package name>/shared_prefs/目录下 第二个参数用于指定操作模式,目前只有MODE_PRIVATE可选(默认模式,和传入0效果相同)
        表示只有当前的应用程序才可以对SharedPreferences文件进行读写(其余模式废弃)
    2. Activity类中的getPreferences()方法
      • 只接收一个操作模式参数
      • 自动将当前活动的类名作为SharedPreferences的文件名
    3. PreferenceManager类中的getDefaultSharedPreferences()方法
      • 静态方法
      • 接收一个Context参数
      • 自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件
  • 向SharedPreferences文件中存储数据
    1. 调用SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象
    2. SharedPreferences.Editor对象中添加数据
      (添加布尔型putBool(),添加字符串putString(),etc.)
    3. 调用apply()方法将添加的数据提交
    SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
    editor.putString("name", "Tom");
    editor.putInt("age", 29);
    editor.putBoolean("married", false);
    editor.apply();
    
  • 从SharedPreferences文件中读取数据
    • SharedPreferences对象中提供了一系列的get方法,每种都对应了SharedPreferences.Editor的一种put方法(E.g. getBoolean(), getString())
    • 这些get方法接收两个参数
      • 第一个参数是键
      • 第二个参数是默认值,即传入的键找不到对应的值时返回的默认值
    SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
    String name = pref.getString("name", "");
    int age = pref.getInt("age", 0);
    boolean married = pref.getBoolean("married", false);
    

SQLite数据库存储

  • 轻量级的关系型数据库
  • 支持标准SQL语法,遵循数据库的ACID事务

创建数据库

  • SQLiteOpenHelper帮助类
    • 抽象类
    • 两个抽象方法onCreate()onUpgrade(),创造、升级数据库的逻辑
    • 两个实例方法,都可以打开或创建(/升级)一个现有的数据库,不同:当数据库不可写入的时候
      • getReadableDatabase()返回的对象以只读方式打开
      • getWritableDatabase()将出现异常
    • 两个构造方法可供重写,一般使用参数少一点的构造方法即可
      • 该构造方法接收4个参数
      • 第一个参数是Context
      • 第二个参数是数据库名
      • 第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作
    • 构建出SQLiteOpenHelper实例后,再调用它的getReadableDatabase()getWritableDatabase()就能创建数据库;
      数据库文件存放在/data/data/<package name>/databases/目录下;
      此时重写的onCreate()方法也会执行(数据库已存在的时候不会执行)
    public class MyDatabaseHelper extends SQLiteOpenHelper {
        public static final String CREATE_BOOK = "create table Book ("
                + "id integer primary key autoincrement, "
                + "author text, "
                + "price real, "
                + "pages integer, "
                + "name text)";
    
        private Context mContext;
    
        public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
            mContext = context;
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(CREATE_BOOK);
            Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public  void  onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
        }
    }
    

使用adb shell查看数据库

  • Android SDK自带的调试工具,存放在sdk的platform-tools目录下
    (Windows系统下一般为C:\Users\<user name>\AppData\Local\Android\Sdk\platform-tools
    把adb的路径配置到环境变量
  • 打开命令行,输入adb shell,进入安卓设备的控制台
  • cd /data/data/<package name>/databases/进入到存放数据库文件的目录下
    ls查看该目录下的文件
    • 两个数据库文件:
      BookStore.db是我们创建的数据库文件
      BookStore.db-journal是为了让数据库能够支持事务而产生的临时日志文件(通常大小为0字节)
  • sqlite3 <database name>打开数据库
    .table查看数据库内有哪些表(android_metadata表是每个数据库中都会自动生成的)
    .schema查看建表语句
    .exit.quit退出数据库
  • 查询:select * from Book

升级数据库

  • 升级更新数据库:onUpgrade()
  • 如何执行onUpgrade():在SQLiteOpenHelper的构造函数中传入更高的版本号
    dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
    

添加数据

  • 借助getReadableDatabase()getWritableDatabase()返回的SQLiteDatabse对象进行CRUD操作
  • SQLiteDatabse提供了insert()方法
  • 接收3个参数
    第一个参数是要添加数据的表名
    第二个参数用于未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般直接传入null即可
    第三个参数是一个ContentValues对象,它提供了一系列put(),用于向ContentValues中添加数据,将表中的每个列名和相应的待添加数据传入即可
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    ContentValues values = new ContentValues();
    values.put("name", "The Da Vinci Code");
    values.put("author", "Dan Brown");
    values.put("pages", 454);
    values.put("price", 16.96);
    db.insert("Book", null, values);
    values.clear();
    values.put("name", "The Lost Symbol");
    values.put("author", "Dan Brown");
    values.put("pages", 510);
    values.put("price", 19.95);
    db.insert("Book", null, values);
    

更新数据

  • SQLiteDatabse提供了update()方法
  • 接收4个参数
    第一个参数是表名
    第二个参数是ContentValues对象
    第三、四个参数用于约束更新某一行或某几行的数据,不指定则默认更新所有行
    • 第三行对应SQL的where部分,表示更新所有name等于?的行
      ?是一个占位符,可以通过第四个参数提供的字符串数组为第三个中的每个占位符指定相应的内容
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    ContentValues values = new ContentValues();
    values.put("price", "10.99");
    db.update("Book", values, "name = ?", new String[] { "The Da Vinci Vode" });
    

删除数据

  • SQLiteDatabse提供了update()方法
  • 接收3个参数
    第一个参数是表明 第二、三个参数用于约束删除某一个或某几行的数据,不指定则默认删除所有行
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    db.delete("Book", "page > ?", new String[] { "500" });
    

查询数据

  • SQLiteDatabse提供了query()方法
  • 最短的方法重载也需要传入7个参数
    • 第一个参数是表名
    • 第二个参数是用于指定查询哪几列,不指定则默认查询所有列
    • 第三、四个参数用于约束查询某一行或某几行的数据,不指定则默认查询所有行
    • 第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作
    • 第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤
    • 第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式 | query()方法参数 | 对应SQL部分 | 描述 | |-----------------|---------------------------|-----------------------------| | table | from table_name | 指定查询的表名 | | columns | select column1, column2 | 指定查询的列名 | | selection | where column = value | 指定where的约束条件 | | selectionArgs | - | 为where中的占位符提供具体的值 | | groupBy | group by column | 指定需要group by的列 | | having | having column = value | 对group by后的结果进一步约束 | | orderBy | order by column1, column2 | 指定查询结果的排序方式 |
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    Cursor cursor = db.query("Book", null, null, null, null, null, null);
    if (cursor.moveToFirst()) {
        do {
            String name = cursor.getString(cursor.getColumnIndex("name"));
            String author = cursor.getString(cursor.getColumnIndex("author"));
            int pages = cursor.getInt(cursor.getColumnIndex("pages"));
            double price = cursor.getDouble(cursor.getColumnIndex("price"));
        } while (cursor.moveToNext());
    }
    cursor.close();
    

使用SQL操作数据库

  • 添加数据
    db.execSQL("insert into Book (name, author, page, price) values(?, ?, ?, ?)",
                new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" });
    db.execSQL("insert into Book (name, author, page, price) values(?, ?, ?, ?)",
                new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
    
  • 更新数据
    db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99", "The Da Vinci Code" });
    
  • 删除数据
    db.execSQL("delete from Book where page > ?", new String[] { "500" });
    
  • 查询数据
    db.rawQuery("select * from Book", null);
    

使用LitePal操作数据库

  • LitePal是一款开源的Android数据库框架
    采用对象关系映射(ORM)的模式将一些常用数据库功能进行封装(将面向对象的语言和面向关系的数据库之间建立映射关系)

配置LitePal

  • 大多数开源项目会将版本提交到jcenter上,只需在app/build.gradle文件中声明该开源库的引用即可
  • 编辑app/build.gradle文件,在dependencies闭包中添加:
    compile 'org.litepal.android:core:1.3.2'
    
  • 配置litepal.xml文件 在app.src.main目录下新建文件夹,创建assets目录,在asset目录下新建litepal.xml文件
    <?xml version="1.0" encoding="utf-8"?>
    <litepal>
        <dbname value="BookStore" ></dbname>
        
        <version value="1" ></version>
        
        <list>
        </list>
    </litepal>
    
    • <dbname>指定数据库名,<version>指定数据库版本号,<list>指定所有的映射模型
  • 配置AndroidManifest.xml,在标签中添加属性
    android:name="org.litepal.LitePalApplication"

创建和升级数据库

  • 声明一个Java类对应数据库中的表,类中的每一个字段分别对应了表中的每一个列,每个字段生成相应的getter和setter方法
  • 将Java类添加到映射模型类型表中,修改litepal.xml:
    <list>
        <mapping class="<package name>.<class name>"></mapping>
    </list>
    
  • 只要进行任意一次数据库操作,类对应的数据库就会自动创建出来
    E.g. Connector.getDatabase() (自动生成table_schema表供LitePal内部使用)
  • 升级:修改类的内容并将版本号加1(不需要drop之前的表)
  • 进行CRUD操作时,模型类需要继承自DataSupport

添加数据

  • 创建出模型类的实例,将所有要存储的数据设置好,调用save()方法

更新数据:

  1. 对已存储的对象重新设值,重新调用save()方法
    • model.isSave():对象是否已经存储
    • 已经调用过model.save()添加数据
      或model对象是通过LitePal提供的查询API查询出来的时
      model.isSave()返回true
  2. 新建一个实例,用setter设置要更新的值,调用该实例的updateAll()方法
    Book book = new Book();
    book.setPrice(14.95);
    book.setPress("Anchor"):
    book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown");
    
    • 可以指定一个约束条件,不指定则更新所有数据
    • 不可以用setter设置到默认值,LitePal不会对用setter直接设置成默认值的updateAll()更新
  • setToDefault():设置默认值
    Book book = new Book();
    book.setToDefault("pages");
    book.updateAll();
    

删除数据

  1. 直接调用已存储对象的delete()方法
  2. DataSupport.deleteAll()
    第一个参数用于指定删除那张表中的数据
    后面的参数用于指定约束条件
    DataSupport.deletaAll(Book.class, "price < ?", "15");
    
    • 如果不指定约束条件,默认删除表中所有数据

查询数据

  • 查询表中的所有数据
    List<Book> books = DataSupport.findAll(Book.class);
    
  • 查询表中的第一条数据
    Book firstBook = DataSupport.findFirst(Book.class);
    
  • 查询表中的最后一条数据
    Book LastBook = DataSupport.findLast(Book.class);
    
  • 连缀查询:
    • select()用于指定查询哪几列数据
      List<Book> books = DataSupport.select("name", "author").find(Book.class);
      
    • where()用于指定查询的约束条件
      List<Book> books = DataSupport.where("pages > ?", "400").find(Book.class);
      
    • order()用于指定结果的排序方式
      List<Book> books = DataSupport.order("price desc").find(Book.class);
      
      • desc表示降序,asc或不写表示升序
    • limit()用于指定查询结果的数量 E.g.只查表中的前三条数据
      List<Book> books = DataSupport.limit(3).find(Book.class);
      
    • offset()用于指定查询结果的偏移量 E.g. 查询表中的第2-4条数据
      List<Book> books = DataSupport.limit(3).offset(1).find(Book.class);
      
    • 任意连缀组合
  • 调用DataSupport.findBySQL()进行原生SQL查询
    第一个参数指定SQL语句,后面的参数指定占位符的值
    返回一个Cursor对象
    Cursor c = DataSupport.findBySQL("select * from Book where pages > ? and price < ?", "400", "20");