Android Content Provider
Content Provider 是 Android 中的一种组件,用于管理应用间的数据共享。它允许一个应用将其数据暴露给其他应用,也可以从其他应用中读取数据。通过 Content Provider,应用程序可以更方便地管理数据存储和数据访问,并且支持标准的数据库操作。
Content Provider 介绍
主要功能
- 数据共享: Content Provider 允许不同的应用程序共享数据。应用程序可以使用 Content Provider 访问另一个应用的数据库,前提是目标应用的 Content Provider 允许这样做。
- 统一接口: Content Provider 提供了一种统一的接口,使得数据访问操作(如查询、插入、更新和删除)不依赖于具体的数据存储方式或数据的实际格式。
- 数据同步: Content Provider 支持对数据的增、删、改、查操作,这些操作会自动更新所有使用该 Content Provider 的应用,确保数据的一致性和同步性。
主要组件
- Content URI: 每个 Content Provider 都有一个唯一的 Content URI,它是一个标识符,用于定位和访问 Content Provider 的数据。Content URI 是一个标准的 URI,例如
content://com.example.provider/contacts
。 - Content Resolver: 这是 Android 系统提供的一个类,用于访问 Content Provider。应用程序通过 Content Resolver 与 Content Provider 进行交互,执行如查询、插入、更新和删除等操作。
- Content Provider: 实现 Content Provider 的类,负责管理数据的具体操作。通常,Content Provider 需要实现
ContentProvider
类,并重写其方法,如query()
,insert()
,update()
,delete()
和getType()
。
主要操作
- 查询数据 (
query()
):- 使用 Content URI 和其他参数(如查询条件)来从 Content Provider 中检索数据。
- 插入数据 (
insert()
):- 向 Content Provider 中插入新的数据行。
- 更新数据 (
update()
):- 更新 Content Provider 中现有的数据行。
- 删除数据 (
delete()
):- 从 Content Provider 中删除数据行。
- 获取数据类型 (
getType()
):- 返回 Content URI 对应的数据类型 MIME 类型。
示例
假设你要在一个联系人应用程序中使用 Content Provider 来管理联系人数据
在AndroidManifest添加需要的权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
绘制管理界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contacts_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/add_contact_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add Contact" />
<Button
android:id="@+id/edit_contact_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Edit Contact" />
<Button
android:id="@+id/delete_contact_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Delete Contact" />
</LinearLayout>
其中使用了RecyclerView需要添加依赖
implementation 'androidx.recyclerview:recyclerview:1.1.0'
为RecyclerView添加适配器
public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ContactViewHolder> {
private final Cursor cursor;
public ContactsAdapter(Cursor cursor) {
this.cursor = cursor;
}
@NonNull
@Override
public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_2, parent, false);
return new ContactViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) {
if (cursor.moveToPosition(position)) {
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
holder.nameTextView.setText(displayName);
}
}
@Override
public int getItemCount() {
return cursor.getCount();
}
public static class ContactViewHolder extends RecyclerView.ViewHolder {
TextView nameTextView;
public ContactViewHolder(@NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(android.R.id.text1);
}
}
}
绑定适配器
private void loadContacts() {
Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
contactsAdapter = new ContactsAdapter(cursor);
contactsRecyclerView.setAdapter(contactsAdapter);
}
activity
public class ContactsActivity extends AppCompatActivity {
private static final int REQUEST_CONTACTS_PERMISSION = 1;
private static final int REQUEST_PICK_CONTACT_OF_UPDATE = 2;
private static final int REQUEST_PICK_CONTACT_OF_DELETE = 3;
private RecyclerView contactsRecyclerView;
private ContactsAdapter contactsAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts);
contactsRecyclerView = findViewById(R.id.contacts_recycler_view);
contactsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
Button addContactButton = findViewById(R.id.add_contact_button);
Button editContactButton = findViewById(R.id.edit_contact_button);
Button deleteContactButton = findViewById(R.id.delete_contact_button);
addContactButton.setOnClickListener(v -> addContact());
editContactButton.setOnClickListener(v -> pickContactForEditing());
deleteContactButton.setOnClickListener(v -> pickContactForDeletion());
//检查联系人权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CONTACTS_PERMISSION);
} else {
//读取联系人信息
loadContacts();
}
}
private void openAddMyDialogFrom() {
// 创建自定义对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View dialogView = getLayoutInflater().inflate(R.layout.my_dialog_layout, null);
builder.setView(dialogView);
EditText editName = dialogView.findViewById(R.id.edit_name);
EditText editPhone = dialogView.findViewById(R.id.edit_phone);
Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
Button btnCancel = dialogView.findViewById(R.id.btn_cancel);
AlertDialog dialog = builder.create();
btnConfirm.setOnClickListener(v -> {
String newName = editName.getText().toString();
String newPhone = editPhone.getText().toString();
addContact(newName, newPhone);
dialog.dismiss();
});
btnCancel.setOnClickListener(v -> dialog.dismiss());
dialog.show();
}
private void addContact(String name, String phone) {
//用来存储原始联系人的数据。
ContentValues values = new ContentValues();
//联系人账户类型的字段,通常用于指定该联系人是哪个账户,设置为 null 表示联系人没有指定账户或是本地联系人。
values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, (String) null);
//这是联系人账户名称的字段,通常用于指定该联系人是哪个账户,设置为 null 同样表示联系人没有指定账户或是本地联系人。
values.put(ContactsContract.RawContacts.ACCOUNT_NAME, (String) null);
//getContentResolver().insert(): 通过 ContentResolver 的 insert() 方法将数据插入到指定的内容 URI 中。
//ContactsContract.RawContacts.CONTENT_URI: 这是原始联系人数据的内容 URI,表示联系人记录的表。插入数据时使用此 URI。
//insert() 方法会返回一个 Uri 对象,表示新插入记录的 URI。这个 URI 包含了新记录的唯一标识符(ID)。
Uri rawContactUri = getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values);
//从返回的 Uri 中提取路径的最后一部分,这部分是新插入记录的 ID。
long rawContactId = Long.parseLong(rawContactUri.getLastPathSegment());
// 添加联系人姓名
//创建 ContentValues 对象: 用于存储将要插入的数据。
ContentValues nameValues = new ContentValues();
//设置联系人 ID: 将 rawContactId 作为原始联系人 ID。
nameValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
//设置 MIME 类型: 指定数据的类型为联系人姓名。
nameValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
//设置姓名: 将用户输入的姓名作为联系人姓名存储。
nameValues.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
//插入数据: 将 ContentValues 插入到 ContactsContract.Data.CONTENT_URI 指定的内容 URI 中。
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, nameValues);
// 添加联系人电话
ContentValues phoneValues = new ContentValues();
phoneValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
phoneValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
phoneValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
phoneValues.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, phoneValues);
loadContacts();
}
private void openUpdateMyDialogFrom(String contactId) {
//定义查询的列
//ContactsContract.Data.MIMETYPE: 数据的 MIME 类型,用于确定数据的具体类型。
//ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME: 联系人的显示名称。
//ContactsContract.CommonDataKinds.Phone.NUMBER: 联系人的电话号码。
String[] projection = new String[]{
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
};
// 查询姓名
//getContentResolver().query(): 用于查询内容提供者的 query() 方法。这会返回一个 Cursor 对象,表示查询结果。
//ContactsContract.Data.CONTENT_URI: 查询的内容 URI,表示联系人数据表。
//projection: 查询中需要返回的列。
//ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?": 查询的 selection 条件。这里是查询条件,? 是占位符。
//new String[]{contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}: selection 条件中的参数替换占位符。contactId 是指定的联系人的 ID,ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE 指定查询姓名数据。
//null: 排序条件,这里没有排序。
Cursor nameCursor = getContentResolver().query(
ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
new String[]{contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE},
null
);
//从 Cursor 对象中提取联系人的姓名
String name = null;
if (nameCursor != null) {
if (nameCursor.moveToFirst()) {
//nameCursor: 这是一个 Cursor 对象,表示查询结果集。它包含了从内容提供者中检索到的数据。
//getColumnIndex(String columnName): 这个方法用于获取指定列的索引(即列的位置)。columnName 是你要检索的列的名称。
//ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME: 这是列名,表示联系人的显示名称。你通过这个列名来查询列的索引。
name = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME));
}
nameCursor.close();
}
// 查询电话号码
Cursor phoneCursor = getContentResolver().query(
ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
new String[]{contactId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE},
null
);
String phone = null;
if (phoneCursor != null) {
if (phoneCursor.moveToFirst()) {
phone = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
}
phoneCursor.close();
}
// 显示或使用联系人信息
if (name != null && phone != null) {
Toast.makeText(this, "Name: " + name + "\nPhone: " + phone, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "No details found", Toast.LENGTH_SHORT).show();
}
// 创建自定义对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View dialogView = getLayoutInflater().inflate(R.layout.my_dialog_layout, null);
builder.setView(dialogView);
EditText editName = dialogView.findViewById(R.id.edit_name);
EditText editPhone = dialogView.findViewById(R.id.edit_phone);
Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
Button btnCancel = dialogView.findViewById(R.id.btn_cancel);
if (name != null) {
editName.setText(name);
}
if (phone != null) {
editPhone.setText(phone);
}
AlertDialog dialog = builder.create();
btnConfirm.setOnClickListener(v -> {
String newName = editName.getText().toString();
String newPhone = editPhone.getText().toString();
//去修改联系人信息
updateContact(contactId, newName, newPhone);
dialog.dismiss();
});
btnCancel.setOnClickListener(v -> dialog.dismiss());
dialog.show();
}
private void updateContact(String contactId, String newName, String newPhone) {
// 更新姓名
ContentValues nameValues = new ContentValues();
nameValues.put(ContactsContract.Data.RAW_CONTACT_ID, contactId);
nameValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
nameValues.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, newName);
// 更新电话号码
ContentValues phoneValues = new ContentValues();
phoneValues.put(ContactsContract.Data.RAW_CONTACT_ID, contactId);
phoneValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
phoneValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, newPhone);
//ContactsContract.CommonDataKinds.Phone.TYPE: 这是一个列名,表示电话号码的类型。例如,家庭、工作、移动等。
//ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: 这是一个常量,表示电话号码的类型是“移动电话”。这使得更新后的电话号码被标记为移动号码。
phoneValues.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
// 使用 ContentResolver 更新数据
getContentResolver().update(
ContactsContract.Data.CONTENT_URI,
nameValues,
ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
new String[]{contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}
);
//where条件改为 ContactsContract.Data.RAW_CONTACT_ID + " = ? ",new String[]{contactId} 同一个联系人下有多个联系方式时会出现混乱
//唯一性: 如果你只用 RAW_CONTACT_ID,可能会更新与该联系人 ID 相关的所有数据类型(如姓名、电话号码、电子邮件等),而不仅仅是电话号码。这会导致数据混乱,尤其是在联系人有多个电话号码或不同类型的联系方式时。
//准确性: 加上 MIMETYPE 条件可以确保你只更新特定类型的数据(如电话号码),而不会影响同一联系人的其他信息。
getContentResolver().update(
ContactsContract.Data.CONTENT_URI,
phoneValues,
ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
new String[]{contactId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}
);
// 重新加载联系人
loadContacts();
}
//绑定recyclerview
private void loadContacts() {
Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
contactsAdapter = new ContactsAdapter(cursor);
contactsRecyclerView.setAdapter(contactsAdapter);
}
//打开系统新建联系人界面
private void openContactCreation() {
Intent intent = new Intent(Intent.ACTION_INSERT);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivity(intent);
}
private void addContact() {
//二选一
//使用系统自带方法添加联系人
// openContactCreation();
//使用自定义对话框添加联系人
openAddMyDialogFrom();
}
private void pickContactForEditing() {
//Intent.ACTION_PICK 是一个动作常量,表示用户要选择某项数据(在这里是联系人)。
//ContactsContract.Contacts.CONTENT_URI 是联系人内容提供者的 URI(统一资源标识符),它指向所有联系人数据的集合。
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, REQUEST_PICK_CONTACT_OF_UPDATE);
}
private void pickContactForDeletion() {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, REQUEST_PICK_CONTACT_OF_DELETE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_PICK_CONTACT_OF_UPDATE && resultCode == RESULT_OK && data != null) {
//从 Intent 对象中获取 Uri。这个 Uri 代表了用户选择的联系人的数据。
Uri contactUri = data.getData();
//从 Uri 中提取最后一部分,通常是联系人的唯一 ID。
String contactId = contactUri.getLastPathSegment();
//使用自定义修改联系人
openUpdateMyDialogFrom(contactId);
// // 使用意图打开联系人编辑界面
// Intent intent = new Intent(Intent.ACTION_EDIT);
// intent.setData(ContactsContract.Contacts.CONTENT_URI.buildUpon().appendPath(contactId).build());
// startActivity(intent);
} else if (requestCode == REQUEST_PICK_CONTACT_OF_DELETE && resultCode == RESULT_OK && data != null) {
Uri contactUri = data.getData();
String contactId = contactUri.getLastPathSegment();
// 删除联系人的具体操作
//ContactsContract.RawContacts.CONTENT_URI: 这是联系人内容提供者的 URI,表示原始联系人数据的集合。它是用来访问和操作联系人的基础 URI。
//buildUpon(): 创建一个 Uri.Builder 对象,用于修改或构建新的 URI。
//appendPath(contactId): 在现有 URI 的末尾追加联系人 ID。这样可以指定要操作的特定联系人。例如,如果原始 URI 是 content://com.android.contacts/raw_contacts,追加 contactId 后的 URI 可能是 content://com.android.contacts/raw_contacts/123,其中 123 是联系人 ID。
//build(): 生成修改后的 URI。在这个例子中,最终的 URI 表示要删除的特定联系人。
Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().appendPath(contactId).build();
int rowsDeleted = getContentResolver().delete(deleteUri, null, null);
if (rowsDeleted > 0) {
Toast.makeText(this, "成功删除该联系人", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "联系人删除失败", Toast.LENGTH_SHORT).show();
}
// 更新联系人列表
loadContacts();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//判断请求码
if (requestCode == REQUEST_CONTACTS_PERMISSION) {
//返回权限请求结果
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//加载联系人数据
loadContacts();
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
}
}
}
}
总结
Content Provider 是 Android 平台提供的强大机制,允许应用程序进行跨进程的数据共享和管理。它通过提供一个统一的接口来简化数据的访问,同时确保数据的完整性和一致性。理解和有效使用 Content Provider 是开发 Android 应用程序的重要技能,特别是在涉及到多应用数据交互和共享的场景中。
标签:CONTENT,ContactsContract,联系人,URI,Content,Provider,Android,Data From: https://www.cnblogs.com/20lxj666/p/18400131