android数据存储有三种方式:文件存储,SharedPreference ,数据库。当然还有SD卡存储.
文件存储是一种最基本的存储,文件存储适合存一些简单的数据,文件存储当然也可以存复杂的数据,但是你需要定义自己的一套数据规范。文件存储的文件保存在android系统目录data/data/…..package/file 下。
下面写一个文件存储的小例子:
布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >
<EditText android:id="@+id/file" android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
public class FileData extends Activity{
private EditText file;
private String filetext;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.file_activity);
file=(EditText) findViewById(R.id.file);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
filetext=file.getText().toString();
save(filetext);
}
private void save(String flie){
FileOutputStream outputStream=null;
BufferedWriter writer=null;
try {
outputStream=openFileOutput("filetest",Context.MODE_PRIVATE);
writer=new BufferedWriter(new OutputStreamWriter(outputStream));
try {
writer.write(filetext);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
以上就是写了个IO输入流,在应用销毁 destroy 方法里保存数据,FileOutPutStream返回一个openFileOutput()方法
openFileOutput 参数:第一个参数写入文件名。第二个参数表示文件的操作模式。主要有两种模式可选:MODE_PRIVATE和MODE_APPEND
再使用java流写入文件
下面我们会看到android应用包File文件夹下面多了个文件。
使用SharedPreferences存储数据。这种方式存储数据是以xml文件方式存储的。以键值对方式进行存储的。这种方式显然比文件存储要方便更多。
例子保存用户名密码:
/** * 利用SharedPerences保存用户密码 */
public class SharedPerences extends Activity {
private EditText username,password;
private Button commit;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.shared_activity);
username = (EditText) findViewById(R.id.username);
password = (EditText) findViewById(R.id.password);
commit = (Button) findViewById(R.id.commit);
SharedPreferences mode = getSharedPreferences("data",MODE_PRIVATE);
String name=mode.getString("name","");
String pass=mode.getString("password","");
username.setText(name);
password.setText(pass);
onclick();
}
private void onclick() {
commit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String usernames = username.getText().toString();
String passwords = password.getText().toString();
SharedPreferences.Editor mode = getSharedPreferences("data",MODE_PRIVATE).edit();
mode.putString("name",usernames);
mode.putString("password",passwords);
mode.commit();
Toast.makeText(SharedPerences.this,"登录成功",Toast.LENGTH_SHORT).show();
}
});
}
}
以上程序很简单并没有做很严谨的判断,只是个利用SharedPreferences取值跟存值。在实际开发中我们得根据用户的选择去做更详细的判断。
运行程序:
这是时候我们可以看到应用文件夹下面多了:
打开该文件:
以上介绍的方法很适合轻量级数据存储。
android为我们提供了数据库存储,sqlite数据库。
使用sqlite数据数据库我们首先得创建数据库。
android提供了sqliteOpenHelper这个类来创建管理数据库. sqliteOpenHelper这个个抽象方法。因此必须实现里面的两个方法 以及一个构造函数。
public class sqlite extends sqliteOpenHelper {
private static final String sql = "create table book (" + "id integer primary key autoincrement," + "author text,"
+ "price real," + "pages integer," + "name text)";
public sqlite(Context context,String name,CursorFactory factory,int version) {
super(context,name,factory,version);
}
@Override
public void onCreate(sqliteDatabase db) {
db.execsql(sql);
}
@Override
public void onUpgrade(sqliteDatabase db,int oldVersion,int newVersion) {
}
}
在使用数据库的时候就非常考验我们sql语句的基本工了。
以上例子中我们分别实现了sqliteOpenHelper中的两个方法。和一个构造函数:
onCreate(sqliteDatabase db) 创建一个数据库。参数这个类是核心类,用于管理和操作sqlite数据库
onUpgrade(sqliteDatabase db,int oldVersion,int newVersion)
该方法是更新数据操作,第二个参数是老数据库版本,第三个参数是新数据库版本。
构造函数
第一个参数上下文,第二个参数据库名字,第三个参数允许我们在查询数据的时候返回一个自定义的 Cursor,一般传入null ,第四个参数为数据库的版本。
我们把sql语句写成了一个字符串常量,在onCreate方法中调用sqliteDatabase的 execsql的方法。创建一张名叫book的表。有author,price,pages,name 这些字段。
下面我们来编写activity中的代码。
public class sqlActivity extends Activity{
private sqlite sql;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sqlactivity);
Button button=(Button) findViewById(R.id.sql);
sql=new sqlite(sqlActivity.this,"xBook",null,1);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sql.getWritableDatabase();//创建
}
});
}
}
以上创建了一个名叫xBook的数据库。
我么打开应用包下面database会看到:
我们导到桌面用数据库可视化软件打开,我用的是sqlite Expert Personal 3
可以看到我们已经成功创建了book这张表。里面并没有数据。
我们接下来来看看数据库如何进行升级。
现在要往这个数据库里面添加一张书籍类别的表,就需要进行数据库更新。
sqliteOpenHelper 里面提供了一个onUpgrade()方法,我们可以用它来升级数据库。
public class sqlite extends sqliteOpenHelper {
private static final String sql = "create table book (" + "id integer primary key autoincrement," + "name text)";
private static final String sqlCATE = "create table CateGroy (" + "id integer primary key autoincrement,"
+ "cate_name text," + "cate_code integer)";
private Context context;
public sqlite(Context context,version);
this.context=context;
}
@Override
public void onCreate(sqliteDatabase db) {
db.execsql(sql);
db.execsql(sqlCATE);
Toast.makeText(context,"成功创建数据库",Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(sqliteDatabase db,int newVersion) {
db.execsql("drop table if exists book");
db.execsql("drop table if exists CateGroy");
onCreate(db);
}
}
我们在onUpgrade里面写了两个drop语句,如果数据库存在这两张表,就会删除这两张表,再去执行onCreate。
其实我们也可以不用到onUpgrade里面写更新。只需把APP卸载掉在次进行部署,在点击按钮其实也可以创建成功,但是用卸载app的方式去创建一张表未免显得太极端了。
这里更新还需要注意在之前activity代码里面sqlite的构造函数里面的版本好只需要写上比老版本号大一个数字的版本就行了。
这里我们了解完了如何更新数据库。
我们接下来了解下如何添加数据到数据表
熟悉sql语句的开发者 肯定知道 添加数据 insert 查询:select 更新:update 删除:delete
如果不熟悉,android给我们在sqliteDatabase类中提供了以上方法来操作数据库。
添加数据 我们可以调用sqliteDatabase中的insert方法,
该方法第一个参数表示要添加数据到那张表,
第二个参数表示未指定添加数据的情况下给某些可为空的列自动赋值 NULL
第三个参数是一个 ContentValues对象, 它提供了一系列的 put()
方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加
数据传入即可。
下面来看看代码:
public class sqlActivity extends Activity{
private sqlite sql;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sqlactivity);
Button button=(Button) findViewById(R.id.sql);
Button add_sql=(Button) findViewById(R.id.sql_add);
sql=new sqlite(sqlActivity.this,2);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sql.getWritableDatabase();//创建
}
});
add_sql.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sqliteDatabase database=sql.getWritableDatabase();
ContentValues values=new ContentValues();
values.put("author","Dan");
values.put("price",50);
values.put("name","程序员从入门到改行");
values.put("pages",500);
database.insert("book",values);//插入第一条数据
values.clear();
values.put("author","程序员从入门到住院");
values.put("pages",values);
}
});
}
}
我们在之前的那个activity添加了一个按钮 点击按钮添加数据。
我们利用sql.getWritableDatabase()返回的一个sqliteDatabase的一个对象,
然后ContentValues对象put进去数据,在调用sqliteDatabase对象的insert方法添加数据。
以上一共添加了两条数据。
打开该数据库可以看到book表多了两条数据。
接下来我们来了解下更新数据:
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",2);
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sqliteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price",10.99);
db.update("Book",values,"name = ?",new String[] { "The Da Vinci Code" });
}
});
}
}
删除数据:
sqliteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book","pages > ?",new String[] { "500" });
查询数据:
public ArrayList<CauseInfo> querys(String tableName,String id,String chapter) {
Log.e("DBchapter","" + chapter);
Cursor cursor = mDB.query(tableName,null,BaseColumns.CAT_IDS + "=? " + "AND " + BaseColumns.CHAPTER + "=?",new String[] { id,chapter },null);
boolean hasNext = cursor.moveToFirst();
ArrayList<CauseInfo> mData = new ArrayList<CauseInfo>();
while (hasNext){
String title = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_TITLE));
String optionA = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_ONE));
String optionB = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_TOW));
String optionC = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_THREE));
String optionD = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_FOUR));
String optionE = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_FIVE));
String optionF = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_SIX));
String answer = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_DAAN));
String analy = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_DAAN_DETAIL));
String image = cursor.getString(cursor.getColumnIndex(BaseColumns.COLUMN_TIMU_IMAGE));
int types = cursor.getInt(cursor.getColumnIndex(BaseColumns.COLUMN_TYPES));
int reply = cursor.getInt(cursor.getColumnIndex(BaseColumns.COLUMN_REPLY));
String cateid = cursor.getString(cursor.getColumnIndex(BaseColumns.CAT_IDS));
String chapters = cursor.getString(cursor.getColumnIndex(BaseColumns.CHAPTER));
CauseInfo info = new CauseInfo(types,title,optionA,optionB,optionC,optionD,optionE,optionF,answer,analy,reply,cateid,chapters,image);
mData.add(info);
hasNext = cursor.moveToNext();
}
cursor.close();
return mData;
}
可以看到我们用sqliteDatabase的query方法去查询数据,查询玩之后就得到一个Cursor对象。接着我们调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。
在这个循环中可以通过Cursor 的getColumnIndex()方法获取到某一列在表中对应的位置索引,
然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。
最后调用close方法来关闭Cursor。
Android即使给我们提供了这些API来操作数据库但是个人感觉不太方便,更加青睐于直接使用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,new String[] { "The Lost Symbol","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 db = dbHelper.getWritableDatabase();
db.beginTransaction(); // 开启事务
try {
db.delete("Book",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",values);
db.setTransactionSuccessful(); // 事务已经执行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction(); // 结束事务
}
使用事务可以有效防止在更新数据的过程当中,新数据由于某种原因没有添加进去而把旧数据给删除了。
我们在删除旧数据的操作完成后手动抛出了一个NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。
为了保证数据库中的表是最新的,之前我们只是简单地在onUpgrade()方法中删除掉了当前所有的表,然后强制重新执行了一遍onCreate()方法。这种方式在产品的开发阶段确实可以用,但是当产品真正上线了之后就绝对不行了。想象以下场景,比如你编写的某个应用已经成功上线,并且还拥有了不错的下载量。现在由于添加新功能的原因,使得数据库也需要一起升级,然后用户更新了这个版本之后发现以前程序中存储的本地数据全部丢失了!那么很遗憾,你的用户群体可能已经流失一大半了。
看个例子:
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 MyDatabaseHelper(Context context,CursorFactory
factory,int version) {
super(context,version);
}
@Override
public void onCreate(sqliteDatabase db) {
db.execsql(CREATE_BOOK);
}
@Override
public void onUpgrade(sqliteDatabase db,int newVersion) {
}
}
来个新需求了
public class MyDatabaseHelper extends sqliteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement,"
+ "name text)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement,"
+ "category_name text,"
+ "category_code integer)";
public MyDatabaseHelper(Context context,version);
}
@Override
public void onCreate(sqliteDatabase db) {
db.execsql(CREATE_BOOK);
db.execsql(CREATE_CATEGORY);
}
@Override
public void onUpgrade(sqliteDatabase db,int newVersion) {
switch (oldVersion) {
case 1:
db.execsql(CREATE_CATEGORY);
default:
}
}
}
可以看到我们是在onUpgrade方法里面写了个switch语句来控制我们版本所对应的信息。如果用户当前数据库的版本号是1,就只会创建一张Category 表。这样当用户是直接安装的第二版的程序时,就会将两张表一起创建。而当用户是使用第二版的程序覆盖安装第一版的程序时,就会进入到升级数据库的操作中,此时由于Book 表已经存在了,因此只需要创建一张Category 表即可。
新需求又来了
这回是要往book表里面添加一个字段
public class MyDatabaseHelper extends sqliteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement,"
+ "name text,"
+ "category_id integer)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement,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 列,这样当用户直接安装第三版的程序时,这个新增的列就已经自动添加成功了。 然而,如果用户之前已经安装了某一版本的程序,现在需要覆盖安装,就会进入到升级数据库的操作中。在onUpgrade()方法里,我们添加了一个新的case,如果当前数据库的版本号是2,就会执行alter 命令为Book 表新增一个category_id 列。这里请注意一个非常重要的细节,switch 中每一个case 的最后都是没有使用break 的,为什么要这么做呢?这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么case 2 中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么case 1 和case 2 中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。