Android 编程技巧之 ----- 解决短信监听 onChange 触发两次的问题

原文出处 : Android短信监听功能(解决onChange触发两次的问题), 作者 : lovelease

前言

项目要做短信验证码自动填充的功能,基本上两种方法:ContentObserver监听数据库、广播;
广播因为要在manifest里面配置接收器之类的 ,怕sdk使用者忘记,索性就选择了监听数据库。
遇到的问题是大部分手机收到一条短信时会触发两次onChange()方法,很多人提出的解决方法是记录smsId,判断如果是旧的就不做处理,是新的就做该做的事。起初这么做了,后来发现有时候自动填充会先填旧的,立马又填新的,会闪一下,虽然只是偶尔出现,但还是觉得不爽,于是抽空又招别的方法,总算找出来了,也是受到一些网友的启发,下面就分享出来,原理我不多说了,代码注释里都讲得很清楚。

package com.jackie.bindmobile;  

import android.app.Activity;  
import android.database.ContentObserver;  
import android.database.Cursor;  
import android.net.Uri;  
import android.os.Handler;  
import android.text.TextUtils;  
import android.util.Log;  

import java.util.regex.Matcher;  
import java.util.regex.Pattern;  

/** 
 * Monitor sms database 
 *  
 * @author Jackie 
 * 
 */  
public class SmsContent extends ContentObserver {  

    private static final String TAG = SmsContent.class.getSimpleName();  
    private static final String MARKER = "YOUR_KEYWORD";  
    private Cursor cursor = null;  
    private Activity mActivity;  

    public SmsContent(Handler handler, Activity activity) {  
        super(handler);  
        this.mActivity = activity;  
    }  

    /** 
     * This method is called when a content change occurs. 
     * 

* Subclasses should override this method to handle content changes. *

* * @param selfChange True if this is a self-change notification. */
@Override public void onChange(boolean selfChange) { super.onChange(selfChange); Log.d(TAG, "onChange(boolean selfChange). selfChange=" + selfChange); onChange(selfChange, null); } /** * Notice: onChange will be triggered twice on some devices when a sms received, * eg: samsung s7 edge(API.23) - twice * samsung note3(API.18) - once * 06-15 11:45:48.706 D/SmsContent: onChange(boolean selfChange, Uri uri). selfChange=false, uri=content://sms/raw * 06-15 11:45:49.466 D/SmsContent: onChange(boolean selfChange, Uri uri). selfChange=false, uri=content://sms/387 * * Generally onChange will be triggered twice, first time is triggered by uri "content://sms/raw"(sms received, * but have not written into inbox), second time is triggered by uri "content://sms/387"(number is sms id) * * Android official comments: * This method is called when a content change occurs. * Includes the changed content Uri when available. *

* Subclasses should override this method to handle content changes. * To ensure correct operation on older versions of the framework that * did not provide a Uri argument, applications should also implement * the {@link #onChange(boolean)} overload of this method whenever they * implement the {@link #onChange(boolean, Uri)} overload. *

* Example implementation: *

 
     * // Implement the onChange(boolean) method to delegate the change notification to 
     * // the onChange(boolean, Uri) method to ensure correct operation on older versions 
     * // of the framework that did not have the onChange(boolean, Uri) method. 
     * {@literal @Override} 
     * public void onChange(boolean selfChange) { 
     *     onChange(selfChange, null); 
     * } 
     * 
     * // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument. 
     * {@literal @Override} 
     * public void onChange(boolean selfChange, Uri uri) { 
     *     // Handle change. 
     * } 
     * 
*

* * @param selfChange True if this is a self-change notification. * @param uri The Uri of the changed content, or null if unknown. */
@Override public void onChange(boolean selfChange, Uri uri) { Log.d(TAG, "onChange(boolean selfChange, Uri uri). selfChange=" + selfChange + ", uri=" + uri); /** * 适配某些较旧的设备,可能只会触发onChange(boolean selfChange)方法,没有传回uri参数, * 此时只能通过"content://sms/inbox"来查询短信 */ if (uri == null) { uri = Uri.parse("content://sms/inbox"); } /** * 06-15 11:45:48.706 D/SmsContent: onChange(boolean selfChange, Uri uri). selfChange=false, uri=content://sms/raw * 06-15 11:45:49.466 D/SmsContent: onChange(boolean selfChange, Uri uri). selfChange=false, uri=content://sms/387 * * Generally onChange will be triggered twice, first time is triggered by uri "content://sms/raw"(sms received, * but have not written into inbox), second time is triggered by uri "content://sms/387"(number is sms id) */ if (uri.toString().equals("content://sms/raw")) { return; } cursor = this.mActivity.getContentResolver().query(uri, null, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { int id = cursor.getInt(cursor.getColumnIndex("_id")); String body = cursor.getString(cursor.getColumnIndex("body")); Log.d(TAG, "sms id: " + id + "\nsms body: " + body); cursor.close(); // Already got sms body, do anything you want, for example: filter the verify code getVerifyCode(body); } } else { Log.e(TAG, "error: cursor == null"); } } /** * Register a monitor of changing of sms */ public void register() { Log.d(TAG, "Register sms monitor"); this.mActivity.getContentResolver().registerContentObserver( Uri.parse("content://sms/"), true, this); } /** * Unregister the monitor of changing of sms */ public void unRegister() { Log.d(TAG, "Unregister sms monitor"); this.mActivity.getContentResolver().unregisterContentObserver(this); } /** * Get verify code from sms body * @param str * @return */ public String getVerifyCode(String str) { String verifyCode = null; if (smsContentFilter(str)) { Log.d(TAG, "sms content matched, auto-fill verify code."); verifyCode = getDynamicPassword(str); } else { // Do nothing Log.d(TAG, "sms content did not match, do nothing."); } return verifyCode; } /** * Check if str is verification-code-formatted * * @param str * @return */ private boolean smsContentFilter(String str) { Log.d(TAG, "smsContentFilter. smsBody = " + str); boolean isMatched = false; if (!TextUtils.isEmpty(str)) { // Check if str contains keyword if (str.contains(MARKER)) { Log.d(TAG, "This sms contains \"" + MARKER + "\""); // Check if str contains continuous 6 numbers Pattern continuousNumberPattern = Pattern.compile("[0-9\\.]+"); Matcher m = continuousNumberPattern.matcher(str); while(m.find()){ if(m.group().length() == 6) { Log.d(TAG, "This sms contains continuous 6 numbers : " + m.group()); isMatched = true; } } } } return isMatched; } /** * Cut the continuous 6 numbers from str * * @param str sms content * @return verification code */ private String getDynamicPassword(String str) { Log.d(TAG, "getDynamicPassword. smsBody = " + str); Pattern continuousNumberPattern = Pattern.compile("[0-9\\.]+"); Matcher m = continuousNumberPattern.matcher(str); String dynamicPassword = ""; while(m.find()){ if(m.group().length() == 6) { Log.d(TAG, m.group()); dynamicPassword = m.group(); } } Log.d(TAG, "Verification code: " + dynamicPassword); return dynamicPassword; } }

用法很简单,注册和注销一下就行:

package com.jackie.bindmobile;  

import android.app.Activity;  
import android.os.Bundle;  
import android.os.Handler;  

public class BindMobileActivity extends Activity {  

    private static final String TAG = BindMobileActivity.class.getSimpleName();  
    private SmsContent smsContent;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  

        // Register sms monitor  
        smsContent = new SmsContent(new Handler(), mActivity);  
        smsContent.register();  
    }  

    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        // Unregister sms monitor  
        smsContent.unRegister();  
    }  
}

结语

本文提供了解决短信监听时常见的 onChange 回调函数触发两次问题的方法, 暂且先不论代码中有可能造成内存泄漏的风险 (Activity 引用传递), 但解决问题的思想是很好的.

你可能感兴趣的:(Android)