目录
1.内容观察者概念
1. 什么是 ContentObserver?
ContentObserver
是一个抽象类,开发者需要继承这个类并实现其中的方法来定义当被观察的数据发生改变时应该采取的动作。它是用来监控内容提供者(Content Provider)所提供的数据变化的工具。
2. 主要方法
- 构造函数:
ContentObserver(Handler handler)
- 接收一个Handler
对象,该对象指定了回调方法将运行在哪一个线程上。 - onChange(boolean selfChange) (API level < 16) / onChange(boolean selfChange, Uri uri) (API level >= 16):当被观察的数据发生变化时调用此方法。从 API 级别 16 开始,提供了带有
Uri
参数的版本,可以知道具体是哪个Uri
发生了变化。
3. 使用场景
- 监听系统联系人的更改。
- 监听短信的发送和接收。
- 监听媒体库中文件的添加或删除。
- 监听其他应用提供的数据变化。
4. 工作原理
当你注册了一个 ContentObserver
到 ContentResolver
上时,你实际上是告诉系统你对某个特定 Uri
的数据感兴趣。当那个 Uri
所代表的数据发生变化时,系统会触发 onChange()
方法,并且如果你使用的是 API 级别 16 或以上,还会传递具体的 Uri
给你的 onChange
方法。
5. 注册和注销
- 注册:通过
ContentResolver
的registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer)
方法来注册ContentObserver
。notifyForDescendants
参数指定是否也通知子Uri
的变化。 - 注销:通过
unregisterContentObserver(ContentObserver observer)
方法来注销不再需要的ContentObserver
。
6. 实现步骤
- 创建一个继承自
ContentObserver
的类。 - 重写
onChange()
方法来处理数据变化。 - 在适当的时机(如 Activity 的
onResume()
方法中),通过ContentResolver
注册ContentObserver
。 - 当不需要监听时(如 Activity 的
onPause()
方法中),记得注销ContentObserver
。
7. 注意事项
onChange
方法默认是在主线程上执行的,所以不要在这个方法里执行耗时操作。- 如果你需要在后台线程上处理
onChange
事件,可以在创建ContentObserver
时传入一个关联到后台线程的Handler
。 - 为了防止内存泄漏,请确保在适当的时候注销
ContentObserver
。
2.创建内容观察者
创建一个类继承ContentObserver类,重写构造和onChange()方法
package com.xiji.mydatabase; import android.database.ContentObserver; import android.os.Handler; public class MyObserver extends ContentObserver { public MyObserver(Handler handler) { super(handler); } //数据发生改变时做的事情 @Override public void onChange(boolean selfChange) { super.onChange(selfChange); } }
3.注册内容观察者
public void registerObserver(){ //获取观察者对象 ContentResolver contentResolver = getContentResolver(); //获取uri 感兴趣的uri Uri uri = Uri.parse("content://com.xiji.mydatabase.provider/user"); //注册内容观察者 contentResolver.registerContentObserver(uri, true, new MyObserver(new Handler())); }
4.取消注册内容观察者
ContentResolver contentResolver = getContentResolver(); contentResolver.unregisterContentObserver(new MyObserver(new Handler()));
5.完整的activity代码
package com.xiji.mydatabase; import android.content.ContentResolver; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); } public void registerObserver(){ //获取观察者对象 ContentResolver contentResolver = getContentResolver(); //获取uri 感兴趣的uri Uri uri = Uri.parse("content://com.xiji.mydatabase.provider/user"); //注册内容观察者 contentResolver.registerContentObserver(uri, true, new MyObserver(new Handler())); } @Override protected void onDestroy() { super.onDestroy(); ContentResolver contentResolver = getContentResolver(); contentResolver.unregisterContentObserver(new MyObserver(new Handler())); } }
6.案例:监听数据库
1)创建一个Android
2)创建数据库
MyDbHelper类
package com.xiji.myusedatabase.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class MyDbHelper extends SQLiteOpenHelper { public MyDbHelper(Context context) { //上下文路径 ======= 数据库名 super(context, "mydb.db", null, 1); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { //创建一个数据库 user表 sqLiteDatabase.execSQL("create table user(id integer primary key autoincrement,name varchar(20),age integer)"); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { //数据库更新操作 } }
3)创建内容提供者
这个主要用封装操作数据库的uri,个人感觉有点类似后端数据接口的封装,只需向指定的uri发送响应的参数就可以调用数据库了
package com.xiji.myusedatabase.db; 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 com.google.android.material.internal.ContextUtils; public class MyContentProvider extends ContentProvider { //定义请求路径匹配器 private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);; //成功状态码 public static final int SUCCESS = 1; //操作数据库对象 private MyDbHelper dbHelper; static { //数据库包路径 表名 请求码 uriMatcher.addURI("com.xiji.myusedatabase.db", "user", SUCCESS); } public MyContentProvider() { } /** * 向外暴漏删除数据的uri * * **/ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // Implement this to handle requests to delete one or more rows. int code = uriMatcher.match(uri); if(code == SUCCESS) { //获取操作对象 SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase(); //数据发生改变时需要通知的观察者 getContext().getContentResolver().notifyChange(uri, null); // 执行删除 第一个参数 表名 第二个参数:条件 第三个参数:条件值 int intRows = writableDatabase.delete("user", selection, selectionArgs); if(intRows > 0) { Uri deleteUri = ContentUris.withAppendedId(uri, intRows); //通知数据库变更 getContext().getContentResolver().notifyChange(deleteUri, null); } return intRows; }else{ throw new UnsupportedOperationException("路径不匹配删除失败"); } } @Override public String getType(Uri uri) { // TODO: Implement this to handle requests for the MIME type of the data // at the given URI. throw new UnsupportedOperationException("Not yet implemented"); } /** * 向外暴漏插入数据库的uri * * */ @Override public Uri insert(Uri uri, ContentValues values) { int code = uriMatcher.match(uri); if(code == SUCCESS) { //获取操作对象 SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase(); // 执行插入 第一个参数:表名 第二个参数:插入的数据 long user = writableDatabase.insert("user", null, values); if (user > 0) { Uri insertedUri = ContentUris.withAppendedId(uri, user); //通知数据库变更 getContext().getContentResolver().notifyChange(insertedUri, null); } //返回插入数据的uri return uri; }else{ throw new UnsupportedOperationException("路径不匹配插入失败"); } } @Override public boolean onCreate() { //创建操作数据库的工具 dbHelper = new MyDbHelper(getContext()); return false; } /** * 向外暴漏查询数据库的uri ,供其他应用操作数据库 */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 判断请求码 int code = uriMatcher.match(uri); if(code == SUCCESS) { //获取操作对象 SQLiteDatabase readableDatabase = dbHelper.getReadableDatabase(); // 执行查询 // 参数1:表名 参数2:查询字段 参数3:条件 参数4:条件值 条件五:排序 返回值:游标 return readableDatabase.query("user", projection, selection, selectionArgs, null, null, sortOrder); }else{ throw new UnsupportedOperationException("路径不匹配查询失败"); } } /** * 向外暴露更新数据库的uri * * */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 更新方法 int code = uriMatcher.match(uri); if(code == SUCCESS) { //获取操作对象 SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase(); // 执行更新 第一个参数:表名 第二个参数:更新数据 第三个参数:条件 第四个参数:条件值 int updateRows = writableDatabase.update("user", values, selection, selectionArgs); if(updateRows > 0) { Uri updateUri = ContentUris.withAppendedId(uri, updateRows); //通知数据库变更 getContext().getContentResolver().notifyChange(updateUri, null); } return updateRows; }else{ throw new UnsupportedOperationException("路径不匹配更新失败"); } } }
4)编写activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?> <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:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="操作privoder" android:textSize="30dp" android:gravity="center" android:background="#3FFACD" android:padding="20dp" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="添加数据" android:id="@+id/add" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="查询数据" android:id="@+id/query" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="删除数据" android:id="@+id/delete" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新数据" android:id="@+id/update" /> </LinearLayout>
5)编写MainActivity
package com.xiji.myusedatabase; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Bundle; import android.widget.Button; import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.xiji.myusedatabase.db.MyContentProvider; import com.xiji.myusedatabase.db.MyDbHelper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class MainActivity extends AppCompatActivity { //定义四个按钮 private Button mBtnAdd; private Button mBtnQuery; private Button mBtnDelete; private Button mBtnUpdate; //获取信息Resolver private ContentResolver resolver; //Uri private Uri uri; //添加数据用的变量 ContentValues values; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); //初始化控件 initView(); initEvent(); initDatabase(); } //控件初始化 private void initView() { mBtnAdd = findViewById(R.id.add); mBtnQuery = findViewById(R.id.query); mBtnDelete = findViewById(R.id.delete); mBtnUpdate = findViewById(R.id.update); } //事件 private void initEvent() { //获取内容提供者 resolver = getContentResolver(); //获取uri uri = Uri.parse("content://com.xiji.myusedatabase.db/user"); //接受键值对的数据 values = new ContentValues(); mBtnAdd.setOnClickListener(v -> { //添加数据 values.put("name", "xiji"); values.put("age", 18); resolver.insert(uri, values); values.clear(); Toast.makeText(MainActivity.this, "添加成功", Toast.LENGTH_SHORT).show(); }); mBtnQuery.setOnClickListener(v -> { //查询数据 Cursor query = resolver.query(uri, null, null, null, null); values.clear(); HashMap<String, String> stringStringHashMap = new HashMap<>(); List<HashMap> list = new ArrayList<>(); while (query.moveToNext()) { @SuppressLint("Range") String name = query.getString(query.getColumnIndex("name")); @SuppressLint("Range") int age = query.getInt(query.getColumnIndex("age")); stringStringHashMap.put(name, String.valueOf(age)); System.out.println(name + ":" + age); list.add(stringStringHashMap); } Toast.makeText(MainActivity.this, "查询成功"+list.toString(), Toast.LENGTH_SHORT).show(); }); mBtnDelete.setOnClickListener(v -> { resolver.delete(uri, null, null); values.clear(); Toast.makeText(MainActivity.this, "删除成功", Toast.LENGTH_SHORT).show(); //删除数据 }); mBtnUpdate.setOnClickListener(v -> { values.put("age", 19); resolver.update(uri, values, null, null); values.clear(); Toast.makeText(MainActivity.this, "修改成功", Toast.LENGTH_SHORT).show(); //修改数据 }); } //创建数据库 private void initDatabase() { //创建数据库 MyDbHelper myDbHelper = new MyDbHelper(this); SQLiteDatabase writableDatabase = myDbHelper.getWritableDatabase(); //数据库初始化并且为数据库赋值 resolver = getContentResolver(); for (int i = 0; i < 10; i++) { values.put("name", "xiji" + i); values.put("age", 18 + i); resolver.insert(uri, values); } } }
6)修改AndroidManifest.xml文件中的内容
<?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.MyUseDatabase" tools:targetApi="31"> <provider android:name=".db.MyContentProvider" android:authorities="com.xiji.myusedatabase.db" android:enabled="true" android:exported="true"></provider> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
7)测试
启动效果
8)创建第二个程序MyListener
9)创建MyObserver类
package com.xiji.mylistener; import android.database.ContentObserver; import android.os.Handler; import android.util.Log; public class MyObserver extends ContentObserver { public MyObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { Log.i("监控数据库的user表变化", "数据库被人动了"); super.onChange(selfChange); } }
10)编写MainActivity代码
package com.xiji.mylistener; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; public class MainActivity extends AppCompatActivity { private MyObserver myObserver;; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); //初始化时注册 myObserver = new MyObserver(new Handler()); //指向数据库user表 Uri uri = Uri.parse("content://com.xiji.myusedatabase.db/user"); //观察者注册 getContentResolver().registerContentObserver(uri, true, myObserver); } @Override protected void onDestroy() { super.onDestroy(); //观察者取消注册 getContentResolver().unregisterContentObserver(myObserver); } }
11)效果图
7.如何SQLite查看数据
1--打开右边的DeviceManger ==》三个点号 ===》Open in device Explorer
2--data===>data ==> 自己应用程序的包名 ===》databases ===>mydb.db ===>鼠标右键 ===》save as ==>选中保存路径===》保存就可以了
使用navicat打开,就可以看到里面的数据了
标签:public,uri,Uri,效果图,new,import,Android,监听,android From: https://blog.csdn.net/2301_76862031/article/details/142904972