阅读前,请浏览此处上方目录。
Android基础知识(6)—数据持久化之数据存储
本章内容为个人笔记,参考书籍有:《疯狂的android》第3版、《第一行代码》
首先,我们要知道什么是数据持久化。
数据持久化就是指那些内存中的瞬时数据保存到存储设备中,保证即使手机在关机的情况下,这些数据不会丢失。保存在内存中的数据是处于瞬时状态,保存在存储设备中的数据是处于持久状态。持久化技术则是提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换。
Android系统主要提供了三种方式用于简单地实现数据持久化功能,即文件存储、SharePreference存储、SQLite数据库存储,最后还有一种就是SD卡存储。
(一)文件存储
文件存储是Android中最基本的一种数据存储方式,他不会对存储的内容进行任何格式处理,所以数据都是原封不动的保存在文件中,因而他适合用于存储一些比较简单的文本或二进制数据。
写入文件:
Context类中提供了一个openFileOutput方法,可以用于将数据存储到指定的文件中,这个方法接收两个参数:第一个参数是文件名;第二个是文件的操作模式;
主要有两种模式:
- MODE_PRIVATE:默认的操作模式,表示当指定同样文件名的时候,所写入的内容会覆盖原有,而且只能被当前程序读写。
- MODE_AOOEND:以追加方式打开该文件,应用程序可以向该文件中追加内容。
FileOutputStream out = openFileOutput(file_name, MODE_PRIVATE); //打开写入文件
接着就可以将字节数据写入文件中:
out.write(str.getBytes());
上述代码使用write()方法将参数的字节数据写入文件,字符串调用getByte()方法转换成字节数组。最后调用close()方法关闭流。
out.close();
读取文件:
读取文件使用FileInputStream对象:
FileInputStream in = openFileInput(file_name); //打开读取文件
上述代码使用继承自Context类的openFileInput()方法打开FileInputStream文件流。参数是文件名字符串,然后我们需要自行建立读取缓存区的byte[]数组以读取文件内容。
byte[] data = new byte[1024]; in.read(data); String str = new String(data);
最后调用close()方法关闭流。
in.close();
【实例】存储和读取文件
布局文件中,EditText和两个按钮,还有TestView,代码就不贴了
主要来看看java文件的代码:
public class FileTest extends Activity { final String file_name = "songsong.txt"; //文件名 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_file_test); } public void Btn_Save(View view) { EditText edit = (EditText) findViewById(R.id.edit); String str = edit.getText().toString(); try { FileOutputStream out = openFileOutput(file_name, MODE_PRIVATE); //打开写入文件 out.write(str.getBytes()); out.close(); Toast.makeText(this, "成功写入", Toast.LENGTH_SHORT).show(); edit.setText(""); } catch (IOException e) { e.printStackTrace(); } } public void Btn_Show(View view) { try { FileInputStream in = openFileInput(file_name); //打开读取文件 byte[] data = new byte[1024]; in.read(data); String str = new String(data); in.close(); Toast.makeText(this, "成功读取", Toast.LENGTH_SHORT).show(); TextView show = (TextView) findViewById(R.id.show); show.setText(str); } catch (IOException e) { e.printStackTrace(); } } }
其实主要的代码都是两个按钮之间的存储、读取。还有捕获异常吧 来看看效果:
(二)使用SharePreferences
SharePreferences是使用键值对的方式来存储数据的,也就是当保存一条数据的时候,需要给这条数据配对一个对应的键,这样在读取的时候就可以根据这个键来取出这个数据。而且SharePreferences还支持多种不同类型的的数据存储,如果数据存储是整型,那么取出来的数据也是整型的。(使用在应用程序的各种配置信息,比如是否打开音效、振动、小游戏积分榜)
如何将数据存储到SharePreferences中:
1、首先需要获得SharePreferences对象。
SharedPreferences preferences = getSharedPreferences("songsong", MODE_PRIVATE);
解释一下,此方法接收会两个参数:
第一个参数:用于指定 SharePreferences文件的名称(将”songsong"命名为文件名),如果指定的文件不存在则会新建一个。
第二个参数:用于指定操作模式 MODE_PRIVATE、MODE_MULTI_PROCESS。
- MODE_PRIVAT:是默认的操作模式,表示只有当前应用程序才可以对这个文件进行读写。
- MODE_MULTI_PROCESS:则是用于多个进程中对同一个文件进行读写。
2、调用SharePreferences对象的edit()方法来获取一个SharePreferences.Editor对象
SharedPreferences.Editor editor = preferences.edit();
3、向SharePreferences对象中添加数据,根据不同类型putXXX。
editor.putString("name", "songsong"); editor.putInt("age", 22); editor.putBoolean("married", false);
4、调用commit()方法将添加了的数据进行提交,从而完成数据存储操作。
editor.commit();
如何从SharePreferences中读取数据:
从SharePreferences文件中读取数据很简单,SharePreferences对象中提供了一系列的get方法用于对存储数据进行读取。每种get方法都对应了之前存储put方法。好比如,我putString,那么就getString。这些get方法接收两个参数,第一个是键,传入存储数据时使用的键就可以获得相应的数据了。第二个参数是默认值,我个人称为后备值,即表示当传入的键找不到对应的值时,就会返回这个后备值。
String name = preferences.getString("name", null); int age = preferences.getInt("age", 0); Boolean married = preferences.getBoolean("married", false);
【实例】接下来演示一个完整的代码示例,布局文件里只定义了两个按钮,一个是读取,另一是写入,十分简单的界面。
public class SharePreferencesTest extends Activity { SharedPreferences preferences; SharedPreferences.Editor editor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share_preferences_test); Button read = (Button) findViewById(R.id.read); Button write = (Button) findViewById(R.id.write); //获取只能被本应用程序读写的SharedPreferences preferences = getSharedPreferences("songsong", MODE_PRIVATE); editor = preferences.edit(); write.setOnClickListener(new View.OnClickListener() { //写入事件 @Override public void onClick(View v) { editor.putString("name", "songsong"); editor.putInt("age", 22); editor.putBoolean("married", false); editor.commit(); } }); read.setOnClickListener(new View.OnClickListener() { //读取事件 @Override public void onClick(View v) { String name = preferences.getString("name", null); int age = preferences.getInt("age", 0); Boolean married = preferences.getBoolean("married", false); Log.d("songsong", "name:" + name); Log.d("songsong", "age:" + age); Log.d("songsong", "married:" + married); } }); } }
运行一下程序,单击写入write按钮,这时数据应该保持成功了。SharePreferences数据总是保存在/data/data/<package name>/shared_prefs目录下
SharePreferences数据总是以XML格式保存。借助File Explorer面板的导出文件,打开该XML文件可以看到如下内容:
<?xml version="1.0" encoding="UTF-8" standalone="true"?> <map> <string name="name">songsong</string> <int name="age" value="22"/> <boolean name="married" value="false"/> </map>
接下来点击读取read按钮,然后查看LogCat中打印的信息:
所有之前存储的数据都成功读取出来,在实际开发中偏好设置功能其实都使用到SharePreferences技术,接下来会做几个实际应用会用到的功能。
-------------
【实例】记录应用程序的使用次数
新建Activity,界面布局就保存最初始化。每次打开应用程序都会显示Toast,而且count的次数都会递增1。
public class UserCount extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_usercount); SharedPreferences preferences = getSharedPreferences("ueercount", MODE_PRIVATE); int count = preferences.getInt("count", 0); //读取sharepreferences里的count数据 Toast.makeText(this, "使用次数:" + count, Toast.LENGTH_SHORT).show(); SharedPreferences.Editor editor = preferences.edit(); editor.putInt("count", ++count); //存入数据 editor.commit(); //提交 } }
【实例】实现记住密码功能
先写一个Login登录界面:(简单为主)
<EditText android:id="@+id/accountEdit" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入账号" /> <EditText android:id="@+id/passwordEdit" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入密码" /> <CheckBox android:id="@+id/cBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="记住密码" /> <Button android:id="@+id/btnlogin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="登录" />
接下来是Java代码
public class LoginActivity extends Activity { SharedPreferences preferences; SharedPreferences.Editor editor; EditText accountEdit, passwordEdit; Button btnlogin; CheckBox cBox; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); accountEdit = (EditText) findViewById(R.id.accountEdit); passwordEdit = (EditText) findViewById(R.id.passwordEdit); btnlogin = (Button) findViewById(R.id.btnlogin); cBox = (CheckBox) findViewById(R.id.cBox); preferences = PreferenceManager.getDefaultSharedPreferences(this); //打开默认的偏好文件 boolean isRemember = preferences.getBoolean("remember_password", false); //读取isRemember if (isRemember) { //将账号密码设置在文本框中 String account = preferences.getString("account", ""); String password = preferences.getString("password", ""); accountEdit.setText(account); passwordEdit.setText(password); cBox.setChecked(true); } btnlogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String account = accountEdit.getText().toString(); String password = passwordEdit.getText().toString(); if (account != "" && password != "") { editor = preferences.edit(); if (cBox.isChecked()) { editor.putString("account", account); editor.putString("password", password); editor.putBoolean("remember_password", true); } else { editor.clear();//清空里面所有数据 } editor.commit(); Intent intent = new Intent(LoginActivity.this, LoginActivity.class); startActivity(intent); finish(); } else Toast.makeText(LoginActivity.this, "请输入账号或密码", Toast.LENGTH_SHORT).show(); } }); } }
先来看看效果:
(三)SQLite数据库存储
创建数据库:
Android为了让我们能够更充分方便地使用管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建和升级。首先我们要知道这个类是一个抽象类,这意味如果我们要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法分别是onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper中还有两个非常重要的实例方法,getReadableDatabase()和getWritableDatabase()。这两个方法共同点是:都可以创建或打开一个现有的数据库(如果数据库不存在则会创建新的),并返回一个可对数据库进行读写操作的对象。不同点是:如果磁盘空间已满,getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将会出现异常。
SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可,这个构造方法中接收四个参数:
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); }
- 第一个参数是Context,这个没什么好说,必须要有它才能对数据库进行操作。
- 第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。
- 第三个参数是允许我们在查询数据时候返回一个自定义的Cursor,一般传入null。
- 第四个参数是表示当前数据库版本号,可用于对数据库进行升级操作。
【实例】创建数据库
创建一个命名为BookStore.db的数据库,然后在这个数据库里面新建一张Book表,表里有id(唯一主键)、作者、价格、页数、书名。
新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下所示:
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, "创建成功", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
布局文件很简单,只有一个按钮,用于创建数据库:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="songsong.com.datatest.SQLiteTest"> <Button android:id="@+id/create_database" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="create database" /> </LinearLayout>
在onCreate方法中构建一个MyDatabaseHelper对象,并通过构建函数的参数将数据库名指定为BookStore.db,版本号指定为1,然后在按钮点击事件里调用了getWritableDatabase方法,当第一次点击按钮时,就会检测到没有BookStore.db这个数据库,于是就去创建该数据库并调用onCreate方法,这样一来Book表也创建成功。
public class SQLiteTest extends Activity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sqlite_test); dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1); //命名为BookStore,版本为1 findViewById(R.id.create_database).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dbHelper.getWritableDatabase();//第一次点击按钮时,就会检查到当前程序中并没有这数据库,接着就调用onCreate } }); } }
升级数据库:
在MyDatabaseHelper中还有 onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 方法是用于升级软件时更新数据库表结构,此方法在数据库的版本发生变化时被调用,oldVersion代表数据库之前版本号,newVersion代表数据库当前版本号。当程序创建SQLiteOpenHelper对象时,第四个参数就必须指定version参数,该参数就决定了使用数据库的版本号。
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
只要某次创建SQLiteOpenHelper时指定的数据库版本号高于之前指定的版本号,系统就会自动触发onUpgrade()方法。(注意:在实际上当应用程序升级表结构时,完全可能因为已有的数据导致升级失败,所以可能需要先对数据进行转储,清空数据表中记录,再更新,再把数据保存回来)
使用insert方法插入数据:
由于开发者水平总是参差不齐,所以在Android中,专门提供了一种辅助性方法,使得在Android中即使不去编写SQL语句也能够轻松完成CRUD操作。SQLiteDatadase提供了一个insert(String table, String nullColumnHack, ContentValues values)方法,这个方法专门用于添加数据,它接收三个参数:
- 第一个参数table:代表想插入数据的表名;
- 第二个参数nullColumnHack:用于在未指定添加数据的情况下给某些可以为空的列自动赋值NULL,一般填NULL即可;
- 第三个参数values:是一个ContentValues对象,提供一系列的put()方法重载,用于向ContentValues中添加数据,只需将表中每个列名对应的内容添加数据传入即可。
【实例】插入数据
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="songsong.com.datatest.SQLiteTest"> ... <Button android:id="@+id/add" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Add" /> </LinearLayout>
添加了个Add按钮,在添加数据Add按钮的点击事件里面,先获取到SQLiteDatabase对象,然后使用ContentValues来对要添加的数据进行组装。因为Book表的id列设置了自动增长,所以只组装了4个值。
findViewById(R.id.add).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", "The one"); values.put("author", "junlin"); values.put("pages", 444); values.put("price", 14.44); db.insert("Book", null, values); values.clear(); values.put("name", "The two"); values.put("author", "songsong"); values.put("pages", 555); values.put("price", 15.55); db.insert("Book", null, values); } });
将数据库导出到桌面,然后用SQLite Database Browser 2.0 b1.exe打开文件。(这个软件非常小而且简单,可以在百度上面搜搜,我也会写一篇文章来介绍这个 我老师传给我的应用)
刚刚添加进去的两条数据都显示出来了,所以我们成功了!
使用update方法更新数据:
SQLiteDatadase提供了一个update方法,update(String table, ContentValues values, String whereClause, String[] whereArgs),这个更新方法的参数说明如下:
- 第一个参数table:代表想更新数据的表名。
- 第二个参数values:代表想更新的数据。
- 第三个参数whereClause:满足该whereClause子句的记录将会被更新,不指定的话就是默认所有行。
- 第四个参数whereArgs:用于为whereClause子句传入参数。
例如,我们想更新书名为“the one”的价格为9.99,可调用如下方法:
首先修改xml文件内容,为其添加个按钮updata:
<Button android:id="@+id/updata_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="updata" />
布局文件的代码十分简单,然后为updata按钮添加事件:
//更新数据 findViewById(R.id.updata).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price", 9.99); db.update("Book", values, "name=?", new String[]{"The one"}); } });
来查看下结果:
使用delete方法删除数据:
SQLiteDatadase提供了一个delete方法,delete(String table, StringwhereClause, String[] whereArgs),这个删除方法的参数说明如下:
- 第一个参数table:代表想更新数据的表名。
- 第二个参数whereClause:满足该whereClause子句的记录将会被删除。
- 第三个参数whereArgs:用于为whereClause子句传入参数。
例如,我们想删除页数超过500页的书籍,可调用如下方法:
在布局文件中添加delete按钮:
<Button android:id="@+id/delete_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="delete" />
为delete按钮添加事件:
//删除数据 findViewById(R.id.delete_data).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("Book", "pages>?", new String[]{"500"}); } });
效果如下:
使用query方法查询数据:
SQLiteDatadase提供了一个query方法,query(boolean distinct, String table, String[]columns, String whereClause, String selectionArgs, String groupBy, String having, String orderBy, String
limit),这个query方法的参数说明如下:
- distinct:指定是否去除重复记录;
- table:执行查询数据的表名;
- columns:要查询出来的列名,相当于select语句select关键字后面的部分。
- whereClause:查询条件子句,相当于select语句where关键字后面的部分,在条件子句中允许使用占位符“?”。
- selectionArgs:用于为whereClause子句中的占位符传入参数值。
- groupBy:用于控制分组。相当于select语句group by关键字后面的部分。
- having:用于对分组进行过滤。
- orderBy:用于对记录进行排序。
- limit:用于进行分页。
我们举个例子:我们想查询the开头的书籍,那么我们就可以这样写:
Cursor cursor = db.query("Book", new String[]{"name,author,price,pages"}, "name like ? ", new String[]{"the%"}, null, null, null);
接下来做个案例,为程序添加一个按钮,并为按钮添加事件:
//查询数据 findViewById(R.id.query_data).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); Cursor cursor = db.query("Book", null, null, null, null, null, null); if (cursor.moveToFirst()) { do { String author = cursor.getString(cursor.getColumnIndex("author")); String price = cursor.getString(cursor.getColumnIndex("price")); String pages = cursor.getString(cursor.getColumnIndex("pages")); String name = cursor.getString(cursor.getColumnIndex("name")); Log.d("SQLiteTest", "name=" + name + ",author=" + author + ",pages=" + pages + ",price=" + price); } while (cursor.moveToNext()); cursor.close(); } } });
当我们点击按钮的时候,调用了SQLiteDatadase的query()方法去查询数据,通过第一个参数book,其他全部为null,表示想查询表里所有数据。
补充说明:Cursor提供了如下方法来移动查询结果的记录指针:
- move(int offset):将记录指针向上或向下移动指定的行数,offset正数就是向下移动,为负数就是向上。
- boolean moveToFirst():将记录指针移动到第一行,如果移动成功则返回true;
- boolean moveToLast():将记录指针移动到最后一行,如果移动成功则返回true;
- boolean moveToNext():将记录指针移动到下一行,如果移动成功则返回true;
- boolean moveToPosition(int position):将记录指针移动到指定行,如果移动成功则返回true;
- boolean moveToPrevious():将记录指针移动到上一行,如果移动成功则返回true;
使用SQL操作数据库:
其实作为一个开发者,无论工作是使用哪种开发语言的,多多少少都会对SQL语句有了解。好比如我,在校的时候学C++,ASP.NET,PHP和Java,都会用到SQL语句,所以很多时候我会比较喜欢用SQL语句来对数据进行操作。而且很多android的书籍也是推荐使用SQL语句进行编程,但是这些辅助性的方法肯定有它存在的价值,作为Android初级开发者的我们,当然也要去了解、学习。
那么接下来也要懂得直接通过SQL来操作数据库,下面就简略演示用SQL来完成之前几个小实例:
插入:
db.execSQL("insert into Book(name,author,pages,price)values(?,?,?,?)", new String[]{"the love", "安德鲁", "777", "29.5"});
更新:
db.execSQL("update Book set price = ? where price > ?", new String[]{"25", "25"});
删除:
db.execSQL("delete from Book where pages > ?", new String[]{"700"});
查询:
db.rawQuery("select * from Book", null);
上面说讲到的内容是十分基础的,真正到了实际开发环境中,就不是那么简单的,如果想真正了解SQL可以在网上搜索更多相关资料。
使用事务:
所谓事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。
- beginTransaction():开始事务
- setTransactionSuccessful():事务已经执行成功,如果没调用此方法,endTransaction()将回滚事务。
- endTransaction():由事务的标志决定是提交事务还是回滚事务。
接下来实例演示,将book表的所有数据删除然后插入一句。如果删除失败,那么插入也会失败。
findViewById(R.id.Transaction_id).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.beginTransaction();//开启事务 try { db.execSQL("delete from Book "); db.execSQL("insert into Book(name,author,pages,price)values(?,?,?,?)", new String[]{"the love", "安德鲁", "777", "29.5"}); db.setTransactionSuccessful();//事务执行完毕 } catch (Exception e) { e.printStackTrace(); } finally { db.endTransaction();//结束事务 } } });
希望本文内容对你有帮助,谢谢!
如果有什么问题可以在栏目下方评论以及交流。