全部笔记All notes

Android数据变化监听与ContentObserver实战指南

阅读 6m 13s6m 13s read

概述

在Android开发中,监听数据变化是实现响应式UI的关键技术。本指南深入介绍Android平台的数据监听机制,包括ContentObserver、LiveData、Room等现代化解决方案,帮助开发者构建数据驱动的应用。

学习目标

  • 掌握ContentProvider和ContentObserver机制
  • 理解Android数据观察者模式
  • 学习Room数据库的数据监听
  • 掌握LiveData的使用方法
  • 了解FileObserver文件系统监听
  • 掌握数据监听的性能优化

ContentProvider基础

ContentProvider是什么?

ContentProvider是Android四大组件之一,用于在不同应用之间共享数据。它提供了标准的接口来操作数据,并支持数据变化通知机制。

创建ContentProvider

// Java实现
public class MyContentProvider extends ContentProvider {
    
    private static final String AUTHORITY = "com.example.provider";
    private static final String TABLE_NAME = "users";
    
    public static final Uri CONTENT_URI = 
        Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
    
    private static final int USERS = 1;
    private static final int USER_ID = 2;
    
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    
    static {
        uriMatcher.addURI(AUTHORITY, TABLE_NAME, USERS);
        uriMatcher.addURI(AUTHORITY, TABLE_NAME + "/#", USER_ID);
    }
    
    private DatabaseHelper dbHelper;
    
    @Override
    public boolean onCreate() {
        dbHelper = new DatabaseHelper(getContext());
        return true;
    }
    
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                       String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        
        switch (uriMatcher.match(uri)) {
            case USERS:
                cursor = db.query(TABLE_NAME, projection, selection,
                    selectionArgs, null, null, sortOrder);
                break;
            case USER_ID:
                String id = uri.getLastPathSegment();
                cursor = db.query(TABLE_NAME, projection, "_id = ?",
                    new String[]{id}, null, null, sortOrder);
                break;
        }
        
        // 设置通知URI
        if (cursor != null) {
            cursor.setNotificationUri(getContext().getContentResolver(), uri);
        }
        
        return cursor;
    }
    
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        long id = db.insert(TABLE_NAME, null, values);
        
        if (id > 0) {
            Uri newUri = ContentUris.withAppendedId(CONTENT_URI, id);
            // 通知数据变化
            getContext().getContentResolver().notifyChange(newUri, null);
            return newUri;
        }
        
        return null;
    }
    
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                     String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;
        
        switch (uriMatcher.match(uri)) {
            case USERS:
                count = db.update(TABLE_NAME, values, selection, selectionArgs);
                break;
            case USER_ID:
                String id = uri.getLastPathSegment();
                count = db.update(TABLE_NAME, values, "_id = ?", new String[]{id});
                break;
        }
        
        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        
        return count;
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;
        
        switch (uriMatcher.match(uri)) {
            case USERS:
                count = db.delete(TABLE_NAME, selection, selectionArgs);
                break;
            case USER_ID:
                String id = uri.getLastPathSegment();
                count = db.delete(TABLE_NAME, "_id = ?", new String[]{id});
                break;
        }
        
        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        
        return count;
    }
    
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case USERS:
                return "vnd.android.cursor.dir/vnd.example.users";
            case USER_ID:
                return "vnd.android.cursor.item/vnd.example.user";
            default:
                return null;
        }
    }
}
// Kotlin实现
class MyContentProvider : ContentProvider() {
    
    companion object {
        private const val AUTHORITY = "com.example.provider"
        private const val TABLE_NAME = "users"
        
        val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/$TABLE_NAME")
        
        private const val USERS = 1
        private const val USER_ID = 2
        
        private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
            addURI(AUTHORITY, TABLE_NAME, USERS)
            addURI(AUTHORITY, "$TABLE_NAME/#", USER_ID)
        }
    }
    
    private lateinit var dbHelper: DatabaseHelper
    
    override fun onCreate(): Boolean {
        dbHelper = DatabaseHelper(context!!)
        return true
    }
    
    override fun query(
        uri: Uri,
        projection: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor? {
        val db = dbHelper.readableDatabase
        val cursor = when (uriMatcher.match(uri)) {
            USERS -> db.query(
                TABLE_NAME, projection, selection,
                selectionArgs, null, null, sortOrder
            )
            USER_ID -> {
                val id = uri.lastPathSegment
                db.query(
                    TABLE_NAME, projection, "_id = ?",
                    arrayOf(id), null, null, sortOrder
                )
            }
            else -> null
        }
        
        cursor?.setNotificationUri(context?.contentResolver, uri)
        return cursor
    }
    
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val db = dbHelper.writableDatabase
        val id = db.insert(TABLE_NAME, null, values)
        
        return if (id > 0) {
            val newUri = ContentUris.withAppendedId(CONTENT_URI, id)
            context?.contentResolver?.notifyChange(newUri, null)
            newUri
        } else {
            null
        }
    }
    
    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        val db = dbHelper.writableDatabase
        val count = when (uriMatcher.match(uri)) {
            USERS -> db.update(TABLE_NAME, values, selection, selectionArgs)
            USER_ID -> {
                val id = uri.lastPathSegment
                db.update(TABLE_NAME, values, "_id = ?", arrayOf(id))
            }
            else -> 0
        }
        
        if (count > 0) {
            context?.contentResolver?.notifyChange(uri, null)
        }
        
        return count
    }
    
    override fun delete(
        uri: Uri,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        val db = dbHelper.writableDatabase
        val count = when (uriMatcher.match(uri)) {
            USERS -> db.delete(TABLE_NAME, selection, selectionArgs)
            USER_ID -> {
                val id = uri.lastPathSegment
                db.delete(TABLE_NAME, "_id = ?", arrayOf(id))
            }
            else -> 0
        }
        
        if (count > 0) {
            context?.contentResolver?.notifyChange(uri, null)
        }
        
        return count
    }
    
    override fun getType(uri: Uri): String? {
        return when (uriMatcher.match(uri)) {
            USERS -> "vnd.android.cursor.dir/vnd.example.users"
            USER_ID -> "vnd.android.cursor.item/vnd.example.user"
            else -> null
        }
    }
}

在AndroidManifest.xml中注册

<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.provider"
    android:exported="false"
    android:grantUriPermissions="true" />

ContentObserver详解

ContentObserver基础

ContentObserver用于监听ContentProvider中数据的变化:

// Java实现
public class MyContentObserver extends ContentObserver {
    
    private Context context;
    private Handler handler;
    
    public MyContentObserver(Context context, Handler handler) {
        super(handler);
        this.context = context;
        this.handler = handler;
    }
    
    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        // 数据发生变化时调用
        Log.d("ContentObserver", "数据发生变化");
        // 处理数据变化
        handleDataChange();
    }
    
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        // 数据发生变化时调用(带URI)
        Log.d("ContentObserver", "数据发生变化: " + uri.toString());
        handleDataChange(uri);
    }
    
    private void handleDataChange() {
        // 查询最新数据
        Cursor cursor = context.getContentResolver().query(
            MyContentProvider.CONTENT_URI,
            null, null, null, null
        );
        
        if (cursor != null) {
            // 处理数据
            while (cursor.moveToNext()) {
                // 读取数据
            }
            cursor.close();
        }
    }
    
    private void handleDataChange(Uri uri) {
        // 根据URI处理特定数据变化
    }
}
// Kotlin实现
class MyContentObserver(
    private val context: Context,
    handler: Handler
) : ContentObserver(handler) {
    
    override fun onChange(selfChange: Boolean) {
        super.onChange(selfChange)
        Log.d("ContentObserver", "数据发生变化")
        handleDataChange()
    }
    
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        super.onChange(selfChange, uri)
        Log.d("ContentObserver", "数据发生变化: ${uri?.toString()}")
        uri?.let { handleDataChange(it) }
    }
    
    private fun handleDataChange() {
        context.contentResolver.query(
            MyContentProvider.CONTENT_URI,
            null, null, null, null
        )?.use { cursor ->
            while (cursor.moveToNext()) {
                // 读取数据
            }
        }
    }
    
    private fun handleDataChange(uri: Uri) {
        // 根据URI处理特定数据变化
    }
}

注册和注销ContentObserver

// Java
public class MainActivity extends AppCompatActivity {
    
    private MyContentObserver contentObserver;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 创建ContentObserver
        Handler handler = new Handler(Looper.getMainLooper());
        contentObserver = new MyContentObserver(this, handler);
        
        // 注册ContentObserver
        getContentResolver().registerContentObserver(
            MyContentProvider.CONTENT_URI,
            true, // notifyForDescendants
            contentObserver
        );
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销ContentObserver
        if (contentObserver != null) {
            getContentResolver().unregisterContentObserver(contentObserver);
        }
    }
}
// Kotlin
class MainActivity : AppCompatActivity() {
    
    private lateinit var contentObserver: MyContentObserver
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 创建ContentObserver
        val handler = Handler(Looper.getMainLooper())
        contentObserver = MyContentObserver(this, handler)
        
        // 注册ContentObserver
        contentResolver.registerContentObserver(
            MyContentProvider.CONTENT_URI,
            true, // notifyForDescendants
            contentObserver
        )
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // 注销ContentObserver
        contentResolver.unregisterContentObserver(contentObserver)
    }
}

数据变化监听实现

完整示例:联系人数据监听

class ContactsObserverActivity : AppCompatActivity() {
    
    private lateinit var contactsObserver: ContentObserver
    private lateinit var contactsAdapter: ContactsAdapter
    private val contacts = mutableListOf<Contact>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_contacts_observer)
        
        setupRecyclerView()
        setupContactsObserver()
        loadContacts()
        
        // 请求权限
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.READ_CONTACTS
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.READ_CONTACTS),
                PERMISSION_REQUEST_CODE
            )
        }
    }
    
    private fun setupRecyclerView() {
        contactsAdapter = ContactsAdapter(contacts)
        recyclerView.apply {
            layoutManager = LinearLayoutManager(this@ContactsObserverActivity)
            adapter = contactsAdapter
        }
    }
    
    private fun setupContactsObserver() {
        contactsObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
            override fun onChange(selfChange: Boolean) {
                super.onChange(selfChange)
                Log.d(TAG, "联系人数据发生变化")
                loadContacts()
            }
        }
        
        // 注册观察者
        contentResolver.registerContentObserver(
            ContactsContract.Contacts.CONTENT_URI,
            true,
            contactsObserver
        )
    }
    
    private fun loadContacts() {
        lifecycleScope.launch(Dispatchers.IO) {
            val contactsList = mutableListOf<Contact>()
            
            contentResolver.query(
                ContactsContract.Contacts.CONTENT_URI,
                arrayOf(
                    ContactsContract.Contacts._ID,
                    ContactsContract.Contacts.DISPLAY_NAME,
                    ContactsContract.Contacts.HAS_PHONE_NUMBER
                ),
                null,
                null,
                ContactsContract.Contacts.DISPLAY_NAME + " ASC"
            )?.use { cursor ->
                while (cursor.moveToNext()) {
                    val id = cursor.getLong(0)
                    val name = cursor.getString(1)
                    val hasPhone = cursor.getInt(2) > 0
                    
                    if (hasPhone) {
                        val phoneNumber = getPhoneNumber(id)
                        contactsList.add(Contact(id, name, phoneNumber))
                    }
                }
            }
            
            withContext(Dispatchers.Main) {
                contacts.clear()
                contacts.addAll(contactsList)
                contactsAdapter.notifyDataSetChanged()
            }
        }
    }
    
    private fun getPhoneNumber(contactId: Long): String? {
        var phoneNumber: String? = null
        
        contentResolver.query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER),
            ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
            arrayOf(contactId.toString()),
            null
        )?.use { cursor ->
            if (cursor.moveToFirst()) {
                phoneNumber = cursor.getString(0)
            }
        }
        
        return phoneNumber
    }
    
    override fun onDestroy() {
        super.onDestroy()
        contentResolver.unregisterContentObserver(contactsObserver)
    }
    
    data class Contact(
        val id: Long,
        val name: String,
        val phoneNumber: String?
    )
    
    companion object {
        private const val TAG = "ContactsObserver"
        private const val PERMISSION_REQUEST_CODE = 100
    }
}

系统设置监听

class SettingsObserverActivity : AppCompatActivity() {
    
    private lateinit var brightnessObserver: ContentObserver
    private lateinit var volumeObserver: ContentObserver
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings_observer)
        
        setupBrightnessObserver()
        setupVolumeObserver()
    }
    
    private fun setupBrightnessObserver() {
        brightnessObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
            override fun onChange(selfChange: Boolean) {
                super.onChange(selfChange)
                val brightness = getBrightness()
                Log.d(TAG, "屏幕亮度变化: $brightness")
                updateBrightnessUI(brightness)
            }
        }
        
        contentResolver.registerContentObserver(
            Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS),
            false,
            brightnessObserver
        )
    }
    
    private fun setupVolumeObserver() {
        volumeObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
            override fun onChange(selfChange: Boolean) {
                super.onChange(selfChange)
                val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
                val volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
                Log.d(TAG, "音量变化: $volume")
                updateVolumeUI(volume)
            }
        }
        
        contentResolver.registerContentObserver(
            Settings.System.CONTENT_URI,
            true,
            volumeObserver
        )
    }
    
    private fun getBrightness(): Int {
        return try {
            Settings.System.getInt(
                contentResolver,
                Settings.System.SCREEN_BRIGHTNESS
            )
        } catch (e: Settings.SettingNotFoundException) {
            0
        }
    }
    
    private fun updateBrightnessUI(brightness: Int) {
        tvBrightness.text = "当前亮度: $brightness"
        seekBarBrightness.progress = brightness
    }
    
    private fun updateVolumeUI(volume: Int) {
        tvVolume.text = "当前音量: $volume"
        seekBarVolume.progress = volume
    }
    
    override fun onDestroy() {
        super.onDestroy()
        contentResolver.unregisterContentObserver(brightnessObserver)
        contentResolver.unregisterContentObserver(volumeObserver)
    }
    
    companion object {
        private const val TAG = "SettingsObserver"
    }
}

Room数据库集成

Room与LiveData结合

Room数据库原生支持LiveData,可以自动监听数据变化:

// Entity
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val name: String,
    val email: String,
    val age: Int
)

// DAO
@Dao
interface UserDao {
    
    @Query("SELECT * FROM users")
    fun getAllUsers(): LiveData<List<User>>
    
    @Query("SELECT * FROM users WHERE id = :userId")
    fun getUserById(userId: Long): LiveData<User>
    
    @Insert
    suspend fun insertUser(user: User)
    
    @Update
    suspend fun updateUser(user: User)
    
    @Delete
    suspend fun deleteUser(user: User)
    
    @Query("SELECT * FROM users WHERE name LIKE :query")
    fun searchUsers(query: String): LiveData<List<User>>
}

// Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    
    abstract fun userDao(): UserDao
    
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        
        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

// ViewModel
class UserViewModel(application: Application) : AndroidViewModel(application) {
    
    private val userDao = AppDatabase.getDatabase(application).userDao()
    
    val allUsers: LiveData<List<User>> = userDao.getAllUsers()
    
    fun insertUser(user: User) {
        viewModelScope.launch(Dispatchers.IO) {
            userDao.insertUser(user)
        }
    }
    
    fun updateUser(user: User) {
        viewModelScope.launch(Dispatchers.IO) {
            userDao.updateUser(user)
        }
    }
    
    fun deleteUser(user: User) {
        viewModelScope.launch(Dispatchers.IO) {
            userDao.deleteUser(user)
        }
    }
    
    fun searchUsers(query: String): LiveData<List<User>> {
        return userDao.searchUsers("%$query%")
    }
}

// Activity
class UserListActivity : AppCompatActivity() {
    
    private lateinit var viewModel: UserViewModel
    private lateinit var userAdapter: UserAdapter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_list)
        
        setupRecyclerView()
        setupViewModel()
        setupFab()
    }
    
    private fun setupRecyclerView() {
        userAdapter = UserAdapter { user ->
            // 处理用户点击
            showUserDetail(user)
        }
        
        recyclerView.apply {
            layoutManager = LinearLayoutManager(this@UserListActivity)
            adapter = userAdapter
            addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
        }
    }
    
    private fun setupViewModel() {
        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        
        // 观察数据变化
        viewModel.allUsers.observe(this) { users ->
            // 数据变化时自动更新UI
            userAdapter.submitList(users)
            
            if (users.isEmpty()) {
                tvEmpty.visibility = View.VISIBLE
                recyclerView.visibility = View.GONE
            } else {
                tvEmpty.visibility = View.GONE
                recyclerView.visibility = View.VISIBLE
            }
        }
    }
    
    private fun setupFab() {
        fabAdd.setOnClickListener {
            showAddUserDialog()
        }
    }
    
    private fun showAddUserDialog() {
        val dialogView = layoutInflater.inflate(R.layout.dialog_add_user, null)
        val etName = dialogView.findViewById<EditText>(R.id.et_name)
        val etEmail = dialogView.findViewById<EditText>(R.id.et_email)
        val etAge = dialogView.findViewById<EditText>(R.id.et_age)
        
        AlertDialog.Builder(this)
            .setTitle("添加用户")
            .setView(dialogView)
            .setPositiveButton("添加") { _, _ ->
                val name = etName.text.toString()
                val email = etEmail.text.toString()
                val age = etAge.text.toString().toIntOrNull() ?: 0
                
                if (name.isNotEmpty() && email.isNotEmpty()) {
                    val user = User(name = name, email = email, age = age)
                    viewModel.insertUser(user)
                }
            }
            .setNegativeButton("取消", null)
            .show()
    }
    
    private fun showUserDetail(user: User) {
        // 显示用户详情
    }
}

Room数据库迁移监听

class DatabaseMigrationObserver {
    
    companion object {
        val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                // 添加新列
                database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT")
                
                // 通知迁移完成
                Log.d("Migration", "数据库从版本1迁移到版本2")
            }
        }
        
        val MIGRATION_2_3 = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                // 创建新表
                database.execSQL("""
                    CREATE TABLE IF NOT EXISTS posts (
                        id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                        userId INTEGER NOT NULL,
                        title TEXT NOT NULL,
                        content TEXT,
                        createdAt INTEGER NOT NULL,
                        FOREIGN KEY(userId) REFERENCES users(id) ON DELETE CASCADE
                    )
                """)
            }
        }
    }
}

// 配置数据库迁移
val database = Room.databaseBuilder(
    context,
    AppDatabase::class.java,
    "app_database"
)
    .addMigrations(
        DatabaseMigrationObserver.MIGRATION_1_2,
        DatabaseMigrationObserver.MIGRATION_2_3
    )
    .addCallback(object : RoomDatabase.Callback() {
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            Log.d("Database", "数据库创建完成")
        }
        
        override fun onOpen(db: SupportSQLiteDatabase) {
            super.onOpen(db)
            Log.d("Database", "数据库打开")
        }
    })
    .build()

LiveData与数据观察

自定义LiveData

class NetworkLiveData(private val context: Context) : LiveData<NetworkInfo>() {
    
    private val connectivityManager = 
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    
    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            super.onAvailable(network)
            postValue(NetworkInfo(true, getNetworkType()))
        }
        
        override fun onLost(network: Network) {
            super.onLost(network)
            postValue(NetworkInfo(false, NetworkType.NONE))
        }
    }
    
    override fun onActive() {
        super.onActive()
        val networkRequest = NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build()
        connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
        
        // 立即更新当前状态
        val isConnected = connectivityManager.activeNetworkInfo?.isConnected ?: false
        postValue(NetworkInfo(isConnected, getNetworkType()))
    }
    
    override fun onInactive() {
        super.onInactive()
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }
    
    private fun getNetworkType(): NetworkType {
        val activeNetwork = connectivityManager.activeNetworkInfo
        return when (activeNetwork?.type) {
            ConnectivityManager.TYPE_WIFI -> NetworkType.WIFI
            ConnectivityManager.TYPE_MOBILE -> NetworkType.MOBILE
            else -> NetworkType.NONE
        }
    }
    
    data class NetworkInfo(
        val isConnected: Boolean,
        val type: NetworkType
    )
    
    enum class NetworkType {
        WIFI, MOBILE, NONE
    }
}

// 使用
class MainActivity : AppCompatActivity() {
    
    private lateinit var networkLiveData: NetworkLiveData
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        networkLiveData = NetworkLiveData(this)
        networkLiveData.observe(this) { networkInfo ->
            when {
                !networkInfo.isConnected -> {
                    showNoConnectionDialog()
                }
                networkInfo.type == NetworkLiveData.NetworkType.MOBILE -> {
                    showMobileDataWarning()
                }
            }
        }
    }
}

MediatorLiveData组合多个数据源

class UserProfileViewModel : ViewModel() {
    
    private val userId = MutableLiveData<Long>()
    
    // 用户基本信息
    private val userInfo: LiveData<User> = userId.switchMap { id ->
        userRepository.getUserById(id)
    }
    
    // 用户帖子
    private val userPosts: LiveData<List<Post>> = userId.switchMap { id ->
        postRepository.getPostsByUserId(id)
    }
    
    // 用户关注者
    private val followers: LiveData<List<User>> = userId.switchMap { id ->
        userRepository.getFollowers(id)
    }
    
    // 组合多个数据源
    val userProfile = MediatorLiveData<UserProfile>().apply {
        addSource(userInfo) { user ->
            value = value?.copy(user = user) ?: UserProfile(user = user)
        }
        
        addSource(userPosts) { posts ->
            value = value?.copy(posts = posts) ?: UserProfile(posts = posts)
        }
        
        addSource(followers) { followersList ->
            value = value?.copy(followers = followersList) ?: UserProfile(followers = followersList)
        }
    }
    
    fun loadUserProfile(id: Long) {
        userId.value = id
    }
    
    data class UserProfile(
        val user: User? = null,
        val posts: List<Post> = emptyList(),
        val followers: List<User> = emptyList()
    )
}

FileObserver文件监听

监听文件系统变化

class LogFileObserver(
    private val logDirectory: File,
    private val onLogFileChanged: (String) -> Unit
) : FileObserver(logDirectory.absolutePath, ALL_EVENTS) {
    
    override fun onEvent(event: Int, path: String?) {
        path?.let {
            when (event) {
                CREATE -> {
                    Log.d(TAG, "文件创建: $path")
                    if (path.endsWith(".log")) {
                        onLogFileChanged("新日志文件: $path")
                    }
                }
                DELETE -> {
                    Log.d(TAG, "文件删除: $path")
                }
                MODIFY -> {
                    Log.d(TAG, "文件修改: $path")
                    if (path.endsWith(".log")) {
                        readLatestLogEntry(File(logDirectory, path))
                    }
                }
                MOVED_TO -> {
                    Log.d(TAG, "文件移入: $path")
                }
            }
        }
    }
    
    private fun readLatestLogEntry(file: File) {
        try {
            val lines = file.readLines()
            if (lines.isNotEmpty()) {
                onLogFileChanged("最新日志: ${lines.last()}")
            }
        } catch (e: Exception) {
            Log.e(TAG, "读取日志文件失败", e)
        }
    }
    
    companion object {
        private const val TAG = "LogFileObserver"
    }
}

// 使用
class LogMonitorActivity : AppCompatActivity() {
    
    private lateinit var fileObserver: LogFileObserver
    private val logEntries = mutableListOf<String>()
    private lateinit var logAdapter: ArrayAdapter<String>
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_log_monitor)
        
        setupListView()
        setupFileObserver()
    }
    
    private fun setupListView() {
        logAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, logEntries)
        listView.adapter = logAdapter
    }
    
    private fun setupFileObserver() {
        val logDir = File(getExternalFilesDir(null), "logs")
        if (!logDir.exists()) {
            logDir.mkdirs()
        }
        
        fileObserver = LogFileObserver(logDir) { message ->
            runOnUiThread {
                logEntries.add(0, "${Date()}: $message")
                logAdapter.notifyDataSetChanged()
            }
        }
        
        fileObserver.startWatching()
    }
    
    override fun onDestroy() {
        super.onDestroy()
        fileObserver.stopWatching()
    }
}

监听下载目录

class DownloadObserver(
    private val context: Context
) : FileObserver(
    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath,
    CREATE or DELETE or MOVED_TO
) {
    
    private val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    
    override fun onEvent(event: Int, path: String?) {
        path?.let {
            when (event) {
                CREATE, MOVED_TO -> {
                    if (isMediaFile(path)) {
                        scanMediaFile(path)
                        notifyNewDownload(path)
                    }
                }
                DELETE -> {
                    notifyFileDeleted(path)
                }
            }
        }
    }
    
    private fun isMediaFile(filename: String): Boolean {
        val mediaExtensions = listOf(".jpg", ".jpeg", ".png", ".mp4", ".mp3", ".pdf")
        return mediaExtensions.any { filename.endsWith(it, ignoreCase = true) }
    }
    
    private fun scanMediaFile(filename: String) {
        val file = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
            filename
        )
        
        MediaScannerConnection.scanFile(
            context,
            arrayOf(file.absolutePath),
            null
        ) { path, uri ->
            Log.d(TAG, "媒体扫描完成: $path")
        }
    }
    
    private fun notifyNewDownload(filename: String) {
        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "下载通知",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }
        
        val notification = NotificationCompat.Builder(context, CHANNEL_ID)
            .setContentTitle("文件下载完成")
            .setContentText(filename)
            .setSmallIcon(android.R.drawable.stat_sys_download_done)
            .setAutoCancel(true)
            .build()
        
        notificationManager.notify(filename.hashCode(), notification)
    }
    
    private fun notifyFileDeleted(filename: String) {
        Log.d(TAG, "文件被删除: $filename")
    }
    
    companion object {
        private const val TAG = "DownloadObserver"
        private const val CHANNEL_ID = "download_channel"
    }
}

性能优化

1. 避免过度监听

class OptimizedContentObserver : ContentObserver(Handler(Looper.getMainLooper())) {
    
    private var lastUpdateTime = 0L
    private val updateThreshold = 1000L // 1秒内的变化只处理一次
    
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastUpdateTime < updateThreshold) {
            return
        }
        lastUpdateTime = currentTime
        
        // 处理数据变化
        handleDataChange(uri)
    }
    
    private fun handleDataChange(uri: Uri?) {
        // 批量处理数据变化
    }
}

2. 使用合适的通知粒度

class GranularContentProvider : ContentProvider() {
    
    companion object {
        private const val AUTHORITY = "com.example.provider"
        
        // 定义不同粒度的URI
        val USERS_URI = Uri.parse("content://$AUTHORITY/users")
        val USER_DETAIL_URI = Uri.parse("content://$AUTHORITY/users/detail")
        val USER_STATS_URI = Uri.parse("content://$AUTHORITY/users/stats")
    }
    
    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        val count = // 执行更新
        
        // 根据更新内容通知不同的URI
        when {
            values?.containsKey("name") == true -> {
                context?.contentResolver?.notifyChange(USER_DETAIL_URI, null)
            }
            values?.containsKey("score") == true -> {
                context?.contentResolver?.notifyChange(USER_STATS_URI, null)
            }
            else -> {
                context?.contentResolver?.notifyChange(uri, null)
            }
        }
        
        return count
    }
}

3. 批量操作优化

class BatchOperationProvider : ContentProvider() {
    
    override fun applyBatch(
        operations: ArrayList<ContentProviderOperation>
    ): Array<ContentProviderResult> {
        val database = dbHelper.writableDatabase
        val results = arrayListOf<ContentProviderResult>()
        
        database.beginTransaction()
        try {
            for (operation in operations) {
                results.add(operation.apply(this, results, results.size))
            }
            database.setTransactionSuccessful()
            
            // 批量操作完成后统一通知
            context?.contentResolver?.notifyChange(CONTENT_URI, null)
        } finally {
            database.endTransaction()
        }
        
        return results.toTypedArray()
    }
}

最佳实践

1. 生命周期管理

class LifecycleAwareObserver(
    private val lifecycle: Lifecycle,
    private val contentResolver: ContentResolver,
    private val uri: Uri,
    private val onChange: () -> Unit
) : LifecycleObserver {
    
    private val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
        override fun onChange(selfChange: Boolean) {
            onChange()
        }
    }
    
    init {
        lifecycle.addObserver(this)
    }
    
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun startObserving() {
        contentResolver.registerContentObserver(uri, true, contentObserver)
    }
    
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stopObserving() {
        contentResolver.unregisterContentObserver(contentObserver)
    }
}

// 使用
class MyActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        LifecycleAwareObserver(
            lifecycle,
            contentResolver,
            MyContentProvider.CONTENT_URI
        ) {
            // 处理数据变化
            loadData()
        }
    }
}

2. 错误处理

class RobustContentObserver(
    private val context: Context,
    handler: Handler
) : ContentObserver(handler) {
    
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        try {
            handleChange(uri)
        } catch (e: SecurityException) {
            Log.e(TAG, "没有权限访问: $uri", e)
            requestPermission()
        } catch (e: Exception) {
            Log.e(TAG, "处理数据变化时出错", e)
            // 可以考虑上报错误
        }
    }
    
    private fun handleChange(uri: Uri?) {
        // 安全地处理数据变化
        context.contentResolver.query(
            uri ?: return,
            null, null, null, null
        )?.use { cursor ->
            // 处理数据
        }
    }
    
    private fun requestPermission() {
        // 请求必要的权限
    }
    
    companion object {
        private const val TAG = "RobustContentObserver"
    }
}

3. 测试策略

@RunWith(AndroidJUnit4::class)
class ContentObserverTest {
    
    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()
    
    private lateinit var context: Context
    private lateinit var contentResolver: ContentResolver
    
    @Before
    fun setup() {
        context = ApplicationProvider.getApplicationContext()
        contentResolver = context.contentResolver
    }
    
    @Test
    fun testContentObserverNotification() {
        var notificationCount = 0
        val latch = CountDownLatch(1)
        
        val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
            override fun onChange(selfChange: Boolean) {
                notificationCount++
                latch.countDown()
            }
        }
        
        contentResolver.registerContentObserver(
            TestContentProvider.CONTENT_URI,
            true,
            observer
        )
        
        // 触发数据变化
        contentResolver.insert(TestContentProvider.CONTENT_URI, ContentValues().apply {
            put("name", "Test")
        })
        
        // 等待通知
        assertTrue(latch.await(5, TimeUnit.SECONDS))
        assertEquals(1, notificationCount)
        
        contentResolver.unregisterContentObserver(observer)
    }
}

调试技巧

1. 日志记录

class DebugContentObserver(
    private val tag: String,
    handler: Handler
) : ContentObserver(handler) {
    
    private var changeCount = 0
    
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        changeCount++
        Log.d(tag, """
            ContentObserver触发 #$changeCount
            URI: $uri
            Self Change: $selfChange
            Thread: ${Thread.currentThread().name}
            Time: ${SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(Date())}
        """.trimIndent())
        
        super.onChange(selfChange, uri)
    }
}

2. 监控工具

object ContentObserverMonitor {
    
    private val activeObservers = mutableMapOf<String, ObserverInfo>()
    
    fun registerObserver(name: String, uri: Uri, observer: ContentObserver) {
        activeObservers[name] = ObserverInfo(uri, observer, System.currentTimeMillis())
        Log.d("Monitor", "注册Observer: $name for $uri")
    }
    
    fun unregisterObserver(name: String) {
        activeObservers.remove(name)?.let { info ->
            val duration = System.currentTimeMillis() - info.registrationTime
            Log.d("Monitor", "注销Observer: $name, 活跃时间: ${duration}ms")
        }
    }
    
    fun getActiveObservers(): List<String> {
        return activeObservers.map { (name, info) ->
            "$name -> ${info.uri}"
        }
    }
    
    data class ObserverInfo(
        val uri: Uri,
        val observer: ContentObserver,
        val registrationTime: Long
    )
}

常见问题

问题1:ContentObserver不触发

原因:

  • URI不匹配
  • 没有调用notifyChange
  • Observer未正确注册

解决方案:

// 确保URI匹配
contentResolver.registerContentObserver(
    Uri.parse("content://com.example.provider/users"),
    true, // notifyForDescendants = true
    observer
)

// 确保在数据变化时通知
override fun insert(uri: Uri, values: ContentValues?): Uri? {
    val id = // 插入数据
    val newUri = ContentUris.withAppendedId(uri, id)
    // 必须调用notifyChange
    context?.contentResolver?.notifyChange(newUri, null)
    return newUri
}

问题2:内存泄漏

原因:

  • Observer未注销
  • 使用非静态内部类

解决方案:

class SafeActivity : AppCompatActivity() {
    
    private var contentObserver: ContentObserver? = null
    
    override fun onStart() {
        super.onStart()
        contentObserver = createObserver()
        contentResolver.registerContentObserver(uri, true, contentObserver!!)
    }
    
    override fun onStop() {
        super.onStop()
        contentObserver?.let {
            contentResolver.unregisterContentObserver(it)
        }
    }
    
    private fun createObserver(): ContentObserver {
        // 使用静态类或独立类,避免持有Activity引用
        return object : ContentObserver(Handler(Looper.getMainLooper())) {
            override fun onChange(selfChange: Boolean) {
                // 处理变化
            }
        }
    }
}

问题3:性能问题

原因:

  • 频繁触发onChange
  • 在onChange中执行耗时操作

解决方案:

class ThrottledContentObserver(
    handler: Handler
) : ContentObserver(handler) {
    
    private val pendingChanges = mutableSetOf<Uri>()
    private val throttleRunnable = Runnable {
        processPendingChanges()
    }
    
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        uri?.let { pendingChanges.add(it) }
        
        handler.removeCallbacks(throttleRunnable)
        handler.postDelayed(throttleRunnable, THROTTLE_DELAY)
    }
    
    private fun processPendingChanges() {
        val changes = pendingChanges.toList()
        pendingChanges.clear()
        
        // 批量处理变化
        GlobalScope.launch(Dispatchers.IO) {
            handleChanges(changes)
        }
    }
    
    private suspend fun handleChanges(uris: List<Uri>) {
        // 异步处理
    }
    
    companion object {
        private const val THROTTLE_DELAY = 500L
    }
}

相关文章

Android组件通信

数据存储

架构组件

总结

数据变化监听是Android应用开发中的重要技术,通过合理使用ContentObserver、LiveData、Room等技术,可以构建响应式的用户界面。

🎯 核心要点

  • ContentObserver:监听ContentProvider数据变化
  • LiveData:生命周期感知的可观察数据
  • Room + LiveData:自动化的数据库监听
  • FileObserver:文件系统变化监听
  • 性能优化:避免过度监听,合理使用通知粒度

🛠️ 开发建议

  • 注意生命周期管理,及时注销Observer
  • 使用现代架构组件简化开发
  • 合理设计数据变化通知粒度
  • 异步处理耗时操作