为啥 ContentObserver 会被多次调用?

Posted

技术标签:

【中文标题】为啥 ContentObserver 会被多次调用?【英文标题】:Why is the ContentObserver called multiple times?为什么 ContentObserver 会被多次调用? 【发布时间】:2011-11-21 12:43:37 【问题描述】:

我有以下ContentObserver实现接收和编写短信,但它被多次调用。

代码:

public class SMSObserverActivity extends Activity 
    protected MyContentObserver observer = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        String url = "content://mms-sms/";
        Uri uri = Uri.parse(url);
        observer = new MyContentObserver(new Handler());
        getContentResolver().registerContentObserver(uri, true, observer);
    

    @Override
    protected void onDestroy()
        super.onDestroy();

        getContentResolver().unregisterContentObserver(observer);
    

    class MyContentObserver extends ContentObserver 
        ContentValues values = new ContentValues();
        Handler handler;

        public MyContentObserver(Handler handler)
            super(handler);
            this.handler = handler;
        

        @Override
        public boolean deliverSelfNotifications()
            return false;
        


        @Override
        public void onChange(boolean arg0)
            super.onChange(arg0);

            Log.v("SMS", "Notification on SMS observer");
            values.put("status", 5);
            Message msg = new Message();
            msg.obj = "xxxxxxxxxx";
            int threadId = 0;
            handler.sendMessage(msg);

            Uri uriSMSURI = Uri.parse("content://sms/");
            Cursor cur =
                    getContentResolver().query(uriSMSURI, null, null, null,
                            null);
            cur.moveToNext();
            Log.e("sms", cur.getString(4)+" "+cur.getString(11));
        
    

清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test"
    android:versionCode="1"
    android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.READ_SMS"></uses-permission>
    <uses-permission android:name="android.permission.WRITE_SMS"></uses-permission>

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".SMSObserverActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

为什么会被多次调用?

编辑: 有人认为问题是由于缺少unregisterContentObserver引起的,但没有任何区别。

【问题讨论】:

你有想过这个吗? 还没有,我还没有时间,但是请阅读答案上的 cmets 并尝试使用服务。也许你会得到它的工作。 ;) 如果在服务中运行,恐怕问题是一样的 是的,同样的问题发生了,不过我已经找到了原因。见答案。 【参考方案1】:

这是因为您正在为整个 SMS 数据库注册内容观察器。因此,每次更新数据库中的表条目时,您的内容观察者都会收到通知。

在这种情况下,例如发送消息时,大约 7 个表条目会更新,因此您的内容观察者会收到 7 次通知。

由于我只对是否发送消息感兴趣,因此我已更改为仅观察排队的消息,这意味着我的观察者总是会收到准确的 3 次通知,因此我已经实现了代码来防止这种情况发生。

可能还有其他一些问题,例如多收件人或多部分消息,但到目前为止基本工作正常。

【讨论】:

好吧,我以为每个对象的所有相关更新只调用一次观察者。我认为有一个唯一的 ID 可以查看,哪些通知属于一个更新,不是吗?【参考方案2】:

为了避免内容观察者发送多条短信,试试这个

public class SmsObserver extends ContentObserver 
    SharedPreferences trackMeData;
    private Context context;
    private static int initialPos;
    private static final String TAG = "SMSContentObserver";
    private static final Uri uriSMS = Uri.parse("content://sms/sent");

    public SmsObserver(Handler handler, Context ctx) 
        super(handler);
        context = ctx;
        trackMeData = context.getSharedPreferences("LockedSIM", 0);
        initialPos = getLastMsgId();

    

    @Override
    public void onChange(boolean selfChange) 
        super.onChange(selfChange);
        queryLastSentSMS();
    

    public int getLastMsgId() 

        Cursor cur = context.getContentResolver().query(uriSMS, null, null, null, null);
        cur.moveToFirst();
        int lastMsgId = cur.getInt(cur.getColumnIndex("_id"));
        Log.i(TAG, "Last sent message id: " + String.valueOf(lastMsgId));
        return lastMsgId;
    

    protected void queryLastSentSMS() 

        new Thread(new Runnable() 

            @Override
            public void run() 
                Cursor cur =
                    context.getContentResolver().query(uriSMS, null, null, null, null);

                if (cur.moveToNext()) 



                    try 

                        String body = cur.getString(cur.getColumnIndex("body"));

                        if (initialPos != getLastMsgId()) 

                            String receiver = cur.getString(cur.getColumnIndex("address"));
                            Log.i("account", myDeviceId);
                            Log.i("date", day + "-" + month + "-" + year + " "
                                + hour + ":" + minute + ":" + seconde);
                            Log.i("sender", myTelephoneNumber);
                            Log.i("receiver", receiver );


                            // Then, set initialPos to the current position.
                            initialPos = getLastMsgId();

                            sendsmstoph(receiver, body);
                        
                     catch (Exception e) 
                        // Treat exception here
                    
                
                cur.close();
            
        ).start();

    

【讨论】:

调用 initialPos = getLastMsgId(); from 构造函数抛出空指针异常 我注意到这很好,但并不总是万无一失的。所以我认为使用 SharedPreferences 可以解决这个问题。这是我的做法: if (!sharedPreferences.getString("lastMessageTime", "0") .equals(String.valueOf(timeInMillis)) || !sharedPreferences.getString("lastMessageNumber", "0") .equals(数字))【参考方案3】:

如果您希望仅在活动处于活动状态时启用观察者,我建议您将registerContentObserver()unregisterContentObserver() 分别移动到方法onResume()onPause()。如果您的应用程序退出,onDestroy() 可能不会被调用,但onPause() 一定会被调用。

【讨论】:

Activity 运行时,观察者应始终处于活动状态。这只是一个观察内容测试。 Activity 在onResume()onPause() 之间运行,其他时间它要么在后台要么不可见。它甚至可能在没有调用onDestroy() 的情况下被杀死,当您在onCreate() 中重新创建应用程序时,您将第二次注册观察者。 为什么要注册两次?我有一个注销声明。 你在onDestroy()方法中有,不保证会被调用。当您的活动停止运行(离开屏幕)时调用的方法是onPause()。来自有关onDestroy() 的文档:“在某些情况下,系统会简单地终止活动的托管进程而不调用此方法(或任何其他方法)”。 好的,我现在已经尝试使用包含观察者的静态类成员。这没什么区别。使用onResume()onPause() 不适合我,因为我想启动Activity,然后我会向我发送短信。

以上是关于为啥 ContentObserver 会被多次调用?的主要内容,如果未能解决你的问题,请参考以下文章

短信 ContentObserver onChange() 触发多次

联系人 ContentObserver 随机调用

为啥在某些网站上多次调用 QWebView.loadFinished,例如YouTube?

Android:内容观察者的“onChange()”方法被多次调用

当且仅当 ContactsContract.Contacts.CONTENT_URI 更改时,ContentObserver 应该调用

为啥外部包含目录编译多次?