用ContentProvider读取数据库
参考:
- SQLiteOpenHelper 打开自定义路径下的db数据库文件
- android初学之拷贝数据库到本地
- android-sdk环境变量配置
- sqlite数据库查看里面数据库版本号
- Adnroid文件存储路径getFilesDir()与getExternalFilesDir的区别
- android 玩转ContentProvider之三--实现一个ContentProvider对多张表进行操作
- ContentProvider的getType()的作用
文章目录
1. 有时可能需要把数据库从Assets复制到/data/data/目录下
根据android初学之拷贝数据库到本地的做法,先在main
目录下创建一个Assets
文件夹,然后把.db
数据库文件粘贴到Assets
目录下。
/data/data/
目录只能用adb
命令查看,没办法直接在手机上查看,该目录可以通过getFilesDir()
获取。
private void copyData() {
// 首先创建文件 也就是放在/data/data目录下的数据库文件
File dataFile = new File(getFilesDir(), FILE_NAME);
// 如果数据库已经拷贝过来了,那么就没必要重新拷一遍了
if (!dataFile.exists()) {
new Thread() {
@Override
private void run() {
// try-catch主要是因为getAssets().open()会throw Exception
try {
InputStream inputStream = getAssets().open(ASSETS_FILE_NAME);
FileOutputStream outputStream = new FileOutputStream(dataFile);
byte[] buffer = new byte[BUFFER_SIZE]; // 每一次读取的字节数
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
}
outputStream.flush(); // 刷新缓冲区
outputStream.close();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
} else {
Log.d(TAG, "数据库已存在!");
}
}
这时,如果要查看/data/data
目录下有没有这个dataFile
的话,执行如下命令:
执行命令之前确保已经配置好了安卓sdk
的各类环境变量:android-sdk环境变量配置
- adb shell
- run-as [pakageName]
- ls
- cd files
然后就可以看到了
如果想看看数据库里面的数据表有没有完全拷过去的话,可以继续执行以下命令:
- sqlite3 [fileName].db
然后屏幕会打印这个xxx.db的简要信息,例如数据库的创建时间
屏幕会出现sqlite>- .help
可以查看有哪些可用的明明- .tables
查看全部表格
一般来说都会有一个android_metadata
的表。
2. ContentProvider操作多张表
(1) 自定义ContextWrapper
自定义ContextWrapper
主要是因为SQLiteOpenHelper
打开的文件有一个默认路径/data/data/databases/
。
但是数据库的路径不可能每一个项目都放到这个默认路径下的,所以必须找一个方法使得放在别处的路径也可以访问到。
通过重写getDatabasePath()
,可以重新定义helper
要访问的数据库的路径。
public class DatabaseContext extends ContextWrapper {
public DatabaseContext(Context context) {
super(context);
}
@Override
public File getDatabasePath(String name) {
File dbFile = getFilesDir() + FILE_NAME;
if (dbFile.exists()) {
return result;
} else {
throw new Exception("没有dbFile");
}
}
// 安卓sdk>=11的时候会调用这个版本
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
return openOrCreateDatabase(name, mode, factory);
}
// 安卓sdk<11的时候会调用这个版本
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {
SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
return result;
}
}
(2) 自定义SQLiteOpenHelper
SQLiteOpenHelper
主要用来创建数据库和升级数据库。
其中在创建数据库的onCreate()
里面可以进行建表。
在升级数据库的onUpgrade()
里面可以通过判断新版本和旧版本来决定要不要升级。
如果不需要创建表格的话可以不建表,也就是onCreate()
里面不写东西。同理,onUpgrade()
也是。
public class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
// 用于初始化DatabaseHelper的Context要注意 要使用自定义的那个DatabaseContext
super(new DatabaseContext(context), DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase dbDatabase) {
Log.d(TAG, "onCreate: ");
// 如果要创建表格的话 一定要严格按照格式来写SQL 不光是变量和类型 空格和标点也不能出问题
// 否则建表失败
}
@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
Log.d(TAG, "onUpgrade: ");
}
}
(3) 自定义ContentProvider
ContentProvider
作为安卓四大组件之一主要是用于数据的共享,既可以应用内共享,也可以跨应用共享。
而且它操作的是SQLiteDatabase
,一个轻量级数据库但是数据不容易丢失。
SharedPreferences
主要用于应用内共享,数据是用键值对的形式存成xml
。如果数据很少的时候用缺失很方便,而且安全性也可以。但是有时会出现访问失败的情况。SharedPreferences
也可以实现跨应用共享,但是官方文档上是不建议这么做的。
public class DatabaseProvider extends ContentProvider {
// 主要用于getType()的返回值
// 当数据库查询返回一个item的时候要返回的字符串必须以SINGLE_ITEM开头
// 如果返回多个item的时候要返回的字符串必须以MULTIPLE_ITEMS开头
private static final String SINGLE_ITEM_STRING = "vnd.android.cursor.item/";
private static final String MULTIPLE_ITEMS_STRING = "vnd.android.cursor.dir/";
private static final String ITEM_STRING = "item";
// 主要用于getType()中对不同Uri的判别
private static final int SINGLE_ITEM_CODE = 0;
private static final int MULTIPLE_ITEMS_CODE = 1;
// 数据库操作时对Uri进行分类
// 可用于getType(),根据Uri区分返回的结果是一个item还是多个item
// 可以在增删改查操作中根据Uri决定使用哪张表或者哪个projectionMap
private static UriMatcher sUriMathcer;
// 字段映射表
// 主要是用于重命名字段名称 避免在拼表的时候弄错字段名
private static HashMap<String, String> sProjectionMapA;
private static HashMap<String, String> sProjectionMapB;
private DatabaseHelper mOpenHelper;
// 一整块代码只执行一次
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 把所有的数据库查询的URI都加入到这个UriMatcher中 因为要区分返回的item个数是一个还是多个
sUriMatcher.addUri(URI_AUTHORITY, URI_PATH_SINGLE, SINGLE_ITEM_CODE);
sUriMatcher.addUri(URI_AUTHORITY, URI_PATH_MULTIPLE, MULTIPLE_ITEMS_CODE);
// 不同的数据表不一定会一样 因为它们的字段名不一定是一样的
// 除非两张表会用上的字段名是一样的 否则不要把两张表的字段名写在同一个projectionMap里面
// 不然系统会默认一张表上包含projectionMap里的所有字段 这时很容易出现unable to find table xxx的错误
sProjectionMapA = new HashMap<>();
sProjectionMapA.put(IN_TITLE_A, OUT_TITLE_A);
sProjectionMapA.put(IN_TITLE_B, OUT_TITLE_B);
sProjectionMapB = new HashMap<>();
sProjectionMapB.put(IN_TITLE_C, OUT_TITLE_C);
sProjectionMapB.put(IN_TITLE_D, OUT_TITLE_D);
}
@Override
public boolean onCreate() {
// 这里直接getContext()就可以了
// 因为DatabaseHelper会根据传进去的context自动new一个DatabaseContext
mOpenHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 用于后续setTables(),setProjectionMap(),cursor.query()
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// 如果需要根据Uri的path来setTable()或者setProjectionMap()的话
// 在使用uri.getPath()的时候一定要小心 返回的值前面会有一个'/'
if (uri.getPath() == null) {
Log.d(TAG, "uri没有path");
return null;
} else {
switch (uri.getPath().substring(1)) {
case SINGLE_PATH:
queryBuilder.setTables(SINGLE_TABLE);
queryBuilder.setProjectionMap(sProjectionMapA);
break;
case MULTIPLE_PATH:
queryBuilder.setTables(MULTIPLE_TABLE);
queryBuilder.setProjectionMap(sProjectionMapB);
break;
default:
throw new Exception("非法uri");
}
}
// 就是之前DatabaseHelper绑定的那个数据库
// 如果是用于查询的话就getReadableDatabase() 否则就是getWritableDatabase()
SQLiteDatabase database = mOpenHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
return cursor;
}
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case SINGLE_ITEM_CODE:
return SINGLE_ITEM_STRING + ITEM_STRING;
case MULTIPLE_ITEMS_CODE:
return MULTIPLE_ITEMS_STRING + ITEM_STRING;
default:
throw new Exception("非法uri");
}
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
}
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
}
}
(4) Manifest.xml
ContentProvider的代码写完之后,务必要在Manifest.xml
里面声明<provider name="" authorities="" exported="" enabled="" />
,否则没有效果了。其中,name
就是包名加类名,authority
就是Uri
里面包含的那个。