你的分享就是我们的动力 ---﹥

安卓数据持久化:文件储存、SharedPreferences存储以及数据库存储

安卓数据持久化:文件存储、SharedPreferences存储以及数据库存储
Android系统中主要提供了三种方式用于简单的实现数据持久化功能:
文件存储(手机自带的内存)、SharedPreferences存储以及数据库存储
当然还可以用sd卡存储
读入写出
下面是疯狂java讲义中的关于IO流的一些补充,回忆一下
安卓数据持久化:文件储存、SharedPreferences存储以及数据库存储

1,文件存储
手机自带的内存,只能供当前应用程序访问,其他应用程序访问不了,程序卸载这些数据也会随着消失
原理:
基本是先获取一个文件的输出流,然后把信息write进去,最后关闭流
a,通过上下文类context的openFileOutput()方法获得一个FileOutputStream输出流
b,要写入的内容通过write()写到FileOutputStream对象
c,关闭流
openFileOutput()可以将数据存储到指定的文件中,方法有两个参数,第一个参数是文件名,不需要带上完整的路径,因为这里都是默认存储到data/data/file下面的
第二个参数是文件的操作模式,两种:MODE_PRIVATE和MODE_APPEND.前者是覆盖,后者是追加
(MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,前者是允许只读,后者是只写,但是都被抛弃,因为不安全)
private void save(String inputText ) {
           FileOutputStream fos = null;
           BufferedWriter writer = null;
            try {
               fos = openFileOutput( "data", Context.MODE_PRIVATE);
               writer = new BufferedWriter( new OutputStreamWriter(fos));
                writer.write( inputText);
           } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
           } finally {
                 try {
                      if( writer != null)
                            writer.close();
                }catch (IOException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                }
主要的就是上面的三句话
openFileOutput打开一个输出流,然后构建一个字节缓冲流对象然后写数据,

此外还有几个方法需要特别注意一下 ,这几个方法对于文件关系提供了更好的支持,配合上面介绍的方式, 就可以对文件的数据进行常规的CRUD操作 (增删改查),方法如下:
File getFIlesDir ():获取文件系统的绝对路径。
boolean deleteFile(String name):删除一个指定文件名为name的文件。
String[] fileList() :当前应用内部存储路径下的所有文件名。
上一节中的保存数据到手机内存也有一些方法

 //帮助我们返回一个目录
  //context.getCacheDir ()的话就是保存到cache缓存文件夹下面     
File file=new File(context.getFilesDir(), "userinfo.txt");           
       FileOutputStream fos= new FileOutputStream( file);          
            //zhangsan 123
            fos.write(( username+ "##"+ password).getBytes());
            fos.close();

也是可以保存数据的         
---------------------
来看看读数据
读数据也是主要是用到一个打开输入的函数openFileInput
private void read( ) {
           FileInputStream fis= null;
           BufferedReader reader = null;

           StringBuilder sb=new StringBuilder();
            try {
               fis= openFileInput( "data");
             reader= new BufferedReader( new InputStreamWriter(fis));
               String line="";
               while((line=reader.readerLine())!=null){
                   sb.append(line)
                    }
           } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
           } finally {
                 try {
                      if( reader!= null)
                            reader.close();
                }catch (IOException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                }
主界面中
String inputText = load();
if (!TextUtils.isEmpty(inputText)) {
edit.setText(inputText);
edit.setSelection(inputText.length());
Toast.makeText(this, "Restoring succeeded",
Toast.LENGTH_SHORT).show();
}

setSelection()方法将输入光标移动到文本的末尾位置以便再次输入
TextUtils.isEmpty()方法可以一次性进行两种空值的判断,当传入的字符串为空或者是null的时候都会返回true

总的来说文件存储不适合用于保存复杂的数据
------------------------------------------------------------------------------------------------------------------------
2,SharedPreferences存储
首先来看存储数据
SharedPreferences存储是利用键值对的方式来存储的,感觉有点类似map集合
要使用该方法来存储数据就要先获得一个SharedPreferences对象,有三种获取方法
a,Context上下文类中的getSharedPreferences
两个参数,第一个是指定文件的名称,不在就创建。目录也是在data/data/包名/shared_prefs目录下
第二个参数是操作模式。MODE_PRIVATE是默认的,只允许当前的应用程序对其进行操作,MODE_MULTI_PROCESS是一般用于有多个进程中对同一个SharedPreferences文件进行读写的情况,同样有MODE_WORLD_WRITEABLE MODE_WORLD_READABLE两个被废弃的模式
b,Activity类中的getPreferences
只有一个参数,也是操作模式,文件的名称是默认的当前活动的类名
c,PreferenceManager管理类中的getDefaultSharedPreferences()
管理类中的一个静态方法,接收一个context参数,自动把当前的包名作为文件命名的前缀

得到一个对象之后,有三步来进行数据的存储
1,调用对象的edit方法获得一个SharedPreferences.Editor对象
2,向.Editor对象添加数据,putBoolean()或者是putString(),,等等putXXX()方法
3,调用commit方法将添加的数据提交,完成数据的存储操作
看一个实例:
@Override
public void onClick(View v) {
      // TODO Auto-generated method stub
 SharedPreferences.Editor editor=getSharedPreferences( "data", MODE_PRIVATE).edit();
             editor.putString( "name", "hl174");
             editor.putInt( "age", 18);
             editor.putBoolean( "吃饭没", false );
             editor.commit();        
           }
     });
最后看结果
安卓数据持久化:文件储存、SharedPreferences存储以及数据库存储
最后的结果是以xml的方式存在内存中的,外面还是map标签,,呵呵 


SharedPreferences中读取数据
SharedPreferences提供了许多的get方法来进行获取数据,都和相应的put方法对应
get方法也是有两个参数,第一个“键”,第二个是键找不到值时候的默认值,自己设定把
还是继续取出上面存储的xml数据文件的数据 
restore_button .setOnClickListener(new OnClickListener() {           
            @Override
            public void onClick(View v) {
                 // TODO Auto-generated method stub
   SharedPreferences  pref=getSharedPreferences( "data", MODE_PRIVATE);
           
           String name= pref.getString( "name", "");
            int age= pref.getInt( "age", 0);
            boolean lunch= pref.getBoolean( "吃饭没", false );
           
           Log. d("MainActivity" , "名字是:" +name );
           Log. d("MainActivity" , "年龄是:" +age );
           Log. d("MainActivity" , "吃饭没:" +lunch );
           }
     });
     }
结果如下;
安卓数据持久化:文件储存、SharedPreferences存储以及数据库存储
----------------------------------------------------------------------------------------------------------------

3,SQLite数据库

为方便管理数据库有一个专门的抽象类SQLiteOpenHelper,它有两个抽象的方法onCreate()和onUpdate()
此外还有两个实例方法,getReadableDatabase()和getWritableDatabase(),都可以创建或者是打开一个现有的数据库(存在就打开,不在就创建),返回一个可对数据库进行操作的对象
当数据库不可写入的时候,前者返回的是一个只能读的方式打开数据库,后者则会出现异常

数据库文件同样还是会放在data/data/包名/databases下面

一般步骤:
1,构建SQLiteOpenHelper的实例,也就是完成其构造函数(参数一是context,第二个是要创建的数据库的名字,第三个是cursor,一般是null,第四个是数据库的版本号)
2,再调用getReadableDatabase()或者getWritableDatabase()方法创建数据库
同时重写onCreate()方法,该方法也会得到执行

界面的操作就不解释,具体看代码
a,自建立一个MyDatabaseHelper实现抽象类SQLiteOpenHelper,里面有相应的建表数据,oncreate()被执行的时候表也会跟着建立起来 
b,主活动中MyDatabaseHelper构造函数,然后getWritableDatabase()
public class MyDatabaseHelper extends SQLiteOpenHelper {
/*
补充一下建表的一些类型
integer ---整型
real-----浮点类型
text---文本类型
blob---二进制类型

*/
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,
                CursorFactory factory, int version) {
            super( context, name, factory, version);
            // TODO Auto-generated constructor stub
            mContext= context;
     }

     @Override
     public void onCreate(SQLiteDatabase db) {
            // TODO Auto-generated method stub
//执行建表语句
    db.execSQL(CREATE_BOOK);
    Toast.makeText(mContext , "数据库创建成功" , Toast.LENGTH_SHORT).show();
     }

     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // TODO Auto-generated method stub
     }
}

再看看主活动里面,非常简单主要就是  dbHelper. getWritableDatabase();
dbHelper= new MyDatabaseHelper( this, "BookStore.db", null, 1);
     Button createDatabase=(Button) findViewById(R.id.create_database );
     createDatabase.setOnClickListener( new OnClickListener() {
           
            @Override
            public void onClick(View v) {
                 // TODO Auto-generated method stub
                 dbHelper. getWritableDatabase();
           }
     });

-------------------------------------------------------------------------------------------------------------
补充一下abd shell的操作,主要是用来看SQLite数据库的操作
C:\Users\Administrator>adb shell
root@generic_x86:/ # cd /data/data/com.example.databasetest/databases/
cd /data/data/com.example.databasetest/databases/
root@generic_x86:/data/data/com.example.databasetest/databases # ls
ls
BookStore.db
BookStore.db-journal
root@generic_x86:/data/data/com.example.databasetest/databases # sqlite3 BookSto
re.db
BookStore.db                                                                 <
SQLite version 3.7.11 2012-03-20 11:35:50
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .table
.table
android_metadata  book
sqlite> .schema
.schema
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE book(id integer primary key autoincrement,author textprice realpage
s integername text);
sqlite> .exit
.exit
root@generic_x86:/data/data/com.example.databasetest/databases # exit
exit
-----------------------------------------------------------------------------------------------------------------
基本上上面的操作就已经完整的建立了一个表了
但是当要添加一个表的时候,比如这样
public void onCreate(SQLiteDatabase db ) {
            // TODO Auto-generated method stub
            db.execSQL( CREATE_BOOK);
            db.execSQL( CREATE_CATEGORY );
           Toast. makeText( mContext"数据库创建成功" , Toast.LENGTH_SHORT ).show();
     }
会发现不成功
因为数据库BookStore.db已经有了,所以不会再次去执行onCreate()语句
可以在onUpgrade()中添加语句
@Override
      public void onUpgrade(SQLiteDatabase db, int oldVersion, intnewVersion) {
            // TODO Auto-generated method stub
            db.execSQL( "drop table if exists book" );
            db.execSQL( "drop table if exists category" );
           onCreate( db);
     }

然后修改主MainActivity中的版本号,再次执行程序就ok了
-------------------------------------------------
上面完成了数据库的创建和升级,再看CRUD操作
关于CRUD操作,由于之前用到了getWritableDatabase或者是getReadableDatabase方法,这两个方法可以返回一个SQLiteDatabase对象,通过这个对象就可以操作数据库
该对象有一个Insert()方法,接收三个参数:第一个是表名,第二个是给未指定添加数据的情况下某些可为空的列赋null,第三个是ContentValue对象,该对象里面有一系列的get put方法,有点类似于SharedPreferences里面的的构造参数的方法

插入数据:Insert()

public long insert (String table, String nullColumnHack, ContentValues values)

Added in API level 1

Convenience method for inserting a row into the database.

Parameters
table the table to insert the row into
nullColumnHack optional; may be null. SQL doesn't allow inserting a completely empty row without naming at least one column name. If your provided values is empty, no column names are known and an empty row can't be inserted. If not set to null, thenullColumnHack parameter provides the name of nullable column name to explicitly insert a NULL into in the case where your values is empty.
values this map contains the initial column values for the row. The keys should be the column names and the values the column values
Returns
  • the row ID of the newly inserted row, or -1 if an error occurred
Button addData =(Button) findViewById(R.id. add_data);
      addData.setOnClickListener( new OnClickListener() {         
            @Override
            public void onClick(View v) {
                 // TODO Auto-generated method stub
                SQLiteDatabase dbOperate= dbHelper.getWritableDatabase();
                ContentValues values= new ContentValues();
            //下面没有给表中的id赋值,因为在建表的时候,id是默认自动增长的
                 //添加第一条记录到Book
                 values.put( "name", "安卓入门之路" );
                 values.put( "author", "hl174");
                 values.put( "pages", 800);
                 values.put( "price", 50);               
                 dbOperate.insert( "book", null, values);
                
                 values.clear();
                
                 //插入第二条记录到book
                 values.put( "name", "安卓精通" );
                 values.put( "author", "hl174");
                 values.put( "pages", 700);
                 values.put( "price", 45);     
              dbOperate.insert( "book", null, values);
           }
     });

更新数据:
update():四个参数

public int update (String table, ContentValues values, String whereClause, String[]whereArgs)

Added in API level 1

Convenience method for updating rows in the database.

Parameters
table the table to update in
values a map from column names to new column values. null is a valid value that will be translated to NULL.
whereClause the optional WHERE clause to apply when updating. Passing null will update all rows.
whereArgs You may include ?s in the where clause, which will be replaced by the values from whereArgs. The values will be bound as Strings.
Retu rns
  • the number of rows affected
第一个参数:表名  第二个参数:ContentValues对象,
最后两个参数是用来约束更新某一行或几行中的数据,不指定的话就是更新所有行
//更新数据
     Button updateData=(Button) findViewById(R.id. update_data);
      updateData.setOnClickListener( new OnClickListener() {
            @Override
            public void onClick(View v) {
                 // TODO Auto-generated method stub
                SQLiteDatabase db= dbHelper.getWritableDatabase();
                ContentValues values= new ContentValues();
                 values.put( "price", 10000);
                
        db.update( "book", values, "name=?", new String[]{"安卓入门之路" });
           }
     });
上面第三个参数对应的是类似sql中的where语句,?是一个占位符,第四个参数的内容可以替换掉该占位符



删除数据
很明显,删除数据就是delete()方法了

public int delete (String table, String whereClause, String[] whereArgs)

Added in API level 1

Convenience method for deleting rows in the database.

Parameters
table the table to delete from
whereClause the optional WHERE clause to apply when deleting. Passing null will delete all rows.
whereArgs You may include ?s in the where clause, which will be replaced by the values from whereArgs. The values will be bound as Strings.
Returns
  • the number of rows affected if a whereClause is passed in, 0 otherwise. To remove all rows and get a count pass "1" as the whereClause.
三个参数:第一个是表名
第二个第三个同样是进行约束的条件行
//删除数据
     Button deleteData=(Button) findViewById(R.id. delete_data);
      deleteData.setOnClickListener( new OnClickListener() {      
            @Override
            public void onClick(View v) {
                 // TODO Auto-generated method stub
                SQLiteDatabase db= dbHelper.getWritableDatabase();
                 //删除页数大于500的记录
                 db.delete( "book", "pages>?", new String[]{"500" });
           }
     });



查询语句
CRUD中其实用的最多的还是查询语句
同样SQLiteDatabase也提供了query()方法,但是这个方法有好多个参数的类型,看最少的参数为7个

public Cursor query (String table, String[] columns, String selection, String[]selectionArgs, String groupBy, String having, String orderBy)

Added in API level 1

Query the given table, returning a Cursor over the result set.

Parameters
table The table name to compile the query against.
columns A list of which columns to return. Passing null will return all columns, which is discouraged to prevent reading data from storage that isn't going to be used.
selection A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given table.
selectionArgs You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings.
groupBy A filter declaring how to group rows, formatted as an SQL GROUP BY clause (excluding the GROUP BY itself). Passing null will cause the rows to not be grouped.
having A filter declare which row groups to include in the cursor, if row grouping is being used, formatted as an SQL HAVING clause (excluding the HAVING itself). Passing null will cause all row groups to be included, and is required when row grouping is not being used.
orderBy How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered.
Returns
  • Cursor object, which is positioned before the first entry. Note that Cursors are not synchronized, see the documentation for more details.
See Also
  • Cursor
1:表名
2:要查询的是哪几列,不指定的话就是默认查询所有列
3、4:约束某一行或者是某几行的数据,不指定则默认是查询所有的数据
5,groupby指定的列,不指定这项参数就相当于跳过
6,having是对group之后的数据进行过滤
7可以指定查询结果的排序方式

当然调用query之后会返回一个Cursor对象,查询到的数据都将从这个对象中取出
public void onClick(View v ) {
          // TODO Auto-generated method stub
       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" , "书名是:" +name );
         Log. d("MainActivity" , "书的作者是:" +author );
         Log. d("MainActivity" ,"书的页数是:" +pages );
         Log. d("MainActivity" , "书的价钱是:" +price );
          } while( cursor.moveToNext());
         }
     cursor.close();
    }
  });
首先是通过db.query得到cursor对象,在cursor类中我们发现了下面的一些方法,getString,getDouble,getInt等但是都需要传一个参数进去,也就是所在列的下标,从0开始计数
要得到相应列所在的下标同样有方法getColumnIndex(String columnName),这样的话先得到列所在的下标,然后通过该下标得到相应的记录值
安卓数据持久化:文件储存、SharedPreferences存储以及数据库存储


public abstract int getColumnIndex (String columnName)

Added in API level 1

Returns the zero-based index for the given column name, or -1 if the column doesn't exist. If you expect the column to exist use getColumnIndexOrThrow(String) instead, which will make the error more clear.

Parameters
columnName the name of the target column.
Returns
  • the zero-based column index for the given column name, or -1 if the column name does not exist.
See Also
  • getColumnIndexOrThrow(String)

结果如下:
安卓数据持久化:文件储存、SharedPreferences存储以及数据库存储