ミッション要件
- 1 つは連絡先データを提供する ContentProvider として
- もう 1 つは、連絡先データの変更を監視するオブザーバーとして機能します。
1. ContactProvider プロジェクトを作成します。
2. ContactProvider プロジェクトの Sqlite データベースを使用して、連絡先の読み取りおよび書き込み機能を実装します。
3. ContactProvider プロジェクトの ContentProvider を通じて連絡先データを提供します。
4. ContactObserver プロジェクトを作成します。
5. ContentObserver を ContactObserver プロジェクトに登録して、連絡先データベースの変更を監視します。
ContactProvider プロジェクトや ContactObserver プロジェクトなどの Android 連絡先アプリケーションを作成する場合は、add、delete、modify、query メソッドと 2 つのページのレイアウト ファイルを実装する必要があります。詳しい手順は次のとおりです。
ステップ 1: ContactProvider プロジェクトを作成する
-
ContactProvider という名前の新しい Android プロジェクトを作成します。
-
ContactProvider プロジェクトで、データベース テーブル構造とコンテンツ プロバイダーの URI を定義する ContactContract という名前の Java クラスを作成します。
package com.leo.contactprovider;
import android.net.Uri;
import android.provider.BaseColumns;
public class ContactContract {
public static final String AUTHORITY = "com.leo.contactprovider";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + AUTHORITY);
public static final String PATH_CONTACTS = "contacts";
public static final class ContactEntry implements BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_CONTACTS).build();
public static final String TABLE_NAME = "contacts";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_PHONE = "phone";
}
}}
- 連絡先データベースを作成および管理するための ContactDbHelper という名前のデータベース ヘルパー クラスを作成します。
package com.example.contactprovider;
package com.leo.contactprovider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class ContactDbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "contacts.db";
private static final int DATABASE_VERSION = 1;
public ContactDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
final String SQL_CREATE_CONTACTS_TABLE = "CREATE TABLE " +
ContactContract.ContactEntry.TABLE_NAME + " (" +
ContactContract.ContactEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
ContactContract.ContactEntry.COLUMN_NAME + " TEXT NOT NULL, " +
ContactContract.ContactEntry.COLUMN_PHONE + " TEXT NOT NULL" +
");";
db.execSQL(SQL_CREATE_CONTACTS_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + ContactContract.ContactEntry.TABLE_NAME);
onCreate(db);
}
}
- ContactProvider という名前のコンテンツ プロバイダー クラスを作成し、連絡先データの追加、削除、変更、確認の機能を実装します。
「新規」—> 「その他」—> 「コンテンツプロバイダー」
package com.leo.contactprovider;
import static com.leo.contactprovider.ContactContract.AUTHORITY;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class ContactProvider extends ContentProvider {
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Define integer constants for the URIs
private static final int CONTACTS = 100;
private static final int CONTACT_ID = 101;
static {
uriMatcher.addURI(AUTHORITY, "contacts", CONTACTS);
uriMatcher.addURI(AUTHORITY, "contacts/#", CONTACT_ID);
}
private ContactDbHelper dbHelper;
@Override
public boolean onCreate() {
dbHelper = new ContactDbHelper(getContext());
return true;
}
// 实现数据的增删改查方法
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
long id = db.insert(ContactContract.ContactEntry.TABLE_NAME, null, values);
if (id > 0) {
getContext().getContentResolver().notifyChange(uri, null);
return ContactContract.ContactEntry.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build();
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int rowsDeleted;
switch (uriMatcher.match(uri)) {
case CONTACTS:
rowsDeleted = db.delete(ContactContract.ContactEntry.TABLE_NAME, selection, selectionArgs);
break;
case CONTACT_ID:
String contactId = uri.getLastPathSegment();
rowsDeleted = db.delete(ContactContract.ContactEntry.TABLE_NAME,
ContactContract.ContactEntry._ID + "=?", new String[]{
contactId});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if (rowsDeleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int rowsUpdated;
switch (uriMatcher.match(uri)) {
case CONTACTS:
rowsUpdated = db.update(ContactContract.ContactEntry.TABLE_NAME, values, selection, selectionArgs);
break;
case CONTACT_ID:
String contactId = uri.getLastPathSegment();
rowsUpdated = db.update(ContactContract.ContactEntry.TABLE_NAME, values,
ContactContract.ContactEntry._ID + "=?", new String[]{
contactId});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if (rowsUpdated > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsUpdated;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor;
switch (uriMatcher.match(uri)) {
case CONTACTS:
cursor = db.query(ContactContract.ContactEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
break;
case CONTACT_ID:
String contactId = uri.getLastPathSegment();
cursor = db.query(ContactContract.ContactEntry.TABLE_NAME, projection,
ContactContract.ContactEntry._ID + "=?", new String[]{
contactId}, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
// Set notification URI on the cursor
cursor.setNotificationUri(getContext().getContentResolver(), uri); // 添加这行代码
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
// ...
}
-
UriMatcher:
UriMatcher
は、受信 URI を照合して実行する操作 (クエリ、挿入、削除、更新など) を決定するために使用されます。 。コードは、静的初期化ブロックを使用してuriMatcher
を初期化し、2 つの異なるタイプの URI (「contacts」と「contacts/#」) に定数の整数を割り当てます。 -
onCreate メソッド: これはコンテンツ プロバイダーの初期化メソッドです。ここでは、データベース ヘルパー クラス (
ContactDbHelper
) を初期化し、それをコンテンツ プロバイダーに関連付けます。コンテンツプロバイダーの初期化は、アプリケーションの起動時に行われます。 -
insert メソッド: これはデータの挿入に使用されるメソッドです。アプリケーションがコンテンツ プロバイダーを通じて新しいデータを挿入すると、
insert
メソッドはデータベースを開いて挿入を実行し、notifyChange
を使用してデータに関心のあるコンテンツ オブザーバーに通知します。 -
delete メソッド: このメソッドはデータを削除するために使用されます。 URI をチェックし、URI のタイプに基づいて削除操作を実行します。一部の行が正常に削除されると、
notifyChange
を使用してコンテンツ オブザーバーに通知します。 -
update メソッド: データの更新に使用されます。
delete
メソッドと同様に、URI をチェックし、適切な更新操作を実行し、notifyChange
を使用してコンテンツ オブザーバーに通知します。 -
クエリ メソッド: データのクエリに使用されます。これは、コンテンツ プロバイダーがデータを取得するために使用する最も一般的な方法です。受信 URI に基づいてクエリを実行し、
setNotificationUri
を使用して関連するコンテンツ オブザーバーに通知します。 -
setNotificationUri: このメソッドはクエリ結果を特定の URI に関連付け、データが変更されたときにオブザーバーに通知します。
このコードは、挿入、削除、更新、クエリ操作の処理や、これらの操作の完了時の関連コンテンツ オブザーバーへの通知など、コンテンツ プロバイダーのコア機能をカバーしています。コンテンツ プロバイダーを使用すると、アプリケーションがデータを共有し、コンテンツ オブザーバー パターンを通じてリアルタイムのデータ更新が可能になります。
- レイアウトファイルの作成 -
contact_provider
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Contact"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true" />
<Button
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Contact"
android:layout_below="@+id/btnAdd"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true" />
<Button
android:id="@+id/btnUpdate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Update Contact"
android:layout_below="@+id/btnDelete"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true" />
<Button
android:id="@+id/btnQuery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Query Contacts"
android:layout_below="@+id/btnUpdate"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true" />
<TextView
android:id="@+id/textViewResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnQuery"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:textSize="20sp"
/>
</RelativeLayout>
効果を達成する
ContactProvider
プロジェクトの各ボタンに適切な機能を追加します。
package com.leo.contactprovider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private TextView textViewResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.contact_provider);
textViewResult = findViewById(R.id.textViewResult);
Button btnAdd = findViewById(R.id.btnAdd);
Button btnDelete = findViewById(R.id.btnDelete);
Button btnUpdate = findViewById(R.id.btnUpdate);
Button btnQuery = findViewById(R.id.btnQuery);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 添加联系人示例
ContentValues values = new ContentValues();
values.put(ContactContract.ContactEntry.COLUMN_NAME, "John Doe");
values.put(ContactContract.ContactEntry.COLUMN_PHONE, "123-456-7890");
Uri insertUri = getContentResolver().insert(ContactContract.ContactEntry.CONTENT_URI, values);
textViewResult.setText("Contact added with URI: " + insertUri.toString());
}
});
btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 删除联系人示例
String selection = ContactContract.ContactEntry.COLUMN_NAME + " = ?";
String[] selectionArgs = {
"John Doe"};
int deletedRows = getContentResolver().delete(ContactContract.ContactEntry.CONTENT_URI, selection, selectionArgs);
textViewResult.setText("Deleted " + deletedRows + " contacts.");
}
});
btnUpdate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 更新联系人示例
ContentValues values = new ContentValues();
values.put(ContactContract.ContactEntry.COLUMN_PHONE, "987-654-3210");
String selection = ContactContract.ContactEntry.COLUMN_NAME + " = ?";
String[] selectionArgs = {
"John Doe"};
int updatedRows = getContentResolver().update(ContactContract.ContactEntry.CONTENT_URI, values, selection, selectionArgs);
textViewResult.setText("Updated " + updatedRows + " contacts.");
}
});
btnQuery.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 查询联系人示例
String[] projection = {
ContactContract.ContactEntry._ID,
ContactContract.ContactEntry.COLUMN_NAME,
ContactContract.ContactEntry.COLUMN_PHONE
};
Cursor cursor = getContentResolver().query(ContactContract.ContactEntry.CONTENT_URI, projection, null, null, null);
StringBuilder result = new StringBuilder();
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactContract.ContactEntry.COLUMN_NAME));
String phone = cursor.getString(cursor.getColumnIndexOrThrow(ContactContract.ContactEntry.COLUMN_PHONE));
result.append("Name: ").append(name).append(", Phone: ").append(phone).append("\n");
}
cursor.close();
textViewResult.setText(result.toString());
}
});
}
}
全体的な導入効果
ステップ 2: ContactObserver プロジェクトを作成する
-
新しい Android プロジェクトを作成し、ContactObserver という名前を付けます。
-
監視対象のコンテンツを表示するためのレイアウト ファイルを 作成
activity_main.xml
します。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:padding="16dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/textViewObserver"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Content Observer Output" />
</RelativeLayout>
- 監視対象のコンテンツを表示するには、作成
MainActivity.java
します。
package com.leo.contactobserver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class ContactObserverActivity extends AppCompatActivity {
private TextView textViewObserver;
private ContentObserver contentObserver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textViewObserver = findViewById(R.id.textViewObserver);
// 创建 ContentObserver 实例
contentObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
// 处理内容变化时的逻辑
Log.i("Content Changed: ", "URI: " + uri.toString());
Log.i("Content Changed: ", "Self Change: " + selfChange);
// 你可以在这里更新 textViewObserver 中的内容
textViewObserver.setText("Content Changed: " + uri.toString());
}
};
}
@Override
protected void onResume() {
super.onResume();
// 注册 ContentObserver 监听内容变化
getContentResolver().registerContentObserver(
Uri.parse("content://com.leo.contactprovider/contacts"),
true, contentObserver);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 在活动销毁时取消注册 ContentObserver
getContentResolver().unregisterContentObserver(contentObserver);
}
}
以下に重要な部分を説明し、強調表示します。
-
ContentObserver:
ContentObserver
は、特定の URI でのデータ変更を監視するために使用されるクラスです。コードでは、ContentObserver
のインスタンスを作成して、特定のコンテンツ プロバイダーのデータ変更を監視します。 -
onChange メソッド:
onChange
メソッドは、監視対象の URI のデータが変更されたときのContentObserver
のコールバック メソッドです。 . と呼ばれます。このメソッド内で、データ変更のロジックを処理できます。コードでは、Log
を通じて URI の変更とselfChange
の値が記録されます。 -
onResume メソッド:
onResume
メソッドでは、特定の URI でコンテンツの変更をリッスンするためにContentObserver
が登録されます。 。こうすることで、ContentObserver
はアクティビティがフォアグラウンドにある場合にのみ有効になります。 -
onDestroy メソッド:
onDestroy
メソッドでは、ContentObserver
の登録がキャンセルされます。アクティビティが破棄されたときにコンテンツの変更をリッスンする必要がないためです。登録を解除すると、メモリ リークの可能性を回避できます。
工作原理:
- コンテンツ プロバイダのデータが変更されると、コンテンツ プロバイダは内部的に
ContentResolver
のnotifyChange
メソッドを呼び出し、登録されているすべてのリスナーに通知します /span>ContentObserver
。 ContentObserver
は、onChange
コールバック メソッドをトリガーします。これには、変更の URI と、変更が単独で開始されたかどうかを示すフラグ (selfChange
) が含まれます。- UI の更新やログの記録など、
onChange
メソッドでデータ変更を処理できます。
- 設定ファイルを変更する
クエリリスニング権限を追加する
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.DayNight"
tools:targetApi="31">
<activity
android:name=".ContactObserverActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.DayNight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<queries>
<provider android:authorities="com.leo.contactprovider"
android:exported="true" />
</queries>
</manifest>
ContactObserverService
が ContentObserver を適切に登録および登録解除し、ブロードキャスト メッセージを送受信していることを確認してください。
効果を達成する
プロバイダーはデータを変更し、オブザーバーはデータの変更を監視します。