SharedPreferences & SQLite & ContentProvider & File
Android 的四种常用数据存储方式:
- SharedPreferences
- SQLite
- ContentProvider
- File
SharedPreferences:
1, 是一种轻型的数据存储方式
2,本质是基于 XML 文件存储 key-value 键值对数据
3,通常用来存储一些简单的配置信息
- SharedPreferences 对象本身只能获取数据而不支持存储和修改,存储修改时通过 Editor 对象实现。
- 实现 SharedPreferences 存储的步骤如下:
- 获得 SharedPreferences 对象
- 获得 SharedPreferences.Editor 对象
- 通过 Editor 接口的 putXxx 方法保存 key-value 对,其中 Xxx 表示不同的数据类型
- 通过 Editor 接口的 commit 方法保存 key-value 对
SQLite 特点:
- 轻量级 一个动态库、单文件
- 独立性 没有依赖、无需安装
- 隔离性 全部在一个文件夹中
- 跨平台 支持众多操作系统
- 多语言接口 支持众多编程语言
- 安全性 事务
关于事务处理的安全性问题:
-通过数据库上的独占性和共享锁来实现独立事务处理
-多个进程可以在同一时间从同一数据库读取数据,但只有一个可以写入数据
SQLite 支持的数据类型:
-NULL、INTEGER、REAL、TEXT、BLOB
-空值、整型值、浮点值、字符串值、二进制对象
动态数据类型 (弱引用)
-当某个值插入到数据库时,SQLite 将会检查它的类型,如果该类型与关联的列不匹配,SQLite 则会尝试将该值转换成该列的类型,如果不能转换,则该值将作为本身的类型存储。
使用须知:
-由于资源占用少,性能良好和零管理成本,嵌入式数据库有了它的用武之地。
例如 Android,IOS
-没有可用于SQLite 的网络服务器,只能通过网络共享可能存在文件锁定或者性能问题
-只提供数据库级的锁定
-没有用户账户概念,而是根据文件系统确定所有数据库的权限。
SQLiteDatabase:
-提供了一些管理 SQLite 数据库的类
-提供创建,删除,执行 SQL 命令,并执行其他常见的数据库管理任务的方法
-每个程序的数据库名字是唯一的
.db.execSQL(sql) // 执行任何 SQL 语句
.db.insert(table, nullColumnHack, values)
.db.delete(table, whereClause, whereArgs)
.db.update(table, values, whereClause, whereArgs)
.db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy)
.db.rawQuery(sql, selectionArgs)
Cursor 是 Android 查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证 Cursor 最终会被释放掉。
然而如果 Cursor 的数据量特别大,特别是如果里面有 Blob 信息时,应该保证 Cursor 占用的内存被及时的释放掉,而不是等待 GC 来处理。而且 Android 明显是倾向于编程者手动的将 Cursor close 掉, 因为在源代码中我们发现,如果等到垃圾回收器来回收时,也就是如果不手动关闭,系统会报错,会给用户以错误提示。
Cursor: 游标接口,提供了遍历查询结果的方法,如移动指针方法 move(), 获得列值方法 getString() 等。 常用方法:
- getCount() 总记录条数
- isFirst() 判断是否第一条记录
- isLast() 判断是否最后一条记录
- moveToFirst() 移动到第一条记录
- moveToLast() 移动到最后一条记录
- move(int offset) 移动到指定记录
- moveToNext() 移动到下一条记录
- moveToPrevious() 移动到上一条记录
- getColumnIndex (String columnName)
- getColumnIndexOrThrow(String columnName)据列名称获得列索引
- getInt(int columnIndex) 获得指定列索引的 int 类型值
- getString(int columnIndex) 获得指定列索引的 String 类型值
SQLiteDatabase db = openOrCreateDatabase("user.db", MODE_PRIVATE, null); db.execSQL("create table if not exists usertb(_id integer primary key autoincrement, name text not null, age integer not null, sex text not null)"); db.execSQL("insert into usertb(name, sex, age) values('zhangsan', 'nv', 18)"); db.execSQL("insert into usertb(name, sex, age) values('lisi', 'nv', 19)"); db.execSQL("insert into usertb(name, sex, age) values('wangwu', 'nan', 20)"); Cursor cursor = db.rawQuery("select * from usertb", null); if(cursor != null){ while(cursor.moveToNext()){ Log.i("yiusanfendi", "**********************"); Log.i("yiusanfendi", "_id:" + cursor.getInt(cursor.getColumnIndex("_id"))); Log.i("yiusanfendi", "name:" + cursor.getString(cursor.getColumnIndex("name"))); Log.i("yiusanfendi", "age:" + cursor.getInt(cursor.getColumnIndex("age"))); Log.i("yiusanfendi", "sex:" + cursor.getString(cursor.getColumnIndex("sex"))); } Log.i("Chunlei", "**********************"); cursor.close(); } db.close(); |
ContentValues:
用来存储一组可以被 ContentResolver 处理的值。
ContentValues values = new ContentValues(); // 类似 HashMap key value
value.put(“name”, “张三”);
执行对应的 Sql 操作
SQLiteDatabase db = openOrCreateDatabase("stu.db", MODE_PRIVATE, null); db.execSQL("create table if not exists stutb(_id integer primary key autoincrement, name text not null, sex text not null, age integer not null)"); ContentValues values = new ContentValues(); values.put("name","张三"); values.put("sex","男"); values.put("age","19"); long rowId = db.insert("stutb", null, values); values.clear(); values.put("name", "张三丰"); values.put("sex", "男"); values.put("age", 99); db.insert("stutb", null, values); values.clear(); values.put("name", "张三疯"); values.put("sex", "男"); values.put("age", 59); db.insert("stutb", null, values); values.clear(); values.put("name", "张三峰"); values.put("sex", "男"); values.put("age", 39); db.insert("stutb", null, values); values.clear(); values.put("name", "张三封"); values.put("sex", "男"); values.put("age", 29); db.insert("stutb", null, values); values.clear(); values.put("sex", "女"); // 将全部 id > 3 的人的性别改成女 db.update("stutb", values, "_id > ?", new String[]{"3"}); // 删除所有名字中带有丰的人 db.delete("stutb", "name like ? ", new String[]{"%丰%"}); Cursor cursor = db.query("stutb", null, "_id > ? ", new String[]{"0"}, null, null, "name"); if(cursor != null){ String[] columns = cursor.getColumnNames(); Log.i("yiusanfendi", "*********************"); while (cursor.moveToNext()){ for(String columnName : columns){ Log.i("yiusanfendi", cursor.getString(cursor.getColumnIndex(columnName))); } } Log.i("yiusanfendi", "*********************"); cursor.close(); } db.close(); |
SQLiteOpenHelper:
-SQLiteDatabase的帮助类,用于管理数据库的创建和版本更新
-一般是建立一个类继承它,并重写 onCreate() 和 onUpgrade() 方法
-方法说明
-onCreate(SQLiteDatabase db) 创建数据库时调用
-onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 版本更新时调用
-getReadableDatabase() 创建或打开一个只读数据库
-getWritableDatabase() 创建或打开一个读写数据库
调用处:
DBOpenHelper helper = new DBOpenHelper(this, "student.db"); // 获取一个只读的数据库,只能查询,不能写入,不能更新 SQLiteDatabase db = helper.getReadableDatabase(); Cursor cursor = db.rawQuery("select * from studenttb", null); if(cursor != null){ String[] cols = cursor.getColumnNames(); while(cursor.moveToNext()){ Log.i("yiusanfendi", "***************************"); for(String str : cols){ Log.i("yiusanfendi", str + " = " + cursor.getString(cursor.getColumnIndex(str))); } Log.i("yiusanfendi", "***************************"); } cursor.close(); } db.close(); |
DBOpenHelper:
public class DBOpenHelper extends SQLiteOpenHelper{
public DBOpenHelper(Context context, String name) {
super(context, name, null, 1);
}
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
// 首次创建数据库的时候调用,一般用于建库,建表的操作
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table if not exists studenttb(_id integer primary key autoincrement, name text not null, sex text not null, age integer not null)");
db.execSQL("insert into studenttb(name, sex, age) values('张三', '女', 18)");
}
// 当数据库的版本发生变化的时候会自动执行
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
} |
File
Android 中文件存储的操作:
-Activity 的 openFileOutput() 方法可以用于把数据输出到文件中
-创建的文件保存在 /data/data/<package name>/files 目录
-实现过程与在 Java 中保存数据到文件中是一样的
把文件写到外置SD卡上
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> File file = new File(Environment.getExternalStorageDirectory().getPath() + "/test.txt");
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "文件已经存在", Toast.LENGTH_LONG).show();
}
file.delete(); |
// 当前应用程序默认的数据存储目录
File file = getFilesDir();
Log.i("yimusanfendi", "file = " + file);
file = getCacheDir();
Log.i("yimusanfendi", "file = " + file);
file = getDir("imooc", MODE_PRIVATE);
Log.i("yimusanfendi", "file = " + file);
file = getExternalFilesDir(Environment.DIRECTORY_MUSIC);
Log.i("yimusanfendi", "file = " + file); 07-04 18:52:33.901 3314-3314/? I/yimusanfendi: file = /data/user/0/com.samsung.mycontentproviderandsqlite/files 07-04 18:52:33.901 3314-3314/? I/yimusanfendi: file = /data/user/0/com.samsung.mycontentproviderandsqlite/cache 07-04 18:52:33.902 3314-3314/? I/yimusanfendi: file = /data/user/0/com.samsung.mycontentproviderandsqlite/app_imooc 07-04 18:52:33.905 3314-3314/? I/yimusanfendi: file = /storage/emulated/0/Android/data/com.samsung.mycontentproviderandsqlite/files/Music |
.MODE_PRIVATE
-为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容。
.MODE_APPEND
-模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
.MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE
-用来控制其他应用是否有权限读写该文件
.MODE_WORLD_READABLE
-表示当前文件可以被其他应用读取
.MODE_WORLD_WRITEABLE
-表示当前文件可以被其他应用写入
getExternalFilesDir(type), getExternalCacheDir():
-可以得到外部的存储位置,该位置的数据和内容的使用时一样的,如果 APP 卸载了,这里面的数据也会自动清除掉,如果开发者不遵守这样的规则,不把数据放到 /data/data/<包名>, /mnt/sdcard/Android/data/<包名>,卸载之后数据将不会自动清除掉,将会造成所谓的数据垃圾
Android中文件存储的操作:
-Activity提供了 openFileOutput() 方法可以用于把数据输出到文件中,具体的实现过程与在 Java 中保存数据到文件中是一样的。
-创建的文件保存在 /data/data/<package name>/files目录
当应用程序在安装时系统会分配给他一个 userid, 当该引用要去访问其他资源比如文件的时候,就需要 userid 匹配。默认情况下,任何应用创建的文件,sharedpreferences, 数据库都应该是私有的(位于 /data/data/<package name>/files), 其他程序无法访问。除非在创建时指定了 MODE_WORLD_READABLE 或者 MODE_WORLD_WRITEABLE.
// 保存文件内容
public void writeFiles(String content){
try {
FileOutputStream fos = openFileOutput("a.txt", MODE_PRIVATE);
fos.write(content.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 读取文件内容
public String readFiles(){
String content = null;
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
fis = openFileInput("a.txt");
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while((len = fis.read(buffer)) != -1){
baos.write(buffer, 0, len);
}
content = baos.toString();
fis.close();
baos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return content;
} |
ContentProvider:
1, 继承抽象类 ContentProvider 实现一系列针对于数据的增、删、改、查等方法;
2, 需在 Androidmanifest.xml 中完成对 ContentProvider 的注册。
<provider
android:name=”com.imooc.MusicProvider”
android:authorities=”com.provider.music”
/>
注:注册的authorities 属性值时全局唯一的
Uri 是指通用资源标识符:
content:// com.imooc.provider / music / #
A B C D
A: 前缀表明数据受控于一个内容提供者。它从不修改,也就是 schema
B: 是指在 AndroidManifest.xml 中我们注册的 provider 中的 android:authorities 属性所对应的
C: 具体操作于哪个条目
D: 具体指定到哪个条目下的哪条记录(#表示通配符)
public class MyContentProvider extends ContentProvider{ // ContentProvider 创建后被调用 @Override public boolean onCreate() { return false; } // 根据 Uri 查询出 selection 指定的条件所匹配的全部记录,并且可以指定查询哪些类,以什么方式排序 @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { return null; } // 返回当前 Uri 的 MIME 类型,如果该 Uri 对应的数据可能包括多条记录 // 那么 MIME 类型字符串就是以 vnd.android.dir/ 开头 // 如果该 Uri 对应的数据只有一条记录,该 MIME 类型字符串 就是以 vnd.android.cursor.item/ 开头 @Nullable @Override public String getType(@NonNull Uri uri) { return null; } // 根据 URI 插入 Values 对应的数据 @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { return null; } // 根据 Uri 删除 selection 指定的条件所匹配的全部记录 @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } // 根据 Uri 修改 selection 指定的条件所匹配的全部记录 @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } } |
UriMatcher 类:
-UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
-UriMatcher.NO_MATCH 表示不匹配任何路径的返回码
-matcher.addURI(“com.imooc.provider”, “music”, 1);
-往UriMatcher 类里添加一个拼凑的 Uri
-UriMatcher 为一个 Uri 的容器,容器里面包含着我们即将可能要操作的 Uri
-如果通过 match() 方法匹配成功就返回 code 值
-matcher.match(uri)
-首先与找通过 addURI() 方法添加进来的 Uri 匹配
匹配成功则返回设置的 code 值,反之,返回一个 UriMatcher.NO_MATCH 常量(-1)
ContentResolver:
-使用ContentResolver 操作 ContentProvider 中的数据:
-当外部应用需要对 ContentProvider 中的数据进行添加、删除、修改和查询操作时,可以使用 ContentResolver 类来完成
-使用 Activity 提供的 getContentResolver() 方法获取 ContentResolver 对象
-ContentResolver 类提供了与 ContentResolver 类相同签名的四个方法
对ContentProvider 查询,插入,修改
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> ContentResolver cr = getContentResolver(); Cursor cursor = cr.query(Contacts.CONTENT_URI, new String[]{Contacts._ID, Contacts.DISPLAY_NAME},null,null,null); if(cursor != null){ while(cursor.moveToNext()){ int id = cursor.getInt(cursor.getColumnIndex(Contacts._ID)); Log.i("yimusanfendi", "_id = " + id); Log.i("yimusanfendi", "name = " + cursor.getString(cursor.getColumnIndex(Contacts.DISPLAY_NAME))); Cursor cursor1 = cr.query(Phone.CONTENT_URI, new String[]{Phone.NUMBER, Phone.TYPE}, Phone.CONTACT_ID + " = " + id, null, null); // 根据联系人 ID, 查询出联系人的电话号码 if(cursor1 != null){ while(cursor1.moveToNext()){ int type = cursor1.getInt(cursor1.getColumnIndex(Phone.TYPE)); if(type == Phone.TYPE_HOME){ Log.i("yimusanfendi", "家庭电话 = " + cursor1.getString(cursor1.getColumnIndex(Phone.NUMBER))); } else if(type == Phone.TYPE_MOBILE){ Log.i("yimusanfendi", "手机号码 = " + cursor1.getString(cursor1.getColumnIndex(Phone.NUMBER))); } } cursor1.close(); } // 根据联系人的 ID 去查询联系人的邮箱地址 Cursor cursor2 = cr.query(Email.CONTENT_URI, new String[]{Email.DATA, Email.TYPE}, Email.CONTACT_ID + " = " + id, null, null); if(cursor2 != null){ while(cursor2.moveToNext()){ int type = cursor2.getInt(cursor2.getColumnIndex(Email.TYPE)); Log.i("yimusanfendi", "Email type = " + type); if(type == Email.TYPE_HOME){ Log.i("yimusanfendi", "工作邮箱 = " + cursor2.getString(cursor2.getColumnIndex(Email.DATA))); } } cursor2.close(); } } cursor.close(); }
// 向联系人中插入一条数据 ContentValues values = new ContentValues(); Uri uri = cr.insert(RawContacts.CONTENT_URI,values); Long raw_contact_id = ContentUris.parseId(uri); values.clear(); values.put(StructuredName.RAW_CONTACT_ID, raw_contact_id); values.put(StructuredName.DISPLAY_NAME, "张三"); values.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); uri = cr.insert(ContactsContract.Data.CONTENT_URI, values); // 插入电话信息 values.clear(); values.put(Phone.RAW_CONTACT_ID, raw_contact_id); values.put(Phone.NUMBER, "13345678900"); values.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE); uri = cr.insert(Data.CONTENT_URI, values); |
ContentProvider:
- 四大组件之一
android:name="" 相同属性
android:authorities="" 不同属性
android:exported="" 不同属性
- 接口的统一,并不能用于存储数据,只是为数据的存储或者添加等操作提供一个统一的接口
- 供多个应用程序共享数据,跨进程访问,自定服务端程序,系统通讯录等
- 设备存储数据,设备使用 ContentProvider 进行存储:通讯录,图片,音频,视频。
- 数据更新监听,
ContentProvider 优缺点:
- 数据访问统一接口。
- 跨进程访问
- 无法单独使用,需要结合数据库,sharedpreference 等
实现数据访问:
- ContentResolver
- Context.getContentResolver()
- 提供四个与 ContentProvider 相一致的方法
- Transport implements IContentProvider
2, Uri
- 协议: //用户名: 密码@主机名:端口号/路径/文件?参数1=值1&参数2=值2
- 协议:content://
- 域名:android:authorities
- 路径:、contacts
- 统一资源标示符,用于表示网络的图片。网址
- 组成:协议 域名 路径
- 作用:访问 ContentProvider
- 不同:与其他组件(Intent)