note_34:用ContentProvider读取数据库

24 篇文章 0 订阅

用ContentProvider读取数据库


参考:



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环境变量配置

  1. adb shell
  2. run-as [pakageName]
  3. ls
  4. cd files
    然后就可以看到了

如果想看看数据库里面的数据表有没有完全拷过去的话,可以继续执行以下命令:

  1. sqlite3 [fileName].db
    然后屏幕会打印这个xxx.db的简要信息,例如数据库的创建时间
    屏幕会出现sqlite>
  2. .help
    可以查看有哪些可用的明明
  3. .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里面包含的那个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值