自动读取短信验证码LoaderManager/CursorLoader的使用

Cursor用来直接读取安卓手机里的数据库记录,如何获取到Cursor?

  • 可以通过SQLiteOpenHelper,打开SQLiteDatabase
  • 通过ContentProvider/ContentResolver获取

自动读取验证码实现思路:

  1. 当验证码的短信到来,能有个监听回调或者广播之类,告知开发人员
  2. 读取短信是危险权限,android6.0权限适配
  3. 获取验证码的短信内容
  4. 从短信内容里刷选出验证码
  5. 显示验证码

接下来针对每一个步骤,做些分析和代码分享

监听接收到新短信

使用ContentResolver,引入内容观察者,监听短信内容的变化,代码如下:

public void register(){
    mReadSmsContentObserver = new ReadSmsContentObserver(new Handler());
    mContext.getContentResolver().registerContentObserver(URI_SMS,true,mReadSmsContentObserver);
}

  public void initReadSmsLoader(){
    LoaderManager loaderManager = getLoaderManager();
    if(loaderManager != null){
        loaderManager.initLoader(0, null, new ReadSmsLoaderListener());
    }
  }      

class ReadSmsContentObserver extends ContentObserver{

    public ReadSmsContentObserver(Handler handler) {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        LOG.e(TAG,"selfChange = " + selfChange+" uri = " + uri);
        if(PermissionHelper.checkReadSmsPermission(mContext)){
            initReadSmsLoader();
        }else{
            takeReadSmsPermission();
        }
    }
}

短信验证码,权限适配

PermissionHelper是自己封装的工具类

PermissionHelper.takeReadSmsPermission(this,PermissionHelper.REQUEST_CODE_READ_SMS);   

获取验证码的短信内容

这里有多个方案,网上相当多技术文章使用contentResolver.query()方法。我认为这个方法不够好。

  • 该方法在UI线程里操作的,当查询的数据库的记录很多时,会出现卡顿现象。
  • 需要手动关闭Cursor,Cursor维护操作交给开发人员。

好在,安卓已经提供了异步的(子线程)数据库查询方式,使用LoaderManager和CursorLoader这两个类,不存在上述两个问题。代码如下
更详细的LoaderManager.LoaderCallbacks各个回调方法的使用,可以参考android CursorLoader用法介绍

/**
 * 注册内容观察者
 */
public void register(){
    mReadSmsContentObserver = new ReadSmsContentObserver(new Handler());
    mContext.getContentResolver().registerContentObserver(URI_SMS,true,mReadSmsContentObserver);
}

public class ReadSmsLoaderListener implements LoaderManager.LoaderCallbacks<Cursor>{

private static Uri URI_SMS_INBOX = Uri.parse("content://sms/inbox");
// "_id", "address", "person", "date", "type"
private static final String[] READ_SMS_PROJECTION = new String[]{"body"};
private static final String READ_SMS_SORT_ORDER = "date desc";

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        //两分钟内,收到的短信,date降序排列
        String readSmsWhere = "date >" + (System.currentTimeMillis() - 2*60*1000);
        return new CursorLoader(mContext,URI_SMS_INBOX,READ_SMS_PROJECTION,readSmsWhere,null,READ_SMS_SORT_ORDER);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        fetchSmsCodeFromCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }
}

/**
 * 从数据库获取短信并过滤出验证码
 */
private void fetchSmsCodeFromCursor(Cursor cursor) {
    if(cursor != null) {
        while (cursor.moveToNext()) {
            String body = cursor.getString(cursor.getColumnIndex("body"));
            if (!TextUtils.isEmpty(body)) {
                //判断该短信是否是该App发送的
                if (body.contains("走么")) {
                    String verifyCode = getSmsCode(body);
                    displayVerifyCode(verifyCode);
                    break;
                }
            }
        }
    }
}

从短信内容里刷选出验证码

使用正则表达式筛选,按正则的使用套路走起

/**
 * 从短信中获取验证码
 * @param smsContent 短信内容
 * @return
 */
private String getSmsCode(String smsContent){
    Pattern pattern = Pattern.compile("[0-9]{4,}");
    Matcher m = pattern.matcher(smsContent);
    while (m.find()) {
        String group = m.group();
        if(group != null && group.length() >= VERIFY_CODE_LENGTH){
            return group;
        }
    }
    return "";
}

显示验证码

 /**
 * 显示验证码
 * @param verifyCode 验证码
 */
private void displayVerifyCode(String verifyCode) {
    mSmsCodeEt.setText(verifyCode);
    //获取焦点
    mSmsCodeEt.setFocusable(true);
    mSmsCodeEt.setFocusableInTouchMode(true);
    mSmsCodeEt.requestFocus();
    //设置光标位置
    mSmsCodeEt.setSelection(verifyCode.length());
}

##总结
至此,成功实现读取短信验证码。近来主流的App,并没有实现自动读取短信验证码,比如滴滴出行,钉钉。

##参考资料