• Android 数据存储


    一、共享参数

    SharedPreferences是Android的一个轻量级存储工具,它采用的存储结构是Key-Value的键值对方式,存储介质是XML文件,且以XML标记保存键值对。保存共享参数键值对信息的文件路为:/data/data/应用包名/shared_prefs/文件名.xml。
    例如:

    
    <map>
    	<string name="name">Mr Leestring>
    	<int nane="age" value="30"/>
    	<boolean name="married" value="true" />
    	<float name="weight" value="100.0"/>
    map>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    基于XML格式的特点,共享参数主要用于如下场合:
    (1)简单且孤立的数据。若是复杂且相互关联的数据,则要保存于关系数据库。
    (2)文本形式的数据。若是二进制数据,则要保存至文件。
    (3)需要持久化存储的数据。App退出后再次启动时,之前保存的数据仍然有效。

    1.1 共享参数的用法

    共享参数类似map集合,也有get和put方法。
    第一步:获取共享参数实例:

    SharedPreferences preferences = getSharedPreferences("config", Context.MODE_PRIVATE);
    
    • 1

    上面的config就是文件名,如果没有会自动创建,MODE_PRIVATE是私有模式,只有当前app可以访问。
    第二步:写入文件

    //获取编辑器
    SharedPreferences.Editor edit = preferences.edit();
    edit.putString("name",name);
    edit.putInt("age",Integer.parseInt(age));
    edit.putFloat("height",Float.parseFloat(height));
    edit.putFloat("weight",Float.parseFloat(weight));
    edit.putBoolean("married",cb_married.isChecked());
    //提交编辑器中的修改
    edit.commit();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    第三步:读取config文件

    String name = preferences.getString("name", null);
    int age = preferences.getInt("age", 0);
    Float height = preferences.getFloat("height", 0f);
    Float weight = preferences.getFloat("weight", 0f);
    Boolean married = preferences.getBoolean("married", false);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二、数据库

    2.1 数据库管理器

    SQLiteDatabase是Android提供的SQLite数据库管理器,开发者可以在活动页面代码调用openOrCreateDatabase方法获取数据库实例,参考代码如下:

    // 创建名为test.db的数据库。数据库如果不存在就创建它,如果存在就打开它
    SQLiteDatabase db = openOrCreateDatabase(getFilesDir() + "/test.db",Context.MODE_PRIVATE, null);
    String desc = String.format("数据库%s创建%s", db.getPath(), (db!=null)?"成功":"失败");
    tv_database.setText(desc);
    // deleteDatabase(getFilesDir() + "/test.db"); // 删除名为test.db数据库
    
    • 1
    • 2
    • 3
    • 4
    • 5

    获得数据库实例之后,就能对该数据库开展各项操作了。数据库管理器SQLiteDatabase提供了若干操作数据表的API,常用的方法有3类,列举如下:

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

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

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

    • beginTransaction:开始事务。
    • setTransactionSuccessful:设置事务的成功标志。
    • endTransaction:结束事务。执行本方法时,系统会判断之前是否调用了
    • setTransactionSuccessful方法,如果之前已调用该方法就提交事务,如果没有调用该方法就回滚
      事务。

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

    • execSQL:执行拼接好的SQL控制语句。一般用于建表、删表、变更表结构。
    • delete:删除符合条件的记录。
    • update:更新符合条件的记录信息。
    • insert:插入一条记录。
    • query:执行查询操作,并返回结果集的游标。
    • rawQuery:执行拼接好的SQL查询语句,并返回结果集的游标。

    2.2 数据库帮助器

    Android提供了数据库帮助器SQLiteOpenHelper,帮助开发者合理使用SQLite。
    SQLiteOpenHelper的具体使用步骤如下:

    步骤一,新建一个继承自SQLiteOpenHelper的数据库操作类,按提示重写onCreate和onUpgrade两个
    方法。其中,onCreate方法只在第一次打开数据库时执行,在此可以创建表结构;而onUpgrade方法在数据库版本升高时执行,在此可以根据新旧版本号变更表结构。

    步骤二,为保证数据库安全使用,需要封装几个必要方法,包括获取单例对象、打开数据库连接、关闭数据库连接,说明如下:

    • 获取单例对象:确保在App运行过程中数据库只会打开一次,避免重复打开引起错误。
    • 打开数据库连接:SQLite有锁机制,即读锁和写锁的处理;故而数据库连接也分两种,读连接可调用getReadableDatabase方法获得,写连接可调用getWritableDatabase获得。
    • 关闭数据库连接:数据库操作完毕,调用数据库实例的close方法关闭连接。

    查询结果会返回一个游标类Cursor,Cursor的常用方法可分为3类,说明如下:

    1.游标控制类方法,用于指定游标的状态

    • close:关闭游标。
    • isClosed:判断游标是否关闭。
    • isFirst:判断游标是否在开头。
    • isLast:判断游标是否在末尾。

    2.游标移动类方法,把游标移动到指定位置

    • moveToFirst:移动游标到开头。
    • moveToLast:移动游标到末尾。
    • moveToNext:移动游标到下一条记录。
    • moveToPrevious:移动游标到上一条记录。
    • move:往后移动游标若干条记录。
    • moveToPosition:移动游标到指定位置的记录。

    3.获取记录类方法,可获取记录的数量、类型以及取值

    • getCount:获取结果记录的数量。
    • getInt:获取指定字段的整型值。
    • getLong:获取指定字段的长整型值。
    • getFloat:获取指定字段的浮点数值。
    • getString:获取指定字段的字符串值。
    • getType:获取指定字段的字段类型。

    SQLite帮助类

    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 = 2;
        private static UserDBHelper mHelper = null;
        private static SQLiteDatabase mRDB = null;
        private static SQLiteDatabase mWDB = null;
    
        public UserDBHelper(Context context) {
            super(context, DB_NAME, null, DB_VERSION);
        }
        //单例模式
        public static UserDBHelper getInstance(Context context){
            if (mHelper == null) {
                mHelper = new UserDBHelper(context);
            }
            return mHelper;
        }
        //打开数据库的读连接
        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;
            }
        }
        //创建数据库,执行建表语句
        @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," +
                    "height LONG NOT NULL," +
                    "weight FLOAT NOT NULL," +
                    "married INTEGER NOT NULL);";
            db.execSQL(sql);
        }
        //数据库版本号DB_VERSION更新时执行
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            String sql1 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN phone VARCHAR";
            db.execSQL(sql1);
            String sql2 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN password VARCHAR";
            db.execSQL(sql2);
        }
        //插入
        public long insert(User user){
            ContentValues values = new ContentValues();
            values.put("name",user.name);
            values.put("age",user.age);
            values.put("height",user.height);
            values.put("weight",user.weight);
            values.put("married",user.married);
            //执行插入记录动作,该语句返回插入记录的行号
            //如果第三个参数values为Nul1或者元素 个数为0,由 于insert()方法要求必须添加一条除 了主键之外其它字段为Null值的记录,
            //为了满足sQL语法的需要,insert 语句必须给定一个字段名 ,如: insert into person (name) values (NULL)
            // 倘若不给定字段名 ,insert语句就成 J这样: insert into person() values(), 显然这不满足标准SQL,
            //.如果第三个参数values不为Nu11并且元素的个数大于0,可以把第二个参数设置为null。
            try {
                //开始事务
                mWDB.beginTransaction();
                //int num = 10/ 0;
                mWDB.insert(TABLE_NAME,null,values);
                //标识成功,表示前面代码没有报错
                mWDB.setTransactionSuccessful();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //结束事务
                mWDB.endTransaction();
            }
            return 1;
        }
        //删除
        public long deleteByName(String name){
           return mWDB.delete(TABLE_NAME,"name = ?",new String[]{name});
        }
        //更新
        public long update(User user){
            ContentValues values = new ContentValues();
            values.put("name",user.name);
            values.put("age",user.age);
            values.put("height",user.height);
            values.put("weight",user.weight);
            values.put("married",user.married);
            return mWDB.update(TABLE_NAME,values,"name=?",new String[]{user.name});
        }
        //查询所有
        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);
                user.height = cursor.getLong(3);
                user.weight = cursor.getFloat(4);
                //SQLite没有布尔型,用0表示false, 用1表示true
                user.married = cursor.getInt(5)==0?false:true;
                list.add(user);
            }
            return list;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125

    三、存储卡的文件操作

    3.1 私有和公共存储空间

    系统给每个App都分配了默认的私有存储空间。App在私有空间上读写文件无须任何授权,但是若想在公共空间读写文件,则要在AndroidManifest.xml里面添加下述的权限配置。

        
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    • 1
    • 2
    • 3

    上面虽然设置了读写权限,但是app的存储权限设置中仍然是禁止的,要去手动打开。


    既然存储卡分为公共空间和私有空间两部分,它们的空间路径获取也就有所不同。
    若想获取公共空间的存储路径,调用的是Environment.getExternalStoragePublicDirectory方法;
    若想获取应用私有空间的存储路径,调用的是getExternalFilesDir方法。
    下面是分别获取两个空间路径的代码例子:

    // 获取系统的公共存储路径
    String publicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
    // 获取当前App的私有存储路径
    String privatePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
    
    boolean isLegacy = true;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    	// Android10的存储空间默认采取分区方式,此处判断是传统方式还是分区方式
    	isLegacy = Environment.isExternalStorageLegacy();
    }
    String desc = "系统的公共存储路径位于" + publicPath +
    "\n\n当前App的私有存储路径位于" + privatePath +
    "\n\nAndroid7.0之后默认禁止访问公共存储目录" +
    "\n\n当前App的存储空间采取" + (isLegacy?"传统方式":"分区方式");
    tv_path.setText(desc);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.2 读写文本文件

    首先创建一个写文件的工具类,进行封装:

    public class FileUtil {
        //把字符串保存到指定路径的文本文件.
        public static void saveText(String path,String txt){
            BufferedWriter bw = null;
            try{
                bw = new BufferedWriter(new FileWriter(path));
                bw.write(txt);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if (bw != null) {
                    try{
                        bw.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
        //从指定路径的文本文件中读取内容字符串
        public static String openText(String path){
            BufferedReader br = null;
            StringBuilder builder = new StringBuilder();
            try{
                br = new BufferedReader(new FileReader(path));
                String line = null;
                while ((line = br.readLine())!=null){
                    builder.append(line);
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if (br != null) {
                    try{
                        br.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
            return builder.toString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    app的主要java代码:

    //外部存储的公共空间
    directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
    //内部存储的私有空间
    path = directory + File.separatorChar + fileName;
    Log.d("path",path);
    FileUtil.saveText(path,sb.toString());
    ToastUtil.show(this,"保存成功");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3 读写图片文件

    文本文件读写可以转换为对字符串的读写,而图片文件保存的是图像数据,需要专门的位图工Bitmap处理。位图对象依据来源不同又分成3种获取方式,分别对应位图工厂BitmapFactory的下列3种方法:

    • decodeResource:从指定的资源文件中获取位图数据。例如下面代码表示从资源文件huawei.png
      获取位图对象:
      Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.huawei);
      
      • 1
    • decodeFile:从指定路径的文件中获取位图数据。注意从Android 10开始,该方法只适用于私有目录下的图片,不适用公共空间下的图片。
    • decodeStream:从指定的输入流中获取位图数据。比如使用IO流打开图片文件,此时文件输入流
      对象即可作为decodeStream方法的入参,相应的图片读取代码如下:
    // 从指定路径的图片文件中读取位图数据
    public static Bitmap openImage(String path) {
    	Bitmap bitmap = null; // 声明一个位图对象
    	// 根据指定的文件路径构建文件输入流对象
    	try (FileInputStream fis = new FileInputStream(path)) {
    		bitmap = BitmapFactory.decodeStream(fis); // 从文件输入流中解码位图数据
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    	return bitmap; // 返回图片文件中的位图数据
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    得到位图对象之后,就能在图像视图上显示位图。图像视图ImageView提供了下列方法显示各种来源的图片:

    • setImageResource:设置图像视图的图片资源,该方法的入参为资源图片的编号,形如"R.drawable.去掉扩展名的图片名称”。
    • setImageBitmap:设置图像视图的位图对象,该方法的入参为Bitmap类型。
    • setImageURI:设置图像视图的路径对象,该方法的入参为Uri类型。字符串格式的文件路径可通过代码“Uri.parse(file_path)”转换成路径对象。

    读文件的操作很多,但写入文件的方式只有通过位图对象的compress方法将位图数据压缩到文件输出流:

     	//把位图数据保存到指定路径的图片文件
        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 (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    读取图片:

    	//从指定路径的图片文件中读取位图数据
        public static Bitmap readImage(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 (Exception e){
                        e.printStackTrace();
                    }
                }
            }
            return bitmap;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    app代码:

    public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_save:
                    String fileName = System.currentTimeMillis() + ".jpeg";
                    //获取当前App的私有下载目录
                    path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + File.pathSeparator + fileName;
                    Log.d("path",path);
                    //从指定的资源文件中获取位图对象
                    Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
                    //把位图对象保存为图片文件
                    FileUtil.saveImage(path,b1);
                    ToastUtil.show(this,"保存成功");
                    break;
                case R.id.btn_read:
                    //第一种方式读取
                    Bitmap b2 = FileUtil.readImage(path);
                    iv_image.setImageBitmap(b2);
                    //第二种方式读取
                    //Bitmap b3 = BitmapFactory.decodeFile(path);
                    //iv_image.setImageBitmap(b3);
                    //第三种方式:直接调用setImageURI方法,设置图像视图的路径对象
                    //iv_image.setImageURI(Uri.parse(path));
                    break;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    四、应用组件Application

    4.1 Application的生命周期

    Application是Android的一大组件,在App运行过程中有且仅有一个Application对象贯穿应用的整个生
    命周期。打开AndroidManifest.xml,发现activity节点的上级正是application节点,不过该节点并未指
    定name属性,此时App采用默认的Application实例。

    自定义一个类继承Application类:

    public class MyApplication extends Application {
        //单例对象
        private static MyApplication mApp;
        public static MyApplication getInstance(){
            return mApp;
        }
    	//在App启动时调用
        public void onCreate() {
            super.onCreate();
            mApp = this;
        }
    	//在App终止时调用,只用于虚拟环境,在开发设备中默认会删除这个方法,不被回调
        public void onTerminate() {
            super.onTerminate();
            Log.d("MyApplication","onTerminate");
        }
        //在配置改变时调用,例如从竖屏变为横屏。
        public void onConfigurationChanged(@NonNull Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            Log.d("MyApplication","onConfigurationChanged");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    继承Application,继承之后可供重写的方法主要有以下3个:

    • onCreate:在App启动时调用。
    • onTerminate:在App终止时调用(按字面意思)。
    • onConfigurationChanged:在配置改变时调用,例如从竖屏变为横屏。

    修改AndroidManifest.xml,设置application节点的name属性为MyApplication,此时app采用的是我们自己定义的application

    <application
            android:name=".MyApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:requestLegacyExternalStorage="true"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.TestMyApplicaion">
    </application>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.2 利用Application操作全局变量

    适合在Application中保存的全局变量主要有下面3类数据:
    (1)会频繁读取的信息,例如用户名、手机号码等。
    (2)不方便由意图传递的数据,例如位图对象、非字符串类型的集合对象等。
    (3)容易因频繁分配内存而导致内存泄漏的对象,例如Handler处理器实例等。

    由于全局变量操作的是内存,所以速度比较快
    下面演示如何使用全局变量去存储一个人的信息:
    先在MyApplication中定义一个map集合用于存储数据

    public class MyApplication extends Application {
        //单例对象
        private static MyApplication mApp;
        //声明一个公共的信息映射对象,可当作全局变量使用
        public HashMap<String,String> infoMap = new HashMap<>();
        public static MyApplication getInstance(){
            return mApp;
        }
    	//在App启动时调用
        public void onCreate() {
            super.onCreate();
            mApp = this;
        }
    	//在App终止时调用,只用于虚拟环境,在开发设备中默认会删除这个方法,不被回调
        public void onTerminate() {
            super.onTerminate();
            Log.d("MyApplication","onTerminate");
        }
        //在配置改变时调用,例如从竖屏变为横屏。
        public void onConfigurationChanged(@NonNull Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            Log.d("MyApplication","onConfigurationChanged");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    读写操作使用:

    //获取MyApplication实例
    MyApplication app = MyApplication.getInstance();
    //写入数据
    String name = et_name.getText().toString();
    String age = et_age.getText().toString();
    String height = et_height.getText().toString();
    String weight = et_weight.getText().toString();
    //将数据保存到application中
    app.infoMap.put("name",name);
    app.infoMap.put("age",age);
    app.infoMap.put("height",height);
    app.infoMap.put("weight",weight);
    app.infoMap.put("married",cb_married.isChecked()?"是":"否");
    
    
    //从application中获取数据
    String name = app.infoMap.get("name");
    if (name == null){
        return;
    }
    String age = app.infoMap.get("age");
    String height = app.infoMap.get("height");
    String weight = app.infoMap.get("weight");
    String married = app.infoMap.get("married");
    
    et_name.setText(name);
    et_age.setText(age);
    et_height.setText(height);
    et_weight.setText(weight);
    if ("是".equals(married)){
        cb_married.setChecked(true);
    }else{
        cb_married.setChecked(false);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    4.3 利用Room简化数据库操作

    虽然Android提供了数据库帮助器,但是开发者在进行数据库编程时仍有诸多不便,比如每次增加一张
    新表,开发者都得手工实现代码逻辑。

    数据库框架—Room,该框架同样基于SQLite,但它通过注解技术极大地简化了数据库操作,减少了原来相当一部分编码工作量。因为是第三方框架所以要在build.gradle中引入依赖:
    dependencies节点添加下面两行配置,表示导入指定版本的Room库

    implementation 'androidx.room:room-runtime:2.5.2'
    annotationProcessor 'androidx.room:room-compiler:2.5.2'
    
    • 1
    • 2

    导入Room库之后,还要编写若干对应的代码文件。以录入图书信息为例,此时要对图书信息表进行增
    删改查,则具体的编码过程分为下列5个步骤:

    1.编写图书信息表对应的实体类
    假设图书信息类名为BookInfo,且它的各属性与图书信息表的各字段一一对应,那么要给该类添加
    “@Entity”注解,表示该类是Room专用的数据类型,对应的表名称也叫BookInfo。如果BookInfo表的
    name字段是该表的主键,则需给BookInfo类的name属性添加“@PrimaryKey”与“@NonNull”两个注
    解,表示该字段是个非空的主键。

    //书籍信息
    @Entity
    public class BookInfo {
    	@PrimaryKey // 该字段是主键,不能重复
    	@NonNull // 主键必须是非空字段
    	private String name; // 书籍名称
    	private String author; // 作者
    	private String press; // 出版社
    	private double price; // 价格
    	public void setName(String name) {
    		this.name = name;
    	}
    	public String getName() {
    		return this.name;
    	}
    	public void setAuthor(String author) {
    		this.author = author;
    	}
    	public String getAuthor() {
    		return this.author;
    	}
    	public void setPress(String press) {
    		this.press = press;
    	}
    	public String getPress() {
    		return this.press;
    	}
    	public void setPrice(double price) {
    		this.price = price;
    	}
    	public double getPrice() {
    		return this.price;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.编写图书信息表对应的持久化类
    所谓持久化,指的是将数据保存到磁盘而非内存,其实等同于增删改等SQL语句。要在接口上添加@Dao注解

    @Dao
    public interface BookDao {
    	@Query("SELECT * FROM BookInfo") // 设置查询语句
    	List<BookInfo> queryAllBook(); // 加载所有书籍信息
    	@Query("SELECT * FROM BookInfo WHERE name = :name") // 设置带条件的查询语句
    	BookInfo queryBookByName(String name); // 根据名字加载书籍
    	@Insert(onConflict = OnConflictStrategy.REPLACE) // 记录重复时替换原记录
    	void insertOneBook(BookInfo book); // 插入一条书籍信息
    	@Insert
    	void insertBookList(List<BookInfo> bookList); // 插入多条书籍信息
    	@Update(onConflict = OnConflictStrategy.REPLACE)// 出现重复记录时替换原记录
    	int updateBook(BookInfo book); // 更新书籍信息
    	@Delete
    	void deleteBook(BookInfo book); // 删除书籍信息
    	@Query("DELETE FROM BookInfo WHERE 1=1") // 设置删除语句
    	void deleteAllBook(); // 删除所有书籍信息
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.编写图书信息表对应的数据库类
    因为先有数据库然后才有表,所以图书信息表还得放到某个数据库里,这个默认的图书数据库要从
    RoomDatabase派生而来,并添加“@Database”注解。

    //entities表示该数据库有哪些表,version表示数据库的版本号
    //exportSchema表示是否导出数据库信息的json串,建议设为false,若设为true还需指定json文件的保存路径
    @Database(entities = {BookInfo.class},version = 1, exportSchema = false)
    public abstract class BookDatabase extends RoomDatabase {
    	// 获取该数据库中某张表的持久化对象
    	public abstract BookDao bookDao();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.在自定义的Application类中声明图书数据库的唯一实例
    为了避免重复打开数据库造成的内存泄漏问题,每个数据库在App运行过程中理应只有一个实例,此时要求开发者自定义新的Application类,在该类中声明并获取图书数据库的实例,并将自定义的
    Application类设为单例模式,保证App运行之时有且仅有一个应用实例。

    public class MainApplication extends Application {
    	private final static String TAG = "MainApplication";
    	private static MainApplication mApp; // 声明一个当前应用的静态实例
    	// 声明一个公共的信息映射对象,可当作全局变量使用
    	public HashMap<String, String> infoMap = new HashMap<String, String>();
    	private BookDatabase bookDatabase; // 声明一个书籍数据库对象
    	// 利用单例模式获取当前应用的唯一实例
    	public static MainApplication getInstance() {
    		return mApp;
    	}
    	@Override
    	public void onCreate() {
    		super.onCreate();
    		Log.d(TAG, "onCreate");
    		mApp = this; // 在打开应用时对静态的应用实例赋值
    		// 构建书籍数据库的实例
    		bookDatabase = Room.databaseBuilder(mApp, BookDatabase.class,"BookInfo")
    						.addMigrations() // 允许迁移数据库(发生数据库变更时,Room默认删除原数据库再创建新数据库。如此一来原来的记录会丢失,故而要改为迁移方式以便保存原有记录)
    						.allowMainThreadQueries() // 允许在主线程中操作数据库(Room默认不能在主
    						线程中操作数据库)
    						.build();
    	}
    	// 获取书籍数据库的实例
    	public BookDatabase getBookDB(){
    		return bookDatabase;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    5.在操作图书信息表的地方获取数据表的持久化对象

    // 从App实例中获取唯一的图书持久化对象
    BookDao bookDao = MainApplication.getInstance().getBookDB().bookDao();
    
    • 1
    • 2
  • 相关阅读:
    MySQL-数据定义语言-DDLdatebase define language
    多线程核心API和Lock锁的使用
    httpd服务
    【数据结构】—从直接插入排序升级到希尔排序究极详解(含C语言实现)
    ROS 动态参数 事实调参方式
    Mysql创建用户及授权
    你眼中的程序员 vs 程序员眼中的自己,是时候打破刻板印象了丨KubeCon 主题活动
    3. Java并发编程-wait & notify
    Python海洋专题五之水深地形图海岸填充
    基于多智能体混沌鸟群算法的机构优化
  • 原文地址:https://blog.csdn.net/lx00000025/article/details/133575830