Android content providers and content observers: the perfect combination of data sharing and real-time updates

Mission requirements

  • One as a ContentProvider providing contact data
  • Another one acts as an Observer to monitor changes in contact data:

1. Create the ContactProvider project;

2. Use the Sqlite database in the ContactProvider project to implement the reading and writing functions of contacts;

3. Provide contact data through ContentProvider in the ContactProvider project;

4. Create the ContactObserver project;

5. Register ContentObserver in the ContactObserver project to monitor changes in the contact database.


When creating an Android contact application, including a ContactProvider project and a ContactObserver project, you need to implement the add, delete, modify, and query methods as well as the layout files of the two pages. Here are the steps in more detail:

Step 1: Create ContactProvider project

  1. Create a new Android project named ContactProvider.

  2. In the ContactProvider project, create a Java class named ContactContract that defines the database table structure and the URI of the content provider.

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";
    }
}}
  1. Create a database helper class named ContactDbHelper for creating and managing contact databases.
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);
    }
}
  1. Create a content provider class named ContactProvider to implement the function of adding, deleting, modifying and checking contact data.

“New” —> “Other” —> “Cotent Provider”
Insert image description here
Insert image description here

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;
    }

    // ...
}
  1. UriMatcher: UriMatcher is used to match incoming URIs to determine which operation to perform (for example, query, insert, delete, or update) . Your code initializes uriMatcher using a static initialization block and assigns constant integers to two different types of URIs ("contacts" and "contacts/#").

  2. onCreate method: This is the initialization method of the content provider. Here, you initialize the database helper class (ContactDbHelper) and associate it with the content provider. Content provider initialization is done when the application starts.

  3. insert method: This is the method used to insert data. When an application inserts new data through a content provider, the insert method opens the database, performs the insertion, and notifies any content observers interested in the data using notifyChange .

  4. delete method: This method is used to delete data. It checks the URI and then performs the delete operation based on the type of URI. If some rows are successfully deleted, it notifies content observers using notifyChange.

  5. update method: used to update data. Similar to the delete method, it checks the URI, performs the appropriate update operation, and notifies content observers using notifyChange.

  6. query method: used to query data. This is the most common method used by content providers to retrieve data. It performs a query based on the incoming URI and then notifies the associated content observer using setNotificationUri.

  7. setNotificationUri: This method associates query results with a specific URI to notify observers when data changes.

This code covers the core functionality of the content provider, including handling insert, delete, update, and query operations, as well as notifying relevant content observers when these operations are completed. Content providers allow applications to share data and enable real-time data updates through the content observer pattern.

  1. Create layout file -contact_provider
    Insert image description here
<?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>

achieve effect
Insert image description here

Add the appropriate functionality for each button in the ContactProvider project.

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());
            }
        });
    }
}


Overall implementation effect
Please add image description

Step 2: Create ContactObserver project

  1. Create a new Android project and name it ContactObserver.

  2. Create activity_main.xml layout file to display the monitored content.

<?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>

Insert image description here

  1. Create MainActivity.java to display the monitored content.
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);
    }

}


Here are the key parts explained and highlighted:

  1. ContentObserver:ContentObserver is a class used to monitor data changes on a specific URI. In the code, we create an instance of ContentObserver to monitor data changes of a specific content provider.

  2. onChange method: onChange method is the callback method of ContentObserver, when the data of the monitored URI changes. is called. Within this method, you can handle the logic of data changes. In the code, the change of URI and the value of are recorded through Log. selfChange

  3. onResume method: In the onResume method, ContentObserver is registered to listen for content changes on a specific URI . This way, ContentObserver will only take effect when the activity is in the foreground.

  4. onDestroy method: In the onDestroy method, the registration of ContentObserver is cancelled. Because there is no need to listen for content changes when the activity is destroyed. By unregistering, you avoid potential memory leaks.

Working principle

  • When the content provider's data changes, the content provider will internally call the ContentResolver's notifyChange method and notify all registered listeners /span>ContentObserver.
  • ContentObserver will trigger theonChange callback method, which includes the URI of the change and a flag indicating whether the change was initiated by itself (selfChange).
  • You can handle data changes in theonChange method, such as updating the UI or recording logs.
  1. Modify configuration file

Add query listening authorities

<?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>

Make sure your ContactObserverService properly registers and unregisters ContentObserver, and sends and receives broadcast messages.

achieve effect

The provider modifies the data, and the observer monitors the data modification.

Please add image description

Guess you like

Origin blog.csdn.net/qq_22841387/article/details/133708103