Android学习笔记——持久化技术

持久化技术


数据持久化技术简介

数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的,持久化技术则是提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换。 Android系统中主要提供了三种方式用于简单地实现数据持久化功能,文件存储、SharedPreference存储以及数据库存储。当然,除了这三种方式之外,你还可以将数据保存在手机的 SD卡中,不过使用文件、SharedPreference或数据库来保存数据会相对更简单一些,而且比起将数据保存在 SD卡中会更加的安全。

文件存储

文件存储是 Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合用于存储一些简单的文本数据或二进制数据。如果你想使用文件存储的方式来保存一些较为复杂的文本数据,就需要定义一套自己的格式规范,这样方便于之后将数据从文件中重新解析出来。

将数据存储到文件中

Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。这个方法接收两个参数,第一个参数是文件名,在文件创建的时候使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/<packagename>/files/录下。第参数文件操作,主有两模式MODE_PRIVATE MODE_APPEND。其中 MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而 MODE_APPEND则表示如果该文件已存在就往文件里面追加内容,不存在就创建新文件。其实文件的操作模式本来还有另外两种,MODE_WORLD_READABLE MODE_WORLD_WRITEABLE,这两种模式表示允许其他的应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危险,很容易引起应用的安全性漏洞,现已在 Android4.2版本中被废弃。

openFileOutput()方法返回的是一个 FileOutputStream对象,得到了这个对象之后就可以使用 Java流的方式将数据写入到文件中了。

保存:

private EditText edit;

    @Override

    protectedvoidonCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        edit = (EditText)findViewById(R.id.edit);

    }

 

    @Override

    protectedvoid onDestroy() {

        super.onDestroy();

        String inputText = edit.getText().toString();

        save(inputText);

    }

 

    publicvoid save(String inputText) {

        FileOutputStreamout = null;

        BufferedWriterwriter = null;

        try {

            // 通过 openFileOutput()方法能够得到一个对象

            out =openFileOutput("data", Context.MODE_PRIVATE);

            // 借助FileOutputStream构建出一个 OutputStreamWriter对象,

            // 接着再使用 OutputStreamWriter构建出一个 BufferedWriter对象

            writer = newBufferedWriter(new OutputStreamWriter(out));

            writer.write(inputText);

        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            try {

                if (writer != null) {

                    writer.close();

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }


从文件中读取数据

         Context类中还提供了一个 openFileInput()方法,用于从文件中读取数据。这个方法要比 openFileOutput()简单一些,它只接收一个参数,即要读取的文件名,然后系统会自动到/data/data/<packagename>/files/目录下去加载这个文件,并返回一个FileInputStream对象,得到了这个对象之后再通过 Java流的方式就可以将数据读取出来了。

    @Override

    protectedvoidonCreate(BundlesavedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        edit = (EditText)findViewById(R.id.edit);

        //调用 load()方法来读取文件中存储的文本内容

        String inputText = load();

        //如果读到的内容不为空

        if (!TextUtils.isEmpty(inputText)) {

            //调用 EditText的 setText()方法将内容填充到 EditText里

            edit.setText(inputText);

            //调用 setSelection方法将输入光标移动到文本的末尾位置以便于继续输入

            edit.setSelection(inputText.length());

            Toast.makeText(this,"Restoringsucceeded", Toast.LENGTH_SHORT).show();

        }

    }

 

    private String load(){

        FileInputStreamin =null;

        BufferedReaderreader =null;

        StringBuildercontent =newStringBuilder();

        try {

            //通过 openFileInput()方法获取到一个FileInputStream对象

            in =openFileInput("data");

            //借助FileInputStream构建出一个 InputStreamReader对象

            //再使用 InputStreamReader构建出一个 BufferedReader对象

            reader =newBufferedReader(new InputStreamReader(in));

            String line = "";

            //通过 BufferedReader进行一行行地读取,把文件中所有的文本内容全部读取出来并存放在一个StringBuilder对象中

            while ((line =reader.readLine()) !=null) {

                content.append(line);

            }

        } catch (IOExceptione) {

            e.printStackTrace();

        } finally {

            if (reader !=null) {

                try {

                    reader.close();

                } catch (IOExceptione) {

                    e.printStackTrace();

                }

            }

        }

        returncontent.toString();

    }


TextUtils.isEmpty()方法,这是一个非常好用的方法,它可以一次性进行两种空值的判断。当传入的字符串等于 null或者等于空字符串的时候,这个方法都会返回 true,从而使得我们不需要单独去判断这两种空值,再使用逻辑运算符连接起来了。

SharedPreferences存储

         SharedPreferences是使用键值对的方式来存储数据的。也就是说当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。而且 SharedPreferences还支持多种不同的数据类型存储。

将数据存储到 SharedPreferences

要想使用SharedPreferences来存储数据,首先需要获取到SharedPreferences对象。Android中主要提供了三种方法用于得到SharedPreferences对象。

1. Context 类中的getSharedPreferences()方法

此方法接收两个参数,第一个参数用于指定SharedPreferences文件的名称,如果指

定的文件不存在则会创建一个,SharedPreferences文件都是存放在/data/data/<package

name>/shared_prefs/目录下的。第二个参数用于指定操作模式,主要有两种模式可以选

择,MODE_PRIVATEMODE_MULTI_PROCESSMODE_PRIVATE仍然是默认的操

作模式, 和直接传入0效果是相同的, 表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。MODE_MULTI_PROCESS则一般是用于会有多个进程中

对同一个SharedPreferences文件进行读写的情况。类似地,MODE_WORLD_READABLE

MODE_WORLD_WRITEABLE这两种模式已在Android 4.2版本中被废弃。

2. Activity 类中的getPreferences()方法

这个方法和Context中的getSharedPreferences()方法很相似,不过它只接收一个操

作模式参数,因为使用这个方法时会自动将当前活动的类名作为SharedPreferences的文

件名。

3.PreferenceManager类中的getDefaultSharedPreferences()方法

这是一个静态方法,它接收一个Context参数,并自动使用当前应用程序的包名作

为前缀来命名SharedPreferences文件。

得到了SharedPreferences对象之后,就可以开始向SharedPreferences文件中存储数据了,主要可以分为三步实现。

1. 调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象。

2. SharedPreferences.Editor对象中添加数据,比如添加一个布尔型数据就使用putBoolean方法,添加一个字符串则使用putString()方法,以此类推。

3. 调用commit()方法将添加的数据提交,从而完成数据存储操作。

 

@Override

  publicvoid onClick(Viewv) {

    // 通过getSharedPreferences()方法指定SharedPreferences的文件名为data,并得到了SharedPreferences.Editor对象

    SharedPreferences.Editor editor =getSharedPreferences("data",MODE_PRIVATE).edit();

    editor.putString("name","Tom");

    editor.putInt("age", 28);

    editor.putBoolean("married",false);

    // 调用 commit()方法进行提交

    editor.commit();

  }

SharedPreferences中读取数据

SharedPreferences对象中提供了一系列的get方法用于对存储的数据进行读取,每种get方法都对应了SharedPreferences. Editor中的一种 put方法,这些 get方法都接收两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了,第二个参数是默认值,即表示当传入的键找不到对应的值时,会以什么样的默认值进行返回。

   @Override

   publicvoid onClick(Viewv) {

   SharedPreferences pref =getSharedPreferences("data",MODE_PRIVATE);

   String name = pref.getString("name","");

   intage =pref.getInt("age", 0);

   booleanmarried =pref.getBoolean("married",false);

   Log.d("MainActivity","name is" + name);

   Log.d("MainActivity","age is" + age);

   Log.d("MainActivity","marriedis " +married);

   }

        });


SQLite数据库存储

SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百 K的内存就足够了,因而特别适合在移动设备上使用。SQLite不仅支持标准的 SQL语法,还遵循了数据库的 ACID事务, SQLite又比一般的数据库要简单得多,它甚至不用设置用户名和密码就可以使用。Android正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地持久化的功能有了一次质的飞跃。

创建数据库

SQLiteOpenHelper 帮助类可以非常简单地对数据库进行创建和升级。

SQLiteOpenHelper 的基本用法:

QLiteOpenHelper 是一个抽象类,要使用它就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate()onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

SQLiteOpenHelper 中还有两个非常重要的实例方法,getReadableDatabase()getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。

SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数,第一个参数是Context,必须要有它才能对数据库进行操作。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data/<packagename>/databases/目录下。此时,重写的onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。

integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。用primarykey id列设为主键,autoincrement关键字表示 id列是自增长的。

publicclassMyDatabaseHelperextends SQLiteOpenHelper {

 

    publicstaticfinal StringCREATE_BOOK ="createtable Book( " +"id integer primary keyautoincrement,"

            + "authortext," + "price real," +"pagesinteger," +"name text)";

 

    private ContextmContext;

 

    publicMyDatabaseHelper(Contextcontext, String name, CursorFactoryfactory,intversion) {

        super(context,name, factory, version);

        mContext =context;

    }

 

    @Override

    publicvoidonCreate(SQLiteDatabasedb) {

        db.execSQL(CREATE_BOOK);

        Toast.makeText(mContext,"Createsucceeded", Toast.LENGTH_SHORT).show();

    }

 

    @Override

    publicvoidonUpgrade(SQLiteDatabasedb, intoldVersion,intnewVersion) {

 

}


把建表语句定义成了一个字符串常量,然后在 onCreate()方法中调用 SQLiteDatabase execSQL()方法去执行这条建表语句。

 

   privateMyDatabaseHelperdbHelper;

 

    @Override

    protectedvoidonCreate(BundlesavedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //构建一个MyDatabaseHelper对象,并通过构造函数的参数将数据库名指定为BookStore.db,版本号指定为 1

        dbHelper =newMyDatabaseHelper(this, "BookStore.db",null, 1);

        Button createDatabase = (Button)findViewById(R.id.create_database);

        createDatabase.setOnClickListener(newOnClickListener() {

 

            @Override

            publicvoid onClick(Viewv) {

                //调用getWritableDatabase()方法

                dbHelper.getWritableDatabase();

            }

        });

    }

使用 cd命令进行到/data/data/com.example.databasetest/databases/目录下,并使用 ls 命令查看到该目录里的文件

这个目录下出现了两个数据库文件,一个正是我们创建的 BookStore.db,而另一个 BookStore.db-journal则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是 0字节。

只需要键入 sqlite3,后面加上数据库名,这时就已经打开了 BookStore.db数据库

键入.table命令,可以看到,此时数据库中有两张表,android_metadata表是每个数据库中都会自动生成的,而另外一张 Book表就是我们在 MyDatabaseHelper中创建的了。这里还可以通过.schema命令来查看它们的建表语句

升级数据

   

@Override

    publicvoidonUpgrade(SQLiteDatabasedb, intoldVersion,intnewVersion) {

        db.execSQL("droptable if exists Book");

        db.execSQL("droptable if exists Category");

        onCreate(db);

    }

增加版本号以执行onUpgrade

dbHelper = newMyDatabaseHelper(this, "BookStore.db",null, 2);

添加数据

         对数据进行的操作也就无非四种,即 CRUD。其中 C 代表添加Create),R代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。

          insert()方法,这个方法就是专门用于添加数据的。它接收三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般我们用不到这个功能,直接传入 null即可。第三个参数是一个 ContentValues对象,它提供了一系列的 put() 方法重载,用于向 ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。

 

 Button addData = (Button)findViewById(R.id.add_data);

  addData.setOnClickListener(newOnClickListener() {

    @Override

    publicvoid onClick(Viewv) {

    SQLiteDatabase db = dbHelper.getWritableDatabase();

    ContentValues values = newContentValues();

    //开始组装第一条数据

    values.put("name","The DaVinci Code");

    values.put("author","DanBrown");

    values.put("pages", 454);

    values.put("price", 16.96);

    db.insert("Book",null,values); // 插入第一条数据

    values.clear();

    //开始组装第二条数据

    values.put("name","TheLost Symbol");

    values.put("author","DanBrown");

    values.put("pages", 510);

    values.put("price", 19.95);

    db.insert("Book",null,values); // 插入第二条数据

    }

  });

更新数据

update()方法用于对数据进行更新,这个方法接收四个参数,第一个参数和 insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。第二个参数是 ContentValues对象,要把更新数据在这里组装进去。第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。

 

   Button updateData = (Button)findViewById(R.id.update_data);

    updateData.setOnClickListener(newOnClickListener() {

 

    @Override

    publicvoid onClick(Viewv) {

        SQLiteDatabasedb =dbHelper.getWritableDatabase();

        ContentValuesvalues =new ContentValues();

        values.put("price", 10.99);

    //第三个参数对应的是 SQL语句的 where部分,表示去更新所有 name等于?的行

    //而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。

        db.update("Book",values, "name =?", new String[] {"The Da VinciCode" });

    }

    });

删除数据

         delete()方法专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,这个已经没什么好说的了,第二、第三个参数又是用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。

     

 Button deleteButton = (Button)findViewById(R.id.delete_data);

      deleteButton.setOnClickListener(newOnClickListener() {

        @Override

        publicvoid onClick(Viewv) {

            SQLiteDatabasedb =dbHelper.getWritableDatabase();

            db.delete("Book","pages> ?", new String[] { "500" });

            //db.delete("Book", null, null);

        }

      });

查询数据

SQL的全称是 StructuredQueryLanguage,翻译成中文就是结构化查询语言。

query()方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。第一个参数是表名,表示我们希望从哪张表中查询数据。第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数用于指定需要去 groupby的列,不指定则表示不对查询结果进行 groupby操作。第六个参数用于对 groupby之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。

调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。

   //查询数据

   Button queryButton = (Button)findViewById(R.id.query_data);

   queryButton.setOnClickListener(newOnClickListener() {

 

   @Override

   publicvoid onClick(Viewv) {

      SQLiteDatabasedb =dbHelper.getWritableDatabase();

      //查询Book表中所有的数据

      Cursor cursor = db.query("Book",null,null,null,null,null,null);

      //将数据的指针移动到第一行的位置

      if (cursor.moveToFirst()){

        do {

         //遍历Cursor对象,取出数据并打印

         //通过getColumnIndex()方法获取到某一列在表中对应的位置索引

         String name = cursor.getString(cursor.getColumnIndex("name"));

         String author = cursor.getString(cursor.getColumnIndex("author"));

         intpages =cursor.getInt(cursor.getColumnIndex("pages"));

         doubleprice =cursor.getDouble(cursor.getColumnIndex("price"));

         Log.d("MainActivity","bookname is " +name);

         Log.d("MainActivity","bookauthor is " +author);

         Log.d("MainActivity","bookpages is " +pages);

         Log.d("MainActivity","bookprice is " +price);

        } while (cursor.moveToNext());

      }

      cursor.close();

   }

   });

使用SQL操作数据库

直接使用SQL来完成前面几小节中学过的CRUD操作。

添加数据的方法如下:

db.execSQL("insert into Book (name, author, pages, price) values(?,?, ?, ?)",new String[] { "The Da Vinci Code", "DanBrown", "454", "16.96" });

db.execSQL("insert into Book (name, author, pages, price) values(?,?, ?, ?)",new String[] { "The Lost Symbol", "DanBrown", "510", "19.95" });

更新数据的方法如下:

db.execSQL("update Book set price = ? where name = ?", newString[] { "10.99","The Da Vinci Code" });

删除数据的方法如下:

db.execSQL("delete from Book where pages > ?", new String[] {"500" });

查询数据的方法如下:

db.rawQuery("select * from Book", null);

除了查询数据的时候调用的是SQLiteDatabaserawQuery()方法,其他的操作都是调用的execSQL()方法。以上几种方式,执行结果和前面几小节中的CRUD操作的结果完全相同。

使用事务

  SQLite数据库是支持事务的,事务的特性可以保证让某一系列的操作要么全部完成,要么一个都不会完成。

     @Override

     publicvoid onClick(Viewv) {

        SQLiteDatabase db = dbHelper.getWritableDatabase();

        db.beginTransaction();//开启事务

        try {

            db.delete("Book",null,null);

            Log.d("MainActivity","1");

            if (true) {

          //在这里手动抛出一个异常,让事务失败

          Log.d("MainActivity","2");

          thrownewNullPointerException();

            }

            Log.d("MainActivity","3");

            ContentValues values = newContentValues();

            values.put("name","Game ofThrones");

            values.put("author","GeorgeMartin");

            values.put("pages", 720);

            values.put("price", 20.85);

            db.insert("Book",null,values);

            db.setTransactionSuccessful();//事务已经执行成功

        } catch (Exceptione) {

            e.printStackTrace();

            Log.d("MainActivity","4");

        } finally {

            db.endTransaction();//结束事务

            Log.d("MainActivity","5");

        }

     }

        });

数据库数据未更改


注掉if后:


数据库中旧数据删掉,新数据添加

升级数据库的最佳写法

每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入到 onUpgrade()方法中去执行更新操作。

   

@Override

    publicvoidonUpgrade(SQLiteDatabasedb, intoldVersion,intnewVersion) {

        switch (oldVersion) {

        case 1:

            db.execSQL(CREATE_CATEGORY);

        case 2:

            db.execSQL("altertable Book add column category_id integer");

        default:

        }

    }

注意:switch中每一个 case的最后都是没有使用 break的,为什么要这么做呢?这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么 case2中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么 case1 case2中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值