第六章 数据存储全方案,详解持久化技术
文件存储
将数据存储到文件中
使用Context类的openFileOutput()方法:
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(inputText);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
从文件中读取数据
使用Context类的openFileInput()方法:
FileInputStream in = null;
BufferedReader reader = null;
StringBuffer content = new StringBuffer();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
SharedPreferences存储
将数据存储到SharedPreferences中
=====================
1.首先获取SharedPreferences对象,Android中提供三种获取
SharedPreferences对象的方法
a.Context类中的getSharedPreferences()方法:
此方法接收两个参数;第一个是SharedPreferences文件的名称,SharedPreferences文件都是存放在/data/data/<”packagename>/shared_prefs/目录下;
第二个参数用于指定操作模式,MODE_PRIVATE 和 MODE_MULTI_PROCESS。前者是默认的操作模式,和直接传入0效果是一样的,表示只有当前的应用程序才能对这个SharedPreferences文件进行读写。MODE_MULTI_PROCESS则一般用于会有多个进程对同一个SharedPreferences文件进行读写操作
b.Activity类中的getPreferences()方法
与上一个方法类似,不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名
c.PreferenceManager类中的getDefaultSharedPreference()方法
这是一个静态方法,它接收一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件
========================
2.调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象,向SharedPreferences.Editor对象中添加数据,然后调用commit()方法提交数据
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name", "blackpig");
editor.putInt("age", 21);
editor.putBoolean("married", false);
editor.commit();
从SharedPreferences中读取
SharedPreferences对象中提供了一系列的get方法用于从SharedPreferences中读取文件
SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
int age = pref.getInt("age", 0);
boolean married = pref.getBoolean("married", false);
SQLite数据库存储
Android提供了SQLiteOpenHelper帮助类管理数据库
创建数据库
- SQLiteOpenHelper是一个抽象类,需要创建一个自己的帮助类去继承它,并重写onCreate()和onUpgrade()方法
- SQLiteOpenHelper中还有两个非常重要的实例方法。getReadableDatabase()和getWriteableDatabase()。这两个方法都能创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。当数据库不可写入时(如磁盘空间不足)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getwriteableDatabase()方法会出现异常
- SQLiteOpenDatabase中有两个构造方法可供重写,一般使用参数少一点的那个构造方法。该方法接收四个参数:第一个参数是Context;第二个参数是数据库名;第三个参数允许在查询数据的时候返回一个自定义的Cursor,一般都是传入null;第四个参数表示当前数据库版本号,可用于数据库升级操作
构建出SQLiteOpenDatabase实例后,调用getReadableDatabase()或getWriteableDatabase()方法就能创建数据库了,重写的onCreate()方法也会得到执行,所以通常在这里处理一些创建表的逻辑
升级数据库
重写onUpgrade()方法,并修改SQLiteOpenDatabase的构造方法里接收的第四个参数
添加数据
使用SQLiteOpenDatabase的insert()方法,insert()方法接收三个参数,第一个参数是表名,第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到,直接传入null,第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
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);
更新数据
使用SQLiteOpenDatabase中的update()方法,该方法接受四个参数,第一个参数是表名,第二个参数是ContentValues对象,第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] {"The Da Vinci Code"});
删除数据
SQLiteOpenDatabase的delete()方法,其中第一个参数仍然是指表名,第二、第三是用于去约束删除某一行或某几行的数据,不指定就是删除所有行
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] {"500"});
查询数据
SQLiteDatabase的query()方法,该方法有七个参数,第一个参数是表名,第二个参数用于指定查询哪几列,若不指定则默认是查询所有行的数据。第三、第四个参数用于约束查询某一行或某几行的参数,不指定则查询所有行。第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作。第六个参数用于对group by之后的参数进行进一步的过滤,不指定则不过滤。第七个参数用于指定查询结果的排列方式,不指定则表示使用默认的排列方式
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.getInt(cursor
.getColumnIndex("price"));
}while (cursor.moveToNext());
}
cursor.close();
使用事务
SQLite数据库是支持事务的,事务的特性可以保证让一系列的操作要么全部完成,要么一个都不完成
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();//结束事务
}
第7章 夸程序共享数据,探究内容提供器
访问其他程序中的数据
ContentResolver的基本用法
通过Context中的getContentResolver()方法获取ContentResolver类的实例,其中ContentResolver类中也提供了类似SQLiteDatabase中的CRUD方法。ContentResolver类的CRUD方法中不接收表名,而是接收一个Uri参数,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,主要由两部分组成,权限(authority)和路径(path) ,权限一般根据程序包名来命名,如包名是com.example.app,则权限为com.example.app.provider。路径则是用于对同一应用程序中不同的表做区别,如某程序的数据库中存在两张表table1,table2,则路径分别命名为/table1 , /table2,将权限和路径组合后在字符串的头部加上协议声明
content://com.example.app.provider/table1
content://com.example.app.provider/table2
调用Uri.parse()方法可以将内容URI字符串解析成Uri对象
内容URI支持通配符:
其中*表示匹配任意长度的任意字符,#表示匹配任意长度的数字
cursor = getContentResolver()
.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null);
while (cursor.moveToNext()){
//获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
创建自己的内容提供器
1。新建一个类去继承ContentProvider,创建一个自己的内容提供器,并实现ContentProvider类中的六个抽象方法
public class MyProvider extends ContentProvider {
@Override
//通常在这里完成对数据库的创建和升级等操作,返回true表示内容提供器初始化成功,false表示失败
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
return null;
}
@Nullable
@Override
//根据传入的内容URI来返回相应的MIME类型
public String getType(Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
}
@Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
}
@Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
}
借助UriMatcher这个类可以实现匹配内容URI的功能。UriMatcher中提供了一个addURI()方法,该方法分别接受权限、路径和一个自定义代码参数。当调用UriMatcher的match()方法时,可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码可以判断调用方期望访问的是哪张表中的数据
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
private static UriMatcher uriMatcher;
private static final String Authority = "com.diyihangdaima.black.databasetest.provider";
private MyDatabaseHelper dbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
uriMatcher.addURI(Authority, "book", BOOK_DIR);//传入权限、路径、自定义代码
uriMatcher.addURI(Authority, "book/#", BOOK_ITEM);
uriMatcher.addURI(Authority, "category", CATEGORY_DIR);
uriMatcher.addURI(Authority, "category/#", CATEGORY_ITEM);
}
...........
@Override
public Cursor query(@NonNull Uri uri, String[] strings, String s, String[] strings1, String s1) {
//查询数据
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
cursor = db.query("Book", strings, s, strings1, null, null, s1);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("Book", strings, "id = ?", new String[]{bookId}, null, null, s1);
break;
case CATEGORY_DIR:
cursor = db.query("Category", strings, s, strings1, null, null, s1);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("Category", strings,
"id = ?", new String[]{ categoryId }, null, null, s1);
break;
default:
break;
}
return cursor;
}
六个方法中getType()是所有内容提供器必须提供的方法,用于获取Uri对象所对应的MIME类型,一个内容URI对应的MIME字符串主要由三部分组成:
- 必须以vnd开头
- 如果内容URI以路径结尾,则后接
android.cursor.dir/
;如果内容URI以id结尾,则后接android.cursor.item/
- 最后接上
vnd.<authority>.<path>
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.diyihangdaima.black.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.diyihangdaima.black.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.diyihangdaima.black.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item.vnd.com.diyihangdaima.black.databasetest.provider.category";
}
return null;
}
最后在AndroidManifese.xml中注册provider
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<provider android:name="com.diyihangdaima.black.databasetest.DatabaseProvider"
android:authorities="com.diyihangdaima.black.databasetest.provider"
android:exported="true">
</provider>
</application>