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

来源:互联网 发布:dean fujioka 知乎 编辑:程序博客网 时间:2024/06/14 17:25

原文出处 : 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.      * <p>      * Subclasses should override this method to handle content changes.      * </p>      *      * @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.      * <p>      * 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.      * </p><p>      * Example implementation:      * <pre><code>      * // 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.      * }      * </code></pre>      * </p>      *      * @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 引用传递), 但解决问题的思想是很好的.

3 0
原创粉丝点击