ContentProvider 作为四大组件之一,广泛用于进程间的数据共享,包括多用户之间的数据共享。
记录一个简单的实例, 插入、查询、删除、更新联系人,其中可以选择根据id操作, 需结合数据库 SQLiteOpenHelper。
1. 继承自ContentProvider
实现query/insert/delete/update 操作, 并在onCreate的时候,创建一个SQLiteOpenHelper实例。
package com.example.personprovider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
//更多介绍 https://www.jianshu.com/p/ea8bc4aaf057
public class PersonProvider extends ContentProvider {
private static final String TAG = "PersonProvider";
private static final String AUTH = "com.example.personprovider";
private static final String DATA_PATH = "person";
private static final int MATCH_CODE_WITHOUT_ID = 1;
private static final int MATCH_CODE_WITH_ID = 2;
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private DataHelper dataHelper;
//保存一些合法的Uri
static {
//不根据id查 -> "/person"
matcher.addURI(AUTH, "/" + DATA_PATH, 1); // code 代表类型
//根据id 查 -> "/person/#"
matcher.addURI(AUTH, "/" + DATA_PATH + "/#", 2); //#表示任意正数? 传入的时候需要是具体的数值,才能匹配到code
}
public PersonProvider() {
logD("constructor call");
}
@Override
public boolean onCreate() {
logD("- onCreate");
dataHelper = new DataHelper(getContext());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
logD("- query uri=" + uri + ", projection=" + projection + ", selection=" + selection + ", selectionArgs="
+ selectionArgs + ", sortOrder=" + sortOrder);
//得到连接对象
SQLiteDatabase database = dataHelper.getReadableDatabase();
//1. 匹配 uri, 返回code
int code = matcher.match(uri);
logD("- query code=" + code);
if (code == MATCH_CODE_WITHOUT_ID) {//不根据id查
Cursor cursor = database.query(DATA_PATH, projection, selection, selectionArgs, null, null, null);
return cursor;
} else if (code == MATCH_CODE_WITH_ID) {//根据id 查
long id = ContentUris.parseId(uri);
Cursor cursor = database.query(DATA_PATH, projection, "_id=?", new String[]{id + ""}, null, null, null);
return cursor;
} else {
throw new RuntimeException("查询的uri 不合法");
}
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
/**
* "content://com.example.personprovider/person/#" --》 根据id 插入 (#代表id)
* "content://com.example.personprovider/person" --》 不根据id 插入
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
logD("insert uri=" + uri + ", values=" + values);
//得到连接对象
SQLiteDatabase database = dataHelper.getReadableDatabase();
//1. 匹配 uri, 返回code
int code = matcher.match(uri);
logD("insert code=" + code);
//如果合法,则插入
if (code == MATCH_CODE_WITHOUT_ID) {
long id = database.insert(DATA_PATH, null, values);
uri = Uri.withAppendedPath(uri, id + "");
database.close();
logD("insert without id, insert to id=" + id);
return uri;
} else if (code == MATCH_CODE_WITH_ID) {
long id = database.insert(DATA_PATH, null, values);
database.close();
//uri = Uri.withAppendedPath(uri, id + ""); // values 中的id
logD("insert with id=" + id + ", uri=" + uri);
return uri;
} else {
throw new RuntimeException("插入的uri 不合法");
}
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
int deleteCount = 0;
//得到连接对象
SQLiteDatabase database = dataHelper.getReadableDatabase();
//1. 匹配 uri, 返回code
int code = matcher.match(uri);
logD("delete code=" + code);
if (code == MATCH_CODE_WITHOUT_ID) {
deleteCount = database.delete(DATA_PATH, selection, selectionArgs);
database.close();
} else if (code == MATCH_CODE_WITH_ID) {
long id = ContentUris.parseId(uri);
deleteCount = database.delete(DATA_PATH, "_id=?", new String[]{id + ""});
database.close();
} else {
throw new RuntimeException("删除的uri 不合法");
}
logD("delete success deleteCount=" + deleteCount);
return deleteCount;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
int updateCount = 0;
//得到连接对象
SQLiteDatabase database = dataHelper.getReadableDatabase();
//1. 匹配 uri, 返回code
int code = matcher.match(uri);
logD("update code=" + code);
if (code == MATCH_CODE_WITHOUT_ID) {
updateCount = database.update(DATA_PATH, values, selection, selectionArgs);
database.close();
} else if (code == MATCH_CODE_WITH_ID) {
long id = ContentUris.parseId(uri);
updateCount = database.update(DATA_PATH, values, "_id=?", new String[]{id + ""});
database.close();
} else {
throw new RuntimeException("删除的uri 不合法");
}
logD("delete success updateCount=" + updateCount);
return updateCount;
}
private void logD(String message) {
Log.d(TAG, message);
}
}
package com.example.personprovider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DataHelper extends SQLiteOpenHelper {
private static final String PERSON_DB_NAME = "simple.db";
private static final String TAG = "DataHelper";
public DataHelper(Context context) {
super(context, PERSON_DB_NAME, null, 1);
logD("DataHelper constructor ");
getReadableDatabase();//TODO - CHECK
logD("DataHelper() constructor end - after call getReadableDatabase ");
}
@Override
public void onCreate(SQLiteDatabase db) {
logD("onCreate() DB has been created!!!");
db.execSQL("create table person(_id integer primary key autoincrement, name varchar)");
db.execSQL("insert into person (name) values ('Petter')");
db.execSQL("insert into person (name) values ('Petter')");
}
@Override
public void onOpen(SQLiteDatabase db) {
logD("onOpen - DB has been opened ");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
logD("onOpen() oldVersion=" + oldVersion + ", newVersion=" + newVersion);
}
private void logD(String message) {
Log.d(TAG, message);
}
}
并且在AndroidManifest中声明 provider:
<provider
android:authorities="com.example.personprovider"
android:name="com.example.personprovider.PersonProvider"
android:exported="true">
需要有默认的Activity才能创建???
2. 客户端调用
package com.example.personclient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class PersonActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private static final String PREFIX = "content://";
private static final String AUTH = "com.example.personprovider";
private static final String DATA_PATH = "person";
private static final String URI_WITHOUT_ID = PREFIX + AUTH + "/" + DATA_PATH;
private static final String URI_WITH_ID = PREFIX + AUTH + "/" + DATA_PATH + "/";
EditText mIdEditText;
EditText mNameEditText;
CustomButton mQueryButton;
CustomButton mInsertButton;
CustomButton mDeleteButton;
CustomButton mUpdateButton;
CustomButton mCurrentClickedButton;
CheckBox mIdCheckBox;
Button mExecuteButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person);
initUI();
showToast("我是 " + this.getClass().getSimpleName());
}
private void initUI() {
mIdEditText = findViewById(R.id.id_et);
mNameEditText = findViewById(R.id.name_et);
mIdCheckBox = findViewById(R.id.id_cb);
initButton();
reset();
}
private void initButton() {
mQueryButton = findViewById(R.id.query_button);
mInsertButton = findViewById(R.id.insert_button);
mDeleteButton = findViewById(R.id.delete_button);
mUpdateButton = findViewById(R.id.update_button);
mExecuteButton = findViewById(R.id.execute_btn);
mCurrentClickedButton = null;
mQueryButton.setCallBack(new CustomButton.CallBack() {
@Override
public void onPerform() {
query();
}
});
mInsertButton.setCallBack(new CustomButton.CallBack() {
@Override
public void onPerform() {
insert();
}
});
mDeleteButton.setCallBack(new CustomButton.CallBack() {
@Override
public void onPerform() {
delete();
}
});
mUpdateButton.setCallBack(new CustomButton.CallBack() {
@Override
public void onPerform() {
update();
}
});
}
private String getId() {
if (mIdEditText != null && (mIdEditText.getVisibility() == View.VISIBLE)
&& !TextUtils.isEmpty(mIdEditText.getText())) {
return mIdEditText.getText().toString();
}
return null;
}
private String getName() {
if (mNameEditText != null && (mNameEditText.getVisibility() == View.VISIBLE)
&& !TextUtils.isEmpty(mNameEditText.getText())) {
return mNameEditText.getText().toString();
}
return null;
}
private Uri getUri(String id) {
Uri uri;
if (!TextUtils.isEmpty(id)) {
uri = Uri.parse(URI_WITH_ID + id); //根据id 查
} else {
uri = Uri.parse(URI_WITHOUT_ID); //不根据id 查
}
logD(" getUri id = " + id + ", uri=" + uri);
return uri;
}
/**
* "content://com.example.personprovider/person/#" --》 根据id 查 (#代表id)
* "content://com.example.personprovider/person" --》 不根据id 查
*/
public void query() {
//1 得到ContentResolver 对象
ContentResolver resolver = getContentResolver();
//2 创建Uri, 调用其query, 得到cursor
Uri uri = getUri(getId());
logD(" query uri=" + uri);
//3. 取出cursor中的数据,并显示
Cursor cursor = resolver.query(uri, null, null, null, null);
logD(" query cursor=" + cursor);
if (cursor == null) {
Toast.makeText(this, "cursor is null, and uri=" + uri, Toast.LENGTH_SHORT).show();
} else {
logD(" query cursor count=" + cursor.getCount());
showToast("共查询到 " + cursor.getCount() + " 条结果");
while (cursor.moveToNext()) {
int id = cursor.getInt(0);
String name = cursor.getString(1);
Toast.makeText(this, "查询结果: id = " + id + ", name= " + name, Toast.LENGTH_SHORT).show();
}
cursor.close();
}
}
/**
* "content://com.example.personprovider/person/#" --》 根据id 插入 (#代表id), 需要传具体的id
* "content://com.example.personprovider/person" --》 不根据id 插入
* 插入已存在的row ,则会保错
* // SQLiteDatabase: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed:
* // person._id (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY[1555])
*/
public void insert() {
//1 得到 ContentResolver 对象
ContentResolver resolver = getContentResolver();
//2 创建Uri, 设置插入的值, 调用其 ContentResolver.insert
String id = getId();
Uri uri = getUri(id);
//设置值
ContentValues contentValues = new ContentValues();
if (!TextUtils.isEmpty(id)) {
contentValues.put("_id", Integer.parseInt(id));
}
String name = getName();
if (!TextUtils.isEmpty(name)) {
contentValues.put("name", name);
}
logD(" insert before insert uri=" + uri + ", contentValues=" + contentValues);
// 3. 插入
Uri insertUri = resolver.insert(uri, contentValues);
showToast("插入后的uri=" + insertUri + "");
logD(" insert after insert uri=" + insertUri);
}
public void delete() {
//1 得到 ContentResolver 对象
ContentResolver resolver = getContentResolver();
//2 创建Uri (先获取要删除的id)
String id = getId();
Uri uri = getUri(id);
logD(" delete id=" + id + ", uri=" + uri);
//3 调用其 ContentResolver.delete
int deleteCount = resolver.delete(uri, null, null);
showToast("删除了 " + deleteCount + " 条记录");
}
public void update() {
//1 得到 ContentResolver 对象
ContentResolver resolver = getContentResolver();
//2 创建Uri (先获取要删除的id)
String id = getId();
Uri uri = getUri(id);
logD(" delete id=" + id + ", uri=" + uri);
//更新值
ContentValues contentValues = new ContentValues();
if (!TextUtils.isEmpty(id)) {
contentValues.put("_id", Integer.parseInt(id));
}
String name = getName();
if (!TextUtils.isEmpty(name)) {
contentValues.put("name", name);
}
logD(" update uri=" + uri + ", contentValues=" + contentValues);
//3 调用其 ContentResolver.update
int updateCount = resolver.update(uri, contentValues, null, null);
showToast("更新 " + updateCount + " 条记录");
}
public boolean useId() {
return mIdCheckBox != null && mIdCheckBox.isChecked();
}
private void logD(String message) {
Log.d(TAG, message);
}
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
logD("onClick v=" + v);
if (v == null) {
logD("onClick v is null , return");
}
switch (v.getId()) {
case R.id.insert_button:
mCurrentClickedButton = mInsertButton;
updateCheckState();
break;
case R.id.query_button:
mCurrentClickedButton = mQueryButton;
updateCheckState();
break;
case R.id.delete_button:
mCurrentClickedButton = mDeleteButton;
updateCheckState();
break;
case R.id.update_button:
mCurrentClickedButton = mUpdateButton;
updateCheckState();
break;
case R.id.id_cb:
CheckBox cb = (CheckBox) v;
setIdEditTextVisible(cb.isChecked());
break;
case R.id.execute_btn:
execute();
break;
default:
break;
}
}
private void execute() {
if (mCurrentClickedButton != null && mCurrentClickedButton.mCallBack != null) {
mCurrentClickedButton.mCallBack.onPerform();
}
reset();
}
private void reset(){
if (mNameEditText != null) {
mNameEditText.setText("");
}
if (mIdEditText != null) {
mIdEditText.setText("");
}
if(mIdCheckBox!=null){
mIdCheckBox.setChecked(false);
}
setIdEditTextVisible(false);
setNameEditTextVisible(false);
}
//任何时候只有一个能被选中
private void updateCheckState() {
mQueryButton.setChecked(mCurrentClickedButton == mQueryButton);
mInsertButton.setChecked(mCurrentClickedButton == mInsertButton);
mDeleteButton.setChecked(mCurrentClickedButton == mDeleteButton);
mUpdateButton.setChecked(mCurrentClickedButton == mUpdateButton);
setNameEditTextVisible(mCurrentClickedButton == mInsertButton || mCurrentClickedButton == mUpdateButton);
}
private void setIdEditTextVisible(boolean visible) {
if (mIdEditText == null) {
return;
}
mIdEditText.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
private void setNameEditTextVisible(boolean visible) {
if (mNameEditText == null) {
return;
}
mNameEditText.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
}
其中,CustonButton 是为了统一处理点击事件
package com.example.personclient;
import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatCheckBox;
public class CustomButton extends AppCompatCheckBox {
public CallBack mCallBack;
public CustomButton(Context context) {
super(context);
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public interface CallBack {
void onPerform();
}
public void setCallBack(CallBack mCallBack) {
this.mCallBack = mCallBack;
}
}
布局文件 activity_person.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/first_row_guide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.005" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/second_row_guide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.1" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/third_row_guide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.30" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/fourth_row_guide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.45" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/first_column_guide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.05" />
<TextView
android:id="@+id/tip_text"
android:layout_width="300dp"
android:layout_height="50dp"
android:text="根据条件进行数据库操作"
android:gravity="center_vertical"
android:textSize="@dimen/title_size"
app:layout_constraintStart_toEndOf="@id/first_column_guide_line"
app:layout_constraintTop_toBottomOf="@id/first_row_guide_line"/>
<com.example.personclient.CustomButton
android:id="@+id/delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="删除联系人"
android:textSize="@dimen/title_size"
app:layout_constraintStart_toStartOf="@id/insert_button"
app:layout_constraintTop_toBottomOf="@id/insert_button" />
<com.example.personclient.CustomButton
android:id="@+id/update_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="更新联系人"
android:textSize="@dimen/title_size"
app:layout_constraintStart_toStartOf="@id/delete_button"
app:layout_constraintTop_toBottomOf="@id/delete_button" />
<com.example.personclient.CustomButton
android:id="@+id/query_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="查询联系人"
android:textSize="@dimen/title_size"
app:layout_constraintStart_toStartOf="@id/first_column_guide_line"
app:layout_constraintTop_toBottomOf="@id/second_row_guide_line" />
<com.example.personclient.CustomButton
android:id="@+id/insert_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="插入联系人"
android:textSize="@dimen/title_size"
app:layout_constraintStart_toStartOf="@id/query_button"
app:layout_constraintTop_toBottomOf="@id/query_button" />
<LinearLayout
android:id="@+id/id_layout"
android:layout_width="wrap_content"
android:layout_height="55dp"
android:orientation="horizontal"
app:layout_constraintStart_toEndOf="@id/first_column_guide_line"
app:layout_constraintTop_toBottomOf="@id/third_row_guide_line">
<CheckBox
android:id="@+id/id_cb"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:onClick="onClick"/>
<TextView
android:id="@+id/id_text"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="@dimen/margin_left"
android:maxLines="1"
android:text="@string/use_id_to_operate"
android:textSize="@dimen/title_size"/>
<EditText
android:id="@+id/id_et"
android:layout_width="120dp"
android:layout_height="45dp"
android:hint="输入id..."
android:layout_marginLeft="@dimen/margin_left"
android:inputType="number" />
</LinearLayout>
<EditText
android:id="@+id/name_et"
android:layout_width="110dp"
android:layout_height="45dp"
android:hint="输入名字..."
android:inputType="textCapCharacters"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/id_layout"
app:layout_constraintStart_toStartOf="@id/first_column_guide_line" />
<Button
android:id="@+id/execute_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始执行"
android:textSize="@dimen/title_size"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@id/fourth_row_guide_line"
app:layout_constraintStart_toEndOf="@id/first_column_guide_line"/>
</androidx.constraintlayout.widget.ConstraintLayout>