文件存储
默 认 存 储 到
/data/data/<package name>/files/目录下。
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" /> </RelativeLayout>
public class MainActivity extends Activity { EditText edit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); edit=(EditText) findViewById(R.id.editText1); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); String inputText=edit.getText().toString(); } 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 (Exception e) { // TODO: handle exception e.printStackTrace(); }finally{ try { if (writer!=null) { writer.close(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } }
然后按下 Back 键关闭程序,这时我们输入的内容就已经保存到文件中了。那么如何才
能证实数据确实已经保存成功了呢?我们可以借助 DDMS 的 File Explorer 来查看一下。
切 换 到 DDMS 视 图 , 并 点 击 File Explorer 切 换 卡 , 在 这 里 进 入 到
/data/data/com.example. filepersistencetest/files/目录下, 可以看到生成了一个data
文件
读取数据
public class MainActivity extends Activity { EditText edit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); edit=(EditText) findViewById(R.id.editText1); } 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 (Exception e) { // TODO: handle exception }finally{ if (reader!=null) { try { reader.close(); } catch (Exception e) { // TODO: handle exception } } } return content.toString(); }
在 onCreate()方法中调用 load()方法来读取文件中存储的文本内容,如果读到的内容不为空,就调用 EditText 的 setText()方法将内容填充到 EditText 里, 并调用 setSelection 方法将输入光标移动到文本的末尾位置以便于继续输入,然后弹出一句还原成功的提示。
SharedPreferences 存储
SharedPreferences 文 件 都 是 存 放 在/data/data/<package name>/shared_prefs/目录下
SharedPreferences 是使用键值对的方式来存储数据的,。也就是说当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。 而且 SharedPreferences 还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的,存储的数据是一个
字符串,读取出来的数据仍然是字符串
要想使用 SharedPreferences 来存储数据,首先需要获取到 SharedPreferences对象。Android 中主要提供了三种方法用于得到 SharedPreferences 对象
1.
此方法接收两个参数,第一个参数用于指定 SharedPreferences 文件的名称,如
果 指 定 的 文 件 不 存 在 则 会 创 建 一 个 第二个参数用于指定操作
模式,主要有两种模式可以选择,MODE_PRIVATE 和 MODE_MULTI_PROCESS。
MODE_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()方法将添加的数据提交,从而完成数据存储操作。
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="54dp" android:text="Button" /> </RelativeLayout>
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn=(Button) findViewById(R.id.button1); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub SharedPreferences.Editor editor=getSharedPreferences("data", MODE_PRIVATE).edit(); editor.putString("name", "Tom"); editor.putInt("age", 28); editor.putBoolean("married", false); editor.commit(); } }); }
从 SharedPreferences 中读取数据
btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub SharedPreferences pref=getSharedPreferences("data", MODE_PRIVATE); String name=pref.getString("name", ""); int age=pref.getInt("age", 0); boolean married=pref.getBoolean("married", false); Log.i("MainActivity", "name is"+name); Log.i("MainActivity", "age is " + age); Log.i("MainActivity", "married is " + married); } });
可以看到, 我们在还原数据按钮的点击事件中首先通过 getSharedPreferences()方法得 到 了 SharedPreferences 对 象 , 然 后 分 别 调 用 它 的 getString() 、 getInt() 和getBoolean()方法去获取前面所存储的姓名、年龄和是否已婚,如果没有找到相应的值就会使用方法中传入的默认值来代替
实现记住密码功能
<RelativeLayout 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" tools:context=".MainActivity" > <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" > </EditText> <EditText android:id="@+id/editText2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/editText1" android:layout_marginTop="15dp" /> <CheckBox android:id="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/editText2" android:layout_marginTop="21dp" android:text="记住密码" /> <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignRight="@+id/editText2" android:layout_below="@+id/checkBox1" android:layout_marginTop="28dp" android:text="Button" /> </RelativeLayout>
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final SharedPreferences pref=PreferenceManager.getDefaultSharedPreferences(this); final EditText edit1=(EditText) findViewById(R.id.editText1); final EditText edit2=(EditText) findViewById(R.id.editText2); final CheckBox box=(CheckBox) findViewById(R.id.checkBox1); Button btn=(Button) findViewById(R.id.button1); boolean isRemember=pref.getBoolean("remember_password", false); if (isRemember) { // 将账号和密码都设置到文本框中 String account=pref.getString("account", ""); String passwd=pref.getString("password", ""); edit1.setText(account); edit2.setText(passwd); box.setChecked(true); } btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub String account=edit1.getText().toString(); String passwd=edit2.getText().toString(); if (account.equals("zs")&&passwd.equals("123456")) { SharedPreferences.Editor editor=pref.edit(); if (box.isChecked()) { editor.putBoolean("remember_password", true); editor.putString("account", account); editor.putString("password", passwd); }else { editor.clear(); } editor.commit(); Intent intent=new Intent(MainActivity.this,SecondActivity.class); startActivity(intent); finish(); }else { Toast.makeText(MainActivity.this, "account or password is invalid", Toast.LENGTH_SHORT).show(); } } }); }
SQLite 数据库存储
创建数据库
数 据 库 文 件 会 存 放 在
/data/data/<package name>/databases/目录下
首先你要知道 SQLiteOpenHelper 是一个抽象类, 这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和 onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。SQLiteOpenHelper 中还有两个非常重要的实例方法,getReadableDatabase()和getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库) ,并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而 getWritableDatabase()方法则将出现异这里我们希望创建一个名为 BookStore.db 的数据库,然后在这个数据库中新建一张Book 表,表中有 id(主键) 、作者、价格、页数和书名等列。创建数据库表当然还是需要用建表语句的,这里也是要考验一下你的 SQL 基本功了,Book 表的建表语句如下所示
create table Book ( id integer primary key autoincrement, author text, price real, pages integer, name text)integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类型。另外,上述建表语句中我们还使用了 primary key 将 id 列设为主键,并用 autoincrement 关键字表示 id 列是自增长的。
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)"; Context mcontext; public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); // TODO Auto-generated constructor stub mcontext=context; } @Override public void onCreate(SQLiteDatabase arg0) { // TODO Auto-generated method stub arg0.execSQL(CREATE_BOOK); Toast.makeText(mcontext, "create succeeded", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) { // TODO Auto-generated method stub } }我们把建表语句定义成了一个字符串常量,然后在 onCreate()方法中又调用了 SQLiteDatabase 的 execSQL()方法去执行这条建表语句, 并弹出一个 Toast 提示创建成功,这样就可以保证在数据库创建完成的同时还能成功创建 Book 表,
修改activity_main。
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginTop="39dp" android:text="Create database" /> </RelativeLayout>
public class MainActivity extends Activity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper=new MyDatabaseHelper(this, "bookstore.db", null, 1); Button btn=(Button) findViewById(R.id.button1); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub dbHelper.getWritableDatabase(); } }); }
Create database 按钮时不会再有 Toast 弹出。可是又回到了之前的那个老问题,怎样才能证实它们的确是创建成功了?如果还是使用 File Explorer,那么最多你只能看到
databases 目录下出现了一个 BookStore.db 文件, Book 表是无法通过 File Explorer 看到的。因此这次我们准备换一种查看方式,使用 adb shell 来对数据库和表的创建情进行检查。adb 是 Android SDK 中自带的一个调试工具, 使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在 sdk 的 platform-tools 目录下,如果想要在命令行中使用这个工具,就需要先把它的路径配置到环境变量里。如果你使用的是 Windows 系统,可以右击我的电脑→属性→高级→环境变量,然后在系统变量里找到 Path 并点击编辑,将 platform-tools 目录配置进去
如果你使用的是 Linux 系统,可以在 home 路径下编辑.bash_profile 文件,将platform-tools 目录配置进去即可
配置好了环境变量之后,就可以使用 adb 工具了。打开命令行界面,输入 adb shell,就会进入到设备的控制台
然后使用 cd 命令进行到/data/data/com.example.databasetest/databases/目录下,并使用 ls 命令查看到该目录里的文件
这个目录下出现了两个数据库文件,一个正是我们创建的 BookStore.db,而另一个BookStore.db-journal 则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是 0 字节。接下来我们就要借助 sqlite 命令来打开数据库了,只需要键入 sqlite3,后面加上数据库名即可
这时就已经打开了 BookStore.db 数据库,现在就可以对这个数据库中的表进行管理了。首先来看一下目前数据库中有哪些表,键入.table 命令
可以看到,此时数据库中有两张表,android_metadata 表是每个数据库中都会自动生成的,不用管它,而另外一张 Book 表就是我们在 MyDatabaseHelper 中创建的了还可以通过.schema 命令来查看它们的建表语句
由此证明,BookStore.db 数据库和 Book 表确实已经是创建成功了。之后键入.exit或.quit 命令可以退出数据库的编辑,再键入 exit 命令就可以退出设备控制台了
升级数据库
目前 DatabaseTest 项目中已经有一张 Book 表用于存放书的各种详细数据,如果我们想再添加一张 Category 表用于记录书籍的分类该怎么做呢?
package com.example.database; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; 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)"; public static final String CREATE_CATEGORY="create table Category (" +"id integer primary key autoincrement," +"category_name text," +"category_code integer)"; Context mcontext; public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); // TODO Auto-generated constructor stub mcontext=context; } @Override public void onCreate(SQLiteDatabase arg0) { // TODO Auto-generated method stub arg0.execSQL(CREATE_BOOK); arg0.execSQL(CREATE_CATEGORY); Toast.makeText(mcontext, "create succeeded", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) { // TODO Auto-generated method stub } }
package com.example.database; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; 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)"; public static final String CREATE_CATEGORY = "create table Category (" + "id integer primary key autoincrement," + "category_name text," + "category_code integer)"; Context mcontext; public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); // TODO Auto-generated constructor stub mcontext = context; } @Override public void onCreate(SQLiteDatabase arg0) { // TODO Auto-generated method stub arg0.execSQL(CREATE_BOOK); arg0.execSQL(CREATE_CATEGORY); Toast.makeText(mcontext, "create succeeded", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) { // TODO Auto-generated method stub arg0.execSQL("drop table if exists Book"); arg0.execSQL("drop table if exists Category"); onCreate(arg0); } }
可以看到,我们在 onUpgrade()方法中执行了两条 DROP 语句,如果发现数据库中已经存在 Book 表或 Category 表了,就将这两张表删除掉,然后再调用 onCreate()方法去
重新创建。这里先将已经存在的表删除掉,是因为如果在创建表时发现这张表已经存在了,就会直接报错。接下来的问题就是如何让 onUpgrade()方法能够执行了, 还记得 SQLiteOpenHelper的构造方法里接收的第四个参数吗?它表示当前数据库的版本号,之前我们传入的是 1,现在只要传入一个比 1 大的数,就可以让 onUpgrade()方法得到执行了
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper=new MyDatabaseHelper(this, "bookstore.db", null, 2); Button btn=(Button) findViewById(R.id.button1); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub dbHelper.getWritableDatabase(); } }); }
现在你已经掌握了创建和升级数据库的方法, 接下来就该学习一下如何对表中的数据进行操作了。其实我们可以对数据进行的操作也就无非四种,即 CRUD。其中 C 代表添加
(Create) ,R 代表查询(Retrieve) ,U 代表更新(Update) ,D 代表删除(Delete) 。每一种操作又各自对应了一种 SQL 命令, 如果你比较熟悉 SQL 语言的话, 一定会知道添加数据时使用 insert,查询数据时使用 select,更新数据时使用 update,删除数据时使用delete。
Button btn1=(Button) findViewById(R.id.add); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub SQLiteDatabase db=dbHelper.getWritableDatabase(); ContentValues values=new ContentValues(); //第一条数据 values.put("name", "zs"); values.put("author", "ZZZ"); values.put("pages", 453); values.put("price", 16.96); db.insert("Book", null, values); values.clear(); //第二条数据 values.put("name", "zas"); values.put("author", "AA"); values.put("pages", 42); values.put("price", 16.96); db.insert("Book", null, values); } }); }
在添加数据按钮的点击事件里面,我们先获取到了 SQLiteDatabase 对象,然后使用ContentValues 来对要添加的数据进行组装。如果你比较细心的话应该会发现,这里只对
Book 表里其中四列的数据进行了组装,id 那一列没并没给它赋值。这是因为在前面创建表的时候我们就将 id 列设置为自增长了,它的值会在入库的时候自动生成,所以不需要手动给它赋值了。接下来调用了 insert()方法将数据添加到表当中,注意这里我们实际上添加了两条数据,上述代码中使用 ContentValues 分别组装了两次不同的内容,并调用了两次insert()方法。
打开 BookStore.db 数据库瞧一瞧。输入 SQL 查询语句 select * from Book
更新数据
Button btn1=(Button) findViewById(R.id.add); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub SQLiteDatabase db=dbHelper.getWritableDatabase(); ContentValues values=new ContentValues(); values.put("price", 10.9); db.update("Book", values, "name=?", new String[]{"The Da Vinci Code"}); } }); }
删除数据
deleteButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("Book", "pages > ?", new String[] { "500" }); } });
可以看到, 我们在删除按钮的点击事件里指明去删除 Book 表中的数据, 并且通过第二、第三个参数来指定仅删除那些页数超过 500 页的书籍。当然这个需求很奇怪,这里也仅仅是为了做个测试。你可以先查看一下当前 Book 表里的数据,其中 The Lost Symbol 这本书的页数超过了 500 页,也就是说当我们点击删除按钮时,这条记录应该会被删除掉
查询数据
SQLiteDatabase db = dbHelper.getWritableDatabase(); // 查询Book表中所有的数据 Cursor cursor = db.query("Book", null, null, null, null, null, null); if (cursor.moveToFirst()) { do { // 遍历Cursor对象,取出数据并打印 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")); Log.d("MainActivity", "book name is " + name); Log.d("MainActivity", "book author is " + author); Log.d("MainActivity", "book pages is " + pages); Log.d("MainActivity", "book price is " + price); } while (cursor.moveToNext()); } cursor.close();
的 query()方法非常简单,只是使用了第一个参数指明去查询 Book表,后面的参数全部为 null。这就表示希望查询这张表中的所有数据,虽然这张表中目前只剩下一条数据了。 查询完之后就得到了一个Cursor对象, 接着我们调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。在这个循环中可以通过 Cursor 的 getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。接着我们使用 Log 的方式将取出的数据打印出来,借此来检查一下读取工作有没有成功完成。最后别忘了调用 close()方法来关闭 Cursor。
使用 SQL 操作数据库
添加数据的方法如下:
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" });
db.execSQL("insert into Book (name, author, pages, 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 pages > ?", new String[] { "500" });
查询数据的方法如下:
db.rawQuery("select * from Book", null);
可以看到, 除了查询数据的时候调用的是 SQLiteDatabase 的 rawQuery()方法, 其他
的操作都是调用的 execSQL()方法。以上演示的几种方式,执行结果会和前面几小节中我
们学习的 CRUD 操作的结果完全相同,选择使用哪一种方式就看你个人的喜好了
Button replaceData = (Button) findViewById(R.id.replace_data); replaceData.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.beginTransaction(); // 开启事务 try { db.delete("Book", null, null); if (true) { // 在这里手动抛出一个异常,让事务失败 throw new NullPointerException(); } ContentValues values = new ContentValues(); values.put("name", "Game of Thrones"); values.put("author", "George Martin"); values.put("pages", 720); values.put("price", 20.85); db.insert("Book", null, values); db.setTransactionSuccessful(); // 事务已经执行成功 } catch (Exception e) { e.printStackTrace(); } finally { db.endTransaction(); // 结束事务 } } }); } }
上 述 代 码 就 是 Android 中 事 务 的 标 准 用 法 , 首 先 调 用 SQLiteDatabase 的beginTransaction()方法来开启一个事务,然后在一个异常捕获的代码块中去执行具体的数据库操作,当所有的操作都完成之后,调用 setTransactionSuccessful()表示事务已经执行成功了,最后在 finally 代码块中调用 endTransaction()来结束事务。
在删除旧数据的操作完成后手动抛出了一个 NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。现在可以运行一下程序并点击 Replace data 按钮,你会发现,Book 表中存在的还是之前的旧数据。然后将手动抛出异常的那行代码去除,再重新运行一下程序,此时点击一下
Replace data 按钮就会将 Book 表中的数据替换成新数据了
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch (oldVersion) { case 1: db.execSQL(CREATE_CATEGORY); default: } }onCreate()方法里我们新增了一条建表语句,然后又在 onUpgrade()方法中添加了一个 switch 判断,如果用户当前数据库的版本号是 1,就只会创建一张Category 表。这样当用户是直接安装的第二版的程序时,就会将两张表一起创建。而当用户是使用第二版的程序覆盖安装第一版的程序时,就会进入到升级数据库的操作中,此时由于 Book 表已经存在了,因此只需要创建一张 Category 表即可。但是没过多久,新的需求又来了,这次要给 Book 表和 Category 表之间建立关联,需要在 Book 表中添加一个 category_id 的字段。再次修改 MyDatabaseHelper 中的代码
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch (oldVersion) { case 1: db.execSQL(CREATE_CATEGORY); case 2: db.execSQL("alter table Book add column category_id integer"); default: } }可以看到,首先我们在 Book 表的建表语句中添加了一个 category_id 列,这样当用户直接安装第三版的程序时,这个新增的列就已经自动添加成功了。然而,如果用户之前已