在这个示例中,我们创建一个 ContentProvider
用于“旅游景点”的数据共享,具体步骤:
- 创建一个继承自
SQLiteOpenHelper
的子类,用于数据库的管理; - 创建一个常量工具类,用于管理“旅游景点”的数据相关的常量,提高代码的简洁性和可维护性;
- 创一个
ContentProvider
的子类,实现增、删、改、查等方法,实现“旅游景点”的数据共享; - 在
AndroidManifest.xml
清单文件中注册该ContentProvider
- 创建一个用于“旅游景点”数据交互的
Activity
,实现写入数据、读取数据的功能 - 运行项目,实验操作
在项目
ContentProviderSample
代码基础上,
1. 创建一个继承自 SQLiteOpenHelper
的子类,用于数据库的管理
创建一个名为 TSDatabaseHelper
的类,继承 SQLiteOpenHelper
,并实现数据库的管理的代码。
Java 代码:
public class TSDatabaseHelper extends SQLiteOpenHelper {
// sql语句,创建一个名为 TouristSpot 数据库表,该表包含三个字段:_id、spot、detail
private static final String createTouristSpot = "create table TouristSpot (" +
" _id integer primary key autoincrement," +
"spot text," +
"detail text)";
public TSDatabaseHelper(@Nullable Context context, @Nullable String name, int version) {
this(context, name, null, version);
}
public TSDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(createTouristSpot);// 执行创建"表"的操作
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
}
}
Kotlin 代码:
class TSDatabaseHelper(context: Context, name: String, version: Int): SQLiteOpenHelper(context, name, null, version) {
// sql语句,创建一个名为 TouristSpot 数据库表,该表包含三个字段:_id、spot、detail
private val createTouristSpot = "create table TouristSpot (" +
" _id integer primary key autoincrement," +
"spot text," +
"detail text)"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(createTouristSpot)// 执行创建"表"的操作
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
}
}
2. 创建一个常量工具类,用于管理“旅游景点”的数据相关的常量
创建一个名为 TouristSpot
的类,
Java 代码:
public final class TouristSpot {
// 定义ContentProvider的 authority
public final static String AUTHORITY = "com.guagua.providers.tourists";
public final static String TABLE = "TouristSpot";
public static final class Spot implements BaseColumns {
// 数据列
public static final String _ID = "_id";
public static final String SPOT = "spot";
public static final String DETAIL = "detail";
// 定义ContentProvider 提供的 Uri
public static final Uri TOURISTS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/tourists");
public static final Uri SPOT_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/spot");
}
}
Kotlin 代码:
class TouristSpot {
companion object {
// 定义ContentProvider的 authority
const val AUTHORITY = "com.guagua.providers.tourists"
const val TABLE = "TouristSpot"
}
object Spot : BaseColumns {
// 数据列
const val _ID = "_id"
const val SPOT = "spot"
const val DETAIL = "detail"
// 定义ContentProvider 提供的 Uri
val TOURISTS_CONTENT_URI = Uri.parse("content://$AUTHORITY/tourists")
val SPOT_CONTENT_URI = Uri.parse("content://$AUTHORITY/spot")
}
}
3. 创一个 ContentProvider
的子类,实现增、删、改、查等方法,实现“旅游景点”的数据共享;
创建一个名为 TouristSpotContentProvider
的类,继承 ContentProvider
.
其中函数 insert()
delete()
update()
query()
分别对应增、删、改、查方法。
Java 代码:
public class TouristSpotContentProvider extends ContentProvider {
private final static int TOURISTS = 1;
private final static int SPOT = 2;
private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
// 向UriMatcher对象注册Uri
matcher.addURI(TouristSpot.AUTHORITY, "tourists", TOURISTS);
matcher.addURI(TouristSpot.AUTHORITY, "spot/#", SPOT);
}
private TSDatabaseHelper dbHelper;
@Override
public boolean onCreate() {
// ContentProvider创建时调用此函数
dbHelper = new TSDatabaseHelper(this.getContext(), "tourist_spots.db3", 1);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
switch (matcher.match(uri)) {
case TOURISTS:
// 查询
return db.query(TouristSpot.TABLE, projection, selection, selectionArgs, null, null, sortOrder);
case SPOT:
// 解析所要查询记录的 ID
long id = ContentUris.parseId(uri);
// 拼接 where 语句
StringBuilder where = new StringBuilder(TouristSpot.Spot._ID);
where.append("=").append(id);
if (!TextUtils.isEmpty(selection)) {
where.append(" and ").append(selection);
}
return db.query(TouristSpot.TABLE, projection, where.toString(), selectionArgs, null, null, sortOrder);
default:
return null;
// throw new IllegalArgumentException("unknown Uri: " + uri);
}
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (matcher.match(uri)) {
case TOURISTS: // 数据是多项记录
return "vnd.android.cursor.dir/com.guagua.tourist_spot";
case SPOT: // 数据是单项记录
return "vnd.android.cursor.item/com.guagua.tourist_spot";
}
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
// 获取数据库实例
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 插入数据
long rowId = db.insert(TouristSpot.TABLE, TouristSpot.Spot._ID, contentValues);
if (0 < rowId) {
//在Uri后追加 ID数据
Uri spotUri = ContentUris.withAppendedId(uri, rowId);
// 通知数据发生了改变
getContext().getContentResolver().notifyChange(spotUri, null);
return spotUri;
}
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;// 删除记录的数量
switch (matcher.match(uri)) {
// 匹配 uri
case TOURISTS:
count = db.delete(TouristSpot.TABLE, s, strings);
break;
case SPOT:
// 解析所要删除记录的 ID
long id = ContentUris.parseId(uri);
// 拼接 where 语句
StringBuilder where = new StringBuilder(TouristSpot.Spot._ID);
where.append("=").append(id);
if (!TextUtils.isEmpty(s)) {
where.append(" and ").append(s);
}
count = db.delete(TouristSpot.TABLE, where.toString(), strings);
break;
// default:
// throw new IllegalArgumentException("unknown Uri: " + uri);
}
// 通知数据发生了改变
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;// 修改记录的数量
switch (matcher.match(uri)) {
case TOURISTS:
count = db.update(TouristSpot.TABLE, contentValues, s, strings);
break;
case SPOT:
// 解析所要修改记录的 ID
long id = ContentUris.parseId(uri);
// 拼接 where 语句
StringBuilder where = new StringBuilder(TouristSpot.Spot._ID);
where.append("=").append(id);
if (!TextUtils.isEmpty(s)) {
where.append(" and ").append(s);
}
count = db.update(TouristSpot.TABLE, contentValues, where.toString(), strings);
break;
// default:
// throw new IllegalArgumentException("unknown Uri: " + uri);
}
// 通知数据发生了改变
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
Kotlin 代码:
class TouristSpotContentProvider : ContentProvider() {
private val TOURISTS = 1
private val SPOT = 2
private val matcher = UriMatcher(UriMatcher.NO_MATCH)
init {
// 向UriMatcher对象注册Uri
matcher.addURI(TouristSpot.AUTHORITY, "tourists", TOURISTS)
matcher.addURI(TouristSpot.AUTHORITY, "spot/#", SPOT)
}
private var dbHelper: TSDatabaseHelper? = null
override fun onCreate(): Boolean {
context?.let {
dbHelper = TSDatabaseHelper(it, "tourist_spots.db3", 1)
return true
} ?: return false
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
val db = dbHelper?.readableDatabase
if (dbHelper == null) {
return null
}
when (matcher.match(uri)) {
TOURISTS -> {
// 查询
return db!!.query(
TouristSpot.TABLE,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
}
SPOT -> {
// 解析所要查询记录的 ID
val id = ContentUris.parseId(uri)
// 拼接 where 语句
val where = StringBuilder(TouristSpot.Spot._ID)
where.append("=").append(id)
if (!TextUtils.isEmpty(selection)) {
where.append(" and ").append(selection)
}
return db!!.query(
TouristSpot.TABLE,
projection,
where.toString(),
selectionArgs,
null,
null,
sortOrder
)
}
}
return null
}
override fun getType(uri: Uri): String? {
when (matcher.match(uri)) {
TOURISTS -> return "vnd.android.cursor.dir/com.guagua.tourist_spot"
SPOT -> return "vnd.android.cursor.item/com.guagua.tourist_spot"
}
return null
}
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
if (dbHelper == null) {
return null
}
// 获取数据库实例
val db = dbHelper!!.writableDatabase
// 插入数据
val rowId = db.insert(TouristSpot.TABLE, TouristSpot.Spot._ID, contentValues)
if (0 < rowId) {
//在Uri后追加 ID数据
val spotUri = ContentUris.withAppendedId(uri, rowId)
// 通知数据发生了改变
context!!.contentResolver.notifyChange(spotUri, null)
return spotUri
}
return null
}
override fun delete(uri: Uri, s: String?, strings: Array<out String>?): Int {
if (dbHelper == null) return 0
val db = dbHelper!!.writableDatabase
var count = 0 // 删除记录的数量
when (matcher.match(uri)) {
TOURISTS -> count = db.delete(TouristSpot.TABLE, s, strings)
SPOT -> {
// 解析所要删除记录的 ID
val id = ContentUris.parseId(uri)
// 拼接 where 语句
val where = StringBuilder(TouristSpot.Spot._ID)
where.append("=").append(id)
if (!TextUtils.isEmpty(s)) {
where.append(" and ").append(s)
}
count = db.delete(TouristSpot.TABLE, where.toString(), strings)
}
}
// 通知数据发生了改变
context!!.contentResolver.notifyChange(uri, null)
return count
}
override fun update(
uri: Uri,
contentValues: ContentValues?,
s: String?,
strings: Array<out String>?
): Int {
if (dbHelper == null) return 0
val db = dbHelper!!.writableDatabase
var count = 0 // 修改记录的数量
when (matcher.match(uri)) {
TOURISTS -> count =
db.update(TouristSpot.TABLE, contentValues, s, strings)
SPOT -> {
// 解析所要修改记录的 ID
val id = ContentUris.parseId(uri)
// 拼接 where 语句
val where = StringBuilder(TouristSpot.Spot._ID)
where.append("=").append(id)
if (!TextUtils.isEmpty(s)) {
where.append(" and ").append(s)
}
count = db.update(TouristSpot.TABLE, contentValues, where.toString(), strings)
}
}
// 通知数据发生了改变
context!!.contentResolver.notifyChange(uri, null)
return count
}
}
3.1 代码说明
UriMatcher
工具类有两个函数:
-
void addURI(String authority, String path, int code)
,向UriMatcher
对象注册Uri
。参数authority
、path
合成一个Uri
,参数code
代表该Uri
的标志码。 -
int match(Uri uri)
,根据Uri
对象来判定其之前注册的标志码,如果不存在则返回-1
.
ContentUris
字符串工具类:
-
long parseId(Uri contentUri)
函数,从Uri
中解析出ID
.
例如Uri
com.guagua.providers.tourists/spot/3
,它的ID
为3
. -
Uri withAppendedId(Uri contentUri, long id)
函数,将id
附加到Uri
路径上。
4. 在AndroidManifest.xml
清单文件中注册该ContentProvider
<provider
android:name=".TouristSpotContentProvider"
android:authorities="com.guagua.providers.tourists" />
<provider/>
标签与 <activity/>
标签同级。
5. 创建一个用于“旅游景点”数据交互的 Activity
,实现写入数据、读取数据的功能
5.1 创建一个名为 TouristSpotActivity
的页面( Activity
),用于“旅游景点”数据交互
5.2 编辑TouristSpotActivity
的布局文件 activity_tourist_spot.xml
activity_tourist_spot.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="20dp"
tools:context=".TouristSpotActivity">
<EditText
android:id="@+id/spotEt"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:hint="spot"
android:lines="1"/>
<EditText
android:id="@+id/detailEt"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:hint="detail"
android:maxLines="3"/>
<Button
android:id="@+id/insertBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#888888" />
<TextView
android:id="@+id/spotsTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Tourist Spot"
android:textSize="15sp" />
</LinearLayout>
5.3 编辑TouristSpotActivity
,实现写入数据、读取数据的功能
进入到这个页面,读取“旅游景点数据”并在界面上呈现;插入数据后,会执行读取“旅游景点数据”的操作。
Java 代码:
public class TouristSpotActivity extends AppCompatActivity {
private TextView spotsTv, spotEt, detailEt;
private final List<String> spotsList = new LinkedList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tourist_spot);
spotEt = findViewById(R.id.spotEt);
detailEt = findViewById(R.id.detailEt);
spotsTv = findViewById(R.id.spotsTv);
findViewById(R.id.insertBtn).setOnClickListener(view -> insertTouristSpot());
readTouristSpot();
}
/**
* 读取共享的旅游景点数据
*/
private void readTouristSpot() {
spotsList.clear();
// 查询数据
Cursor cursor = getContentResolver().query(TouristSpot.Spot.TOURISTS_CONTENT_URI, null, null, null, null);
while (cursor.moveToNext()) {
String spot = cursor.getString(cursor.getColumnIndex(TouristSpot.Spot.SPOT));
String detail = cursor.getString(cursor.getColumnIndex(TouristSpot.Spot.DETAIL));
spotsList.add(spot + "\n" + detail);// 将读取到的数据放入集合中
}
cursor.close();
// 将数据在界面上呈现出来
updateUI();
}
private void updateUI() {
spotsTv.setText("");
for (String s : spotsList) {
// 简化的操作,用TextView显示数据
spotsTv.append("\n\n");
spotsTv.append(s);
}
}
/**
* 写入共享的旅游景点数据
*/
private void insertTouristSpot() {
String spot = spotEt.getText().toString();
String detail = detailEt.getText().toString();
if (TextUtils.isEmpty(spot) || TextUtils.isEmpty(detail)) return;
// 插入数据
ContentValues values = new ContentValues();
values.put(TouristSpot.Spot.SPOT, spot);
values.put(TouristSpot.Spot.DETAIL, detail);
getContentResolver().insert(TouristSpot.Spot.TOURISTS_CONTENT_URI, values);
// 提示插入数据完成
Toast.makeText(this, " 添加景点记录完成 ", Toast.LENGTH_SHORT).show();
spotEt.setText("");
detailEt.setText("");
// 重新读取数据,检验上述操作是否成功
readTouristSpot();
}
}
Kotlin 代码:
class TouristSpotActivity : AppCompatActivity() {
private lateinit var spotsTv: TextView
private lateinit var spotEt: EditText
private lateinit var detailEt: EditText
private val spotsList: LinkedList<String> = LinkedList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tourist_spot)
spotEt = findViewById(R.id.spotEt)
detailEt = findViewById(R.id.detailEt)
spotsTv = findViewById(R.id.spotsTv)
findViewById<View>(R.id.insertBtn).setOnClickListener {
insertTouristSpot() }
readTouristSpot()
}
/**
* 读取共享的旅游景点数据
*/
private fun readTouristSpot() {
spotsList.clear()
// 查询数据
contentResolver.query(
TouristSpot.Spot.TOURISTS_CONTENT_URI,
null, null, null, null
)?.apply {
while (moveToNext()) {
val spot = getString(getColumnIndex(TouristSpot.Spot.SPOT))
val detail = getString(getColumnIndex(TouristSpot.Spot.DETAIL))
spotsList.add("$spot \n$detail")
}
close()
}
// 将数据在界面上呈现出来
updateUI()
}
private fun updateUI() {
spotsTv.text = ""
for (s in spotsList) {
// 简化的操作,用TextView显示数据
spotsTv.append("\n\n")
spotsTv.append(s)
}
}
/**
* 写入共享的旅游景点数据
*/
private fun insertTouristSpot() {
val spot = spotEt.text.toString()
val detail = detailEt.text.toString()
if (TextUtils.isEmpty(spot) || TextUtils.isEmpty(detail)) return
// 插入数据
val values = ContentValues()
values.put(TouristSpot.Spot.SPOT, spot)
values.put(TouristSpot.Spot.DETAIL, detail)
contentResolver.insert(TouristSpot.Spot.TOURISTS_CONTENT_URI, values)
// 提示插入数据完成
Toast.makeText(this, " 添加景点记录完成 ", Toast.LENGTH_SHORT).show()
spotEt.setText("")
detailEt.setText("")
// 重新读取数据,检验上述操作是否成功
readTouristSpot()
}
}
5.4 在 MainActivity
页面中增加一个按钮——进入TouristSpotActivity
页面的的入口
activity_main.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="30dp">
......
<Button
android:id="@+id/spotBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tourist Spot" />
</LinearLayout>
MainActivity
代码,
Java 代码:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
......
Button spotBtn = findViewById(R.id.spotBtn);
spotBtn.setOnClickListener(view -> {
Intent intent = new Intent(this, TouristSpotActivity.class);
startActivity(intent);
});
}
Kotlin 代码:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
......
val spotBtn = findViewById<Button>(R.id.spotBtn)
spotBtn.setOnClickListener {
val intent = Intent(this, TouristSpotActivity::class.java)
startActivity(intent)
}
}
6. 运行项目,实验操作
运行项目,点击按钮,进入TouristSpotActivity
页面,在文本框中输入景点的名称和介绍,然后点击按钮插入数据;
然后就可以看到界面刷新,显示出刚刚输入的景点数据。
项目代码地址
Java 版:
https://github.com/BethelDEV/shaguaAndroid/tree/main/javaSource/ContentProviderSampleKotlin 版:
https://github.com/BethelDEV/shaguaAndroid/tree/main/kotlinSource/ContentProviderSample