0x07 数据存储

(1)共享参数 SharedPreferences

a. 用法

  • 用法

    • SharedPreferences 是 Android 的一个轻量级存储工具,采用的存储结构为键值对的方式
    • 共享参数的存储介质是符合 XML 规范的配置文件,路径为/data/data/com.example.test/shared_prefs/xxx.xml
  • 使用场景

    • 简单且孤立的数据
    • 文本形式的数据
    • 需要持久化存储的数据

    共享参数经常存储的数据包括 App 的个性化数据、用户的行为信息、临时需要保存的片段信息

举例:登记个人信息

  • // import ...
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.view.View;
    import android.widget.EditText; public class Activity1 extends AppCompatActivity implements View.OnClickListener { private EditText et_name;
    private EditText et_age;
    private SharedPreferences preferences; @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_1);
    et_name = findViewById(R.id.et_name);
    et_age = findViewById(R.id.et_age);
    findViewById(R.id.btn).setOnClickListener(this);
    preferences = getSharedPreferences("preference", Context.MODE_PRIVATE);
    reload();
    } private void reload() { // 加载已经存储的信息
    String name = preferences.getString("name", null);
    if (name != null) {
    et_name.setText(name);
    }
    int age = preferences.getInt("age", 0);
    if (age != 0) {
    et_age.setText(String.valueOf(age));
    }
    } @Override
    public void onClick(View view) {
    String name = et_name.getText().toString();
    String age = et_age.getText().toString();
    SharedPreferences.Editor editor = preferences.edit();
    // 添加信息
    editor.putString("name", name);
    editor.putInt("age", Integer.parseInt(age));
    // 提交信息
    editor.commit();
    }
    }

b. 实现记住密码功能

(2)数据库 SQLite

a. SQLite 基础语法

  1. 数据定义

    1. 创建表格

      CREATE TABLE IF NOT EXISTS user_info (
      _id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
      name VARCHAR NOT NULL,
      age INTEGER NOT NULL
      );
      • 仅单引号包括起来的内容区分大小写
      • 通过IF NOT EXISTS语句避免重复建表
      • SQLite 支持整形INTEGER、长整型LONG、字符串VARCHAR、浮点数FLOAT,不支持布尔类型,如果直接保存布尔数据会被强转为 0 / 1,分别代表 false/true
      • 建表必须有唯一标识字段(主键):_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
    2. 删除表格

      DROP TABLE IF EXISTS user_info;
    3. 修改表结构

      SQLite 只支持增加字段,不支持修改/删除字段,且每次智能添加一列字段

      ALTER TABLE user_info ADD COLUMN age INTEGER;
  2. 数据操纵语言

    1. 添加

      INSERT INTO user_info (name, age) VALUES ('张三', 20);
    2. 删除

      DELETE FROM user_info WHERE name='张三'
    3. 修改

      UPDATE user_info SET age=21 WHERE name
    4. 查询

      SELECT name FROM table WHERE name='张三'
      • 排序输出结果

        SELECT * FROM user_info ORDER BY age ASC;
        • ASC:升序
        • DESC:降序

b. 数据库管理器 SQLiteDatabase

  • SQLiteDatabase 是 SQLite 的数据库管理类,提供了若干操作数据表的 API,包含以下三个类

    1. 数据处理类,用于数据表层面的操作

      • execSQL():执行拼接好的 SQL 控制语句
      • insert():插入一条记录
      • update():更新符合条件的记录
      • delete():删除符合条件的记录
      • query():执行查询操作,返回结果集的游标
      • rawQuery():执行拼接好的 SQL 查询语句,返回结果集的游标

      使用详情见UserDBHelper

    2. 事务类,用于事务层面的操作

      • beginTransaction():开始事务
      • setTransactionSuccessful():设置事务的成功标志
      • endTransaction():结束事务

      以数据处理类中的insert()方法实现为例:

      ContentValues values = new ContentValues();
      values.put("name", user.name);
      values.put("age", user.age);
      try {
      mWDB.beginTransaction();
      mWDB.insert(TABLE_NAME, null, values); // 第一条记录
      mWDB.insert(TABLE_NAME, null, values); // 第二条记录
      mWDB.setTransactionSuccessful();
      } catch (Exception e) {
      e.printStackTrace();
      } finally {
      mWDB.endTransaction();
      }
      return 1;

      当两条记录添加之间出现错误(如:int i = 1 / 0)时,通过添加事务的方法,避免出现仅成功添加第一条记录。事务若未成功,则通过回滚的措施取消本次添加操作

    3. 管理类,用于数据库层面的操作

      • openDatabase():打开指定路径的数据库
      • isOpen():判断数据库是否已打开
      • close():关闭数据库
      • getVersion():获取数据库版本号
      • setVersion():设置数据库版本号

      通过修改UserDBHelper中的onUpgrade()方法

      // ...
      private static final int DB_VERSION = 2; // 数据库版本
      // ...
      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      String sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN c1 VARCHAR;";
      db.execSQL(sql);
      sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN c2 VARCHAR;";
      db.execSQL(sql);
      }
  • 创建/打开数据库

    SQLiteDatabase db = openOrCreateDatabase(getFilesDir() + "/test.db", Context.MODE_PRIVATE, null);
    String str = String.format("数据库%s创建%s", db.getPath(), (db != null)?"成功":"失败");
    tv.setText(str);
  • 删除数据库

    deleteDatabase(getFilesDir() + "/test.db");

c. 数据库帮助器 SQLiteOpenHelper

  • SQLitOpenHelper 是 Android 提供的数据库辅助工具,用于指导合理使用 SQLite,使用方法:

    • 新建一个继承自 SQLiteOpenHelper 的数据库操作类,提示重写onCreate()onUpgrade()
    • 封装保证数据库安全的必要方法
    • 提供对表记录进行增删改查的操作方法
  • 游标
    • 调用 SQLiteDatabase 的query()rawQuery()方法时,返回的都是 Cursor 对象,获取查询结果需要根据游标的指示遍历结果集合,包含以下三个类:

      • 游标控制类,用于指定游标的状态

        • close():关闭游标
        • isClosed():判断游标是否关闭
        • isFirst():判断游标是否在开头
        • isLast():判断游标是否在末尾
      • 游标移动类,用于包游标移动到指定位置
        • move():往后移动若干条记录
        • moveToFirst():移动游标到开头
        • moveToLast():移动游标到末尾
        • moveToNext():移动游标到下一条记录
        • moveToPrevious():移动游标到上一条记录
        • moveToPosition():移动游标到指定位置的记录
      • 获取记录类,用于获取记录的数量、类型、取值
        • getCount():获取记录数量
        • getInt():获取指定字段整型值
        • getLong():获取指定字段长整型值
        • getFloat():获取指定字段浮点数值
        • getString():获取指定字段的字符串值
        • getType():获取指定字段的字段类型

举例:人员信息表的增删改查

  • /database/UserDBHelper.java

    帮助器,包含 SQLite 操作语句与方法

    package com.example.test.database;
    
    import android.content.ContentValues;
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper; import com.example.test.util.User; import java.util.ArrayList;
    import java.util.List; public class UserDBHelper extends SQLiteOpenHelper { private static final String DB_NAME = "user.db"; // 数据库名
    private static final String TABLE_NAME = "user_info"; // 表名
    private static final int DB_VERSION = 1; // 数据库版本
    private static UserDBHelper mHelper = null; //帮助器 private SQLiteDatabase mRDB = null; // 读取数据库
    private SQLiteDatabase mWDB = null; // 写入数据库 private UserDBHelper(Context context) {
    super(context, DB_NAME, null, DB_VERSION);
    } // 打开数据库读取连接
    public SQLiteDatabase openReadLink() {
    if (mRDB == null || !mRDB.isOpen()) {
    mRDB = mHelper.getReadableDatabase();
    }
    return mRDB;
    } // 打开数据库写入连接
    public SQLiteDatabase openWriteLink() {
    if (mWDB == null || !mWDB.isOpen()) {
    mWDB = mHelper.getWritableDatabase();
    }
    return mWDB;
    } // 关闭数据库连接
    public void closeLink() {
    if (mRDB != null && mRDB.isOpen()) {
    mRDB.close();
    mRDB = null;
    }
    if (mWDB != null && mWDB.isOpen()) {
    mWDB.close();
    mWDB = null;
    }
    } // 利用单例模式获取数据库帮助器的唯一实例
    public static UserDBHelper getInstance(Context context) {
    if (mHelper == null) {
    mHelper = new UserDBHelper(context);
    }
    return mHelper;
    } // 数据库创建
    @Override
    public void onCreate(SQLiteDatabase db) {
    String sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
    "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
    " name VARCHAR NOT NULL," +
    " age INTEGER NOT NULL);";
    db.execSQL(sql);
    } // 数据库版本更新
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    } // 增加
    public long insert(User user) {
    ContentValues values = new ContentValues();
    values.put("name", user.name);
    values.put("age", user.age);
    return mWDB.insert(TABLE_NAME, null, values);
    } // 删除
    public long delete(String name) {
    return mWDB.delete(TABLE_NAME, "name=?", new String[]{ name });
    }
    // 清空
    public long deleteAll() {
    return mWDB.delete(TABLE_NAME, "1=1", null);
    } // 修改
    public long update(User user) {
    ContentValues values = new ContentValues();
    values.put("name", user.name);
    values.put("age", user.age);
    return mWDB.update(TABLE_NAME, values, "name=?", new String[]{ user.name });
    } // 查询
    public List<User> query(String name) {
    List<User> list = new ArrayList<>();
    // 执行记录查询行为,返回为结果集的游标
    Cursor cursor = mRDB.query(TABLE_NAME, null, "name=?", new String[]{ name }, null, null, null);
    // 循环取出游标指向的每条记录
    while (cursor.moveToNext()) {
    User user = new User();
    user.id = cursor.getInt(0);
    user.name = cursor.getString(1);
    user.age = cursor.getInt(2);
    list.add(user);
    }
    return list;
    }
    // 列出全部
    public List<User> queryAll() {
    List<User> list = new ArrayList<>();
    Cursor cursor = mRDB.query(TABLE_NAME, null, null, null, null, null, null);
    while (cursor.moveToNext()) {
    User user = new User();
    user.id = cursor.getInt(0);
    user.name = cursor.getString(1);
    user.age = cursor.getInt(2);
    list.add(user);
    }
    return list;
    }
    }
  • /util/User.java

    人员信息类

    package com.example.test.util;
    
    public class User {
    
        public int id;          // 序号
    public String name; // 姓名
    public int age; // 年龄 public User() {} public User(String name, int age) {
    this.name = name;
    this.age = age;
    } @Override
    public String toString() {
    return "User{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }
  • /util/ToastUtil.java

    消息提示

    package com.example.test.util;
    
    import android.content.Context;
    import android.widget.Toast; public class ToastUtil {
    public static void show(Context ctx, String desc) {
    Toast.makeText(ctx, desc, Toast.LENGTH_SHORT).show();
    }
    }
  • MainActivity.java

    主活动

    package com.example.test;
    
    import androidx.appcompat.app.AlertDialog;
    import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.TextView; import com.example.test.database.UserDBHelper;
    import com.example.test.util.ToastUtil;
    import com.example.test.util.User; import java.util.List; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private EditText et_name;
    private EditText et_age;
    private TextView tv;
    UserDBHelper mHelper; @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    et_name = findViewById(R.id.et_name);
    et_age = findViewById(R.id.et_age);
    tv = findViewById(R.id.tv);
    findViewById(R.id.btn_ins).setOnClickListener(this);
    findViewById(R.id.btn_del).setOnClickListener(this);
    findViewById(R.id.btn_upd).setOnClickListener(this);
    findViewById(R.id.btn_sel).setOnClickListener(this);
    findViewById(R.id.btn_sal).setOnClickListener(this);
    findViewById(R.id.btn_dal).setOnClickListener(this);
    } @Override
    protected void onStart() {
    super.onStart();
    // 获取数据库帮助器实例
    mHelper = UserDBHelper.getInstance(this);
    mHelper.openReadLink();
    mHelper.openWriteLink();
    } @Override
    protected void onStop() {
    super.onStop();
    mHelper.closeLink();
    } @Override
    public void onClick(View view) {
    String name = et_name.getText().toString();
    String age = et_age.getText().toString();
    User user = null;
    switch (view.getId()) {
    case R.id.btn_ins:
    user = new User(name, Integer.parseInt(age));
    if (mHelper.insert(user) > 0) {
    ToastUtil.show(this, "添加成功");
    }
    break;
    case R.id.btn_del:
    if (mHelper.delete(name) > 0) {
    ToastUtil.show(this, "删除成功");
    }
    break;
    case R.id.btn_upd:
    user = new User(name, Integer.parseInt(age));
    if (mHelper.update(user) > 0) {
    ToastUtil.show(this, "更新成功");
    }
    break;
    case R.id.btn_sel:
    String str1 = "";
    List<User> list1 = mHelper.query(name);
    for (User u : list1) {
    str1 = str1 + u.toString() + "\n";
    }
    tv.setText(str1);
    break;
    case R.id.btn_sal:
    String str2 = "";
    List<User> list2 = mHelper.queryAll();
    for (User u : list2) {
    str2 = str2 + u.toString() + "\n";
    }
    tv.setText(str2);
    break;
    case R.id.btn_dal:
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("警告");
    builder.setMessage("确认清空?");
    builder.setPositiveButton("确认", (dialogInterface, i) -> {
    if (mHelper.deleteAll() > 0) {
    ToastUtil.show(this, "全部清空");
    }
    });
    builder.setNegativeButton("取消", (dialogInterface, i) -> {});
    AlertDialog alertDialog = builder.create();
    alertDialog.show();
    break;
    }
    return;
    }
    }

(3)存储卡的文件操作

a. 私有存储空间与公共存储空间

  • 存储空间:Android 把外部存储分成了两部分:

    1. 仅应用本身可以访问的私有空间
    2. 所有应用都可以访问的公共空间
  • 获取外部存储空间(存储卡)的路径:
    • 公共空间:Environment 类的getExternalStoragePublicDirectory()方法
    • 私有空间:Environment 类的getExternalFilesDir()方法

b. 在存储卡上读写文本文件

  • 读取

    • /util/FileUtil.java

      import java.io.BufferedReader;
      import java.io.FileReader;
      import java.io.IOException;
      // ...
      public static String openText(String path) {
      BufferedReader is = null;
      StringBuilder sb = new StringBuilder();
      try {
      is = new BufferedReader(new FileReader(path));
      String line = null;
      while ((line = is.readLine()) != null) {
      sb.append(line);
      }
      } catch (Exception e) {
      e.printStackTrace();
      } finally {
      if (is != null) {
      try {
      is.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
      return sb.toString();
      }
    • MainActivity.java

      StringBuilder sb = new StringBuilder();
      sb.append("姓名:").append(name);
      sb.append("年龄:").append(age);
      String fileName = System.currentTimeMillis() + ".txt";
      String directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
      path = directory + File.separatorChar + fileName; // String 类型全局变量
      FileUtil.saveText(path, sb.toString());
      ToastUtil.show(this, "保存成功"); // 消息提示
      1. 外部存储的私有空间

        directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
      2. 外部存储的公共空间

        directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
        // AndroidManifest.xml
        // 开启读写权限
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      3. 内部存储的私有空间

        directory = getFilesDir().toString();
  • 写入

    • /util/FileUtil.java

      import java.io.BufferedWriter;
      import java.io.FileWriter;
      import java.io.IOException;
      // ...
      public static void saveText(String path, String text) { // path:路径, text:文本内容
      BufferedWriter os = null;
      try {
      os = new BufferedWriter(new FileWriter(path));
      os.write(text);
      } catch (Exception e) {
      e.printStackTrace();
      } finally {
      if (os != null) {
      try {
      os.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
      }
    • MainActivity.java

      tv.setText(FileUtil.openText(path));

c. 在存储卡上读写图片文件

  • Android 的位图工具是 Bitmap,App 读写 Bitmap 可以使用性能更好的 BufferedOutputStream 和 BufferedInputStream
  • Android 提供了 BitmapFactory 工具来读取各种来源的图片
    • decodeResource():从资源文件中读取图片信息
    • decodeFile():将指定路径的图片读取到 Bitmap 对象
    • decodeStream():从输入流读取位图数据
  • 读取

    • /util/FileUtil.java

      public static Bitmap openImage(String path) {
      Bitmap bitmap = null;
      FileInputStream fis = null;
      try {
      fis = new FileInputStream(path);
      bitmap = BitmapFactory.decodeStream(fis);
      } catch (Exception e) {
      e.printStackTrace();
      } finally {
      if (fis != null) {
      try {
      fis.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
      return bitmap;
      }
    • MainActivity.java

      1. Bitmap bitmap1 = FileUtil.openImage(path);
        iv.setImageBitmap(bitmap1);
      2. Bitmap bitmap1 = BitmapFactory.decodeFile(path);
        iv.setImageBitmap(bitmap1);
      3. iv.setImageURI(Uri.parse(path));
  • 写入

    • /util/FileUtil.java

      import android.graphics.Bitmap;
      // ...
      public static void saveImage(String path, Bitmap bitmap) {
      FileOutputStream fos = null;
      try {
      fos = new FileOutputStream(path);
      // 把位图数据压缩到文件输出流中
      bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
      } catch (Exception e) {
      e.printStackTrace();
      } finally {
      if (fos != null) {
      try {
      fos.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
      }
    • MainActivity.java

      String fileName = System.currentTimeMillis() + ".jpeg";
      // 获取当前 App 的私有下载目录
      path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + fileName;
      // 从指定的资源文件(./res/drawable/test.jpg)获取位图对象
      Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.test);
      // 把位图对象保存为图片文件
      FileUtil.saveImage(path, bitmap1);
      ToastUtil.show(this, "保存成功");

(4)应用组件 Application

a. 生命周期

  • Application 是 Android 的一大组件,在 App 运行时有且仅有一个 Application 对象贯穿整个生命周期

  • Application 先于 MainActivity 创建

    public class MyApplication extends Application {
    // App 启动时调用
    @Override
    public void onCreate() {
    super.onCreate();
    Log.d("SRIGT", "MyApplication onCreate");
    } // App 终止时调用
    @Override
    public void onTerminate() {
    super.onTerminate();
    Log.d("SRIGT", "onTerminate");
    } // 配置改变时调用
    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    Log.d("SRIGT", "onConfigurationChanged");
    }
    }
    public class MainActivity extends AppCompatActivity {
    
        @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.d("SRIGT", "MainActivity onCreate");
    }
    }
    <application
    android:name=".util.MyApplication">
    <activity
    android:name=".MainActivity"
    android:exported="true">
    </activity>
    </application>

    运行后,Logcat 的输出结果为:

    D/SRIGT: MyApplication onCreate
    D/SRIGT: MainActivity onCreate

b. 利用 Application 操作全局变量

  • 全局的意思是其他代码都可以引用该变量,因此,全局变量是共享数据和消息传递的好帮手

  • 适合在 Application 中保存的全局变量主要有下面 3 类数据:

    • 会频繁读取的信息,如用户名、手机号等
    • 不方便由意图传递的数据,如位图对象、非字符串类型的集合对象等
    • 容易因频繁分配内存而导致内存泄露的对象,如 Handle 对象
    // MyApplication.java
    private static MyApplication mApp; // 声明一个公共的信息映射对象,可当作全局变量使用
    public HashMap<String, String> infoMap = new HashMap<>(); public static MyApplication getInstance() {
    return mApp;
    } // 实例化 mApp
    @Override
    public void onCreate() {
    super.onCreate();
    mApp = this;
    }
    // MainActivity.java
    public class MainActivity extends AppCompatActivity implements View.OnClickListener { private EditText et;
    private MyApplication app = MyApplication.getInstance(); @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    et = findViewById(R.id.et);
    findViewById(R.id.btn).setOnClickListener(this);
    app = MyApplication.getInstance();
    reload();
    } private void reload() {
    String text = app.infoMap.get("Text");
    et.setText(text);
    } @Override
    public void onClick(View view) {
    String text = et.getText().toString();
    if (text == null) {
    return;
    }
    app.infoMap.put("Text", text);
    }
    }

c. 利用 Room 简化数据库操作

  • Room 框架的导入

    • Room 是 Google 公司推出的基于 SQLite的数据库处理框架,其通过注解技术极大简化了数据库操作,减少了原来相当一部分编码工作量。

    • 使用前要在模块的 build.gradle 文件中的dependencies节点添加两行配置以导入指定版本的 Room 库

      implementation 'androidxx.room:room-runtime:2.2.5'
      annotationProcessor 'androidx.room:room-compiler:2.2.5'
  • Room 框架的编码步骤(以录入书籍信息为例,共以下五步)

    1. 编写书籍信息表对应的实体类:@Entity
    2. 编写书籍信息表对应的持久化类:@Dao
    3. 编写书籍信息表对应的数据库类,该类从 RoomDatabase 派生而来:@Database
    4. 在自定义的 Application 类中声明书籍数据库的唯一实例
    5. 在操作书籍信息表的地方获取数据表的持久化对象
  • 实例:

    • 实体类:/enity/BookInfo.java

      package com.example.test.enity;
      
      import androidx.room.Entity;
      import androidx.room.PrimaryKey; @Entity
      public class BookInfo { @PrimaryKey(autoGenerate = true)
      private int id; private String name;
      private String author;
      private String press;
      private double price; // Generate(生成) - Getter and Setter
      public int getId() {
      return id;
      } public void setId(int id) {
      this.id = id;
      } public String getName() {
      return name;
      } public void setName(String name) {
      this.name = name;
      } public String getAuthor() {
      return author;
      } public void setAuthor(String author) {
      this.author = author;
      } public String getPress() {
      return press;
      } public void setPress(String press) {
      this.press = press;
      } public double getPrice() {
      return price;
      } public void setPrice(double price) {
      this.price = price;
      } @Override
      public String toString() {
      return "BookInfo{" +
      "id=" + id +
      ", name='" + name + '\'' +
      ", author='" + author + '\'' +
      ", press='" + press + '\'' +
      ", price=" + price +
      '}';
      }
      }
    • 持久化类:/dao/BookDao.java(接口 Interface)

      package com.example.test.dao;
      
      import androidx.room.Dao;
      import androidx.room.Delete;
      import androidx.room.Insert;
      import androidx.room.Query;
      import androidx.room.Update; import com.example.test.enity.BookInfo; import java.util.List; @Dao
      public interface BookDao { @Insert
      void insert(BookInfo... book); // 删除全部
      @Query("DELETE FROM BookInfo")
      void deleteAll(); // 指定删除项
      @Delete
      void delete(BookInfo... book); @Update
      int update(BookInfo... book);I // 查询所有书籍
      @Query("SELECT * FROM BookInfo")
      List<BookInfo> queryAll(); // 按名称查找书籍
      @Query("SELECT * FROM BookInfo WHERE name = :name ORDER BY id DESC limit 1")
      BookInfo queryByName(String name); }
    • 数据库类:/database/BookDatabase.java

      package com.example.test.database;
      
      import androidx.room.Database;
      import androidx.room.RoomDatabase; import com.example.test.dao.BookDao;
      import com.example.test.enity.BookInfo; @Database(entities = {BookInfo.class}, version = 1, exportSchema = true)
      public abstract class BookDatabase extends RoomDatabase {
      // 获取该数据库中某张表的持久化对象
      public abstract BookDao bookDao();
      }
      • entities:表示该数据库所包含的表

      • version:表示该数据库的版本

      • exportSchema:表示是否导出数据库信息的 json 串,设置为 true 时须在 build.gradle 中指定 json 文件的保存路径,内容如下:

        android {
        // ...
        defaultConfig {
        // ...
        javaCompileOptions {
        annotationProcessorOptions {
        arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
        }
        }
        }
        }
    • 实例化:/util/MyApplication.java

      package com.example.test.util;
      
      import android.app.Application;
      import androidx.room.Room;
      import com.example.test.database.BookDatabase; public class MyApplication extends Application { private static MyApplication mApp;
      // 声明一个书籍数据库对象
      private BookDatabase bookDatabase;
      public static MyApplication getInstance() {
      return mApp;
      }
      // 实例化书籍数据库
      @Override
      public void onCreate() {
      super.onCreate();
      mApp = this;
      // 构建书籍数据库的实例
      bookDatabase = Room.databaseBuilder(this, BookDatabase.class, "book")
      // 允许迁移数据库(发生数据库变更时,Room 会默认删除原数据库再创建新数据库)
      .addMigrations()
      // 允许在主线程中操作数据库(Room 默认不能在主线程中操作数据库)
      .allowMainThreadQueries()
      .build();
      }
      // 获取书籍数据库的实例
      public BookDatabase getBookDatabase() {
      return bookDatabase;
      }
      }
    • 在 MainActivity.java 中使用 Room

      // ...
      private BookDao bookDao; @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      // ...
      // 从 App 实例中获取唯一的书籍持久化对象
      bookDao = MyApplication.getInstance().getBookDatabase().bookDao();
      } @Override
      public void onClick(View view) {
      String name = et_name.getText().toString();
      String author = et_author.getText().toString();
      String press = et_press.getText().toString();
      String price = et_price.getText().toString();
      BookInfo b1 = new BookInfo();
      b1.setName(name);
      b1.setAuthor(author);
      b1.setPress(press);
      b1.setPrice(Double.parseDouble(price));
      switch (view.getId()) {
      case R.id.btn_ins:
      bookDao.insert(b1);
      ToastUtil.show(this, "保存成功");
      break;
      case R.id.btn_del:
      bi.setId(1);
      bookDao.delete(bi);
      ToastUtil.show(this, "删除成功");
      break;
      case R.id.btn_upd:
      BookInfo bTemp = bookDao.queryByName(name);
      bi.setId(bTemp.getId());
      bi.setName(name);
      bi.setAuthor(author);
      bi.setPress(press);
      bi.setPrice(Double.parseDouble(price));
      bookDao.update(bi);
      break;
      case R.id.btn_sel:
      BookInfo bIns = bookDao.queryByName(name);
      Log.d("SRIGT", bIns.toString());
      break;
      case R.id.btn_sho:
      StringBuilder str = new StringBuilder();
      List<BookInfo> list = bookDao.queryAll();
      for (BookInfo b : list) {
      Log.d("SRIGT", b.toString());
      str.append(b).append("\n");
      }
      tv.setText(str.toString());
      break;
      case R.id.btn_cls:
      bookDao.deleteAll();
      break;
      }
      }

-End-

Android 开发入门(5)的更多相关文章

  1. [译]:Xamarin.Android开发入门——Hello,Android Multiscreen深入理解

    原文链接:Hello, Android Multiscreen_DeepDive. 译文链接:Xamarin.Android开发入门--Hello,Android Multiscreen深入理解. 本 ...

  2. [译]:Xamarin.Android开发入门——Hello,Android深入理解

    返回索引目录 原文链接:Hello, Android_DeepDive. 译文链接:Xamarin.Android开发入门--Hello,Android深入理解 本部分介绍利用Xamarin开发And ...

  3. [译]:Xamarin.Android开发入门——Hello,Android快速上手

    返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...

  4. 教我徒弟Android开发入门(一)

    前言: 这个系列的教程是为我徒弟准备的,也适合还不懂java但是想学android开发的小白们~ 本系列是在Android Studio的环境下运行,默认大家的开发环境都是配置好了的 没有配置好的同学 ...

  5. Android开发入门经典【申明:来源于网络】

    Android开发入门经典[申明:来源于网络] 地址:http://wenku.baidu.com/view/6e7634050740be1e650e9a7b.html?re=view

  6. Android开发入门要点记录:四大组件

    cocos2dx跨平台开发中需要了解android开发,昨天快速的浏览了一本Android开发入门教程,因为之前也似懂非懂的写过Activity,Intent,XML文件,还有里面许多控件甚至编程思想 ...

  7. Android开发入门

    教我徒弟Android开发入门(一) 教我徒弟Android开发入门(二) 教我徒弟Android开发入门(三) 出处:http://www.cnblogs.com/kexing/tag/Androi ...

  8. android开发入门经验 ADT Bundle环境搭建

    现在有许多做开发的转做移动端开发,做J2EE的转做Android开发,我也把自己的一些入门经验与大家分享一下,希望能给你带来帮助. 工具/原料 JDK,ADT,JAVA 方法/步骤   开发工具的准备 ...

  9. [Android]Android开发入门之HelloWorld

    引言:在做Unity开发的时候,发现这么个问题,虽然Unity是跨平台的,能够进行Android,IOS,Web,PC等开发,但如果要实现一些稍微系统层的东西,还是需要通过通信,调用原系统的接口(自定 ...

  10. 教我徒弟Android开发入门(二)

    前言: 上一期实现了简单的QQ登录效果,这一期继续对上一期进行扩展 本期的知识点: Toast弹窗,三种方法实现按钮的点击事件监听 正文:   Toast弹窗其实很简单,在Android Studio ...

随机推荐

  1. 接口自动化有多少case?覆盖率是多少?执行完需要多久?

    case根据接口数量而定,比如两百个接口,大概有5000个用例,一个接口大概有25到30个用例,一个接口大概200ms左右响应时间 覆盖率能达到95%以上,有时候可以达到百分之百,所有接口自动化用例执 ...

  2. TLS原理与实践(四)国密TLS

    主页 个人微信公众号:密码应用技术实战 个人博客园首页:https://www.cnblogs.com/informatics/ 引言 TLS作为保证网络通信安全的关键技术和基石被广泛应用,但目前主流 ...

  3. A left join B B表有多条记录,max(create_time)取最新一条

    例如:A表合同表t_contract  B表合同审核表t_contract_audit.两个表根据contract_id关联.且一条合同有多条审核记录.求:A.合同状态.B.最新审核记录结果. 简单: ...

  4. RC4算法:流密码算法的经典之作

    一.RC4算法的起源与演变 RC4算法是由著名密码学家Ron Rivest在1987年设计的一种流密码算法,其名字来源于Rivest Cipher 4.RC4算法简单高效,被广泛应用于数据加密和网络安 ...

  5. 摆脱鼠标系列 - vscode 软件 最大化快捷键 - win + ↑

    摆脱鼠标系列 - vscode 软件 最大化快捷键 - win + ↑ vscode默认打开不是最大化,所以按 win + 上箭头 使其最大化 不想按 F11 那个不太方便,左上角就没有项目名称了 优 ...

  6. 32位数字电位器AD5228使用及调试总结

    一 概念 什么是数字电位计? 数字电位器(Digital Potentiometer)亦称数控可编程电阻器,是一种代替传统机械电位器(模拟电位器)的新型CMOS数字.模拟混合信号处理的集成电路.数字电 ...

  7. cpprestsdk移植到mingw,项目上传至github

    如题 https://github.com/bbqz007/cpprestsdk4mingw 移植过程解决的问题,下面列出其中一些问题: 1. mingw对#pragma once支持不好. 须要在所 ...

  8. [置顶] tomcat处理请求导致页面出现ERR_CONNECTION_RESET错误解决方案

    现象: 浏览器发送请求到servlet,servlet处理时间太久,所以导致chrome浏览器出现ERR_CONNECTION_RESET错误 解决方案: 在相应servlet执行最后添加一句代码: ...

  9. LinuxDNS分析从入门到放弃(记一次有趣的dns问题排查记录,ping 源码分析,getaddrinfo源码分析)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明   ubuntu 18.04 前言   我们这里有一块 ...

  10. Moe RE - 【bugku】

    发现好像没人写wp,虽然很简单但是写一个.... 题目 分析 下载文件打开,习惯首先丢到Exeinfo PE里看看有没有壳 没有壳的样子 那放心丢到IDA(64-bit)里面 一进去就看到很有嫌疑的字 ...