现在的应用在注册登录或者修改密码中都用到了短信验证码,那在android中是如何实现获取短信验证码并自动填写的呢?
首先,需要要在manifest中注册接收和读取短信的权限:
实现一个广播SMSBroadcastReceiver来监听短信:
package com.example.receive; import java.text.SimpleDateFormat; import java.util.Date; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.SmsMessage; /** * 短信监听 * @author * */ public class SMSBroadcastReceiver extends BroadcastReceiver { private static MessageListener mMessageListener; public static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED"; public SMSBroadcastReceiver() { super(); } @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(SMS_RECEIVED_ACTION)) { Object[] pdus = (Object[]) intent.getExtras().get("pdus"); for(Object pdu:pdus) { SmsMessage smsMessage = SmsMessage.createFromPdu((byte [])pdu); String sender = smsMessage.getDisplayOriginatingAddress(); //短信内容 String content = smsMessage.getDisplayMessageBody(); long date = smsMessage.getTimestampMillis(); Date tiemDate = new Date(date); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = simpleDateFormat.format(tiemDate); //过滤不需要读取的短信的发送号码 if ("+8613450214963".equals(sender)) { mMessageListener.onReceived(content); abortBroadcast(); } } } } //回调接口 public interface MessageListener { public void onReceived(String message); } public void setOnReceivedMessageListener(MessageListener messageListener) { this.mMessageListener = messageListener; } }
在需要填写验证码的Activity中,生产SMSBroadcastReceiver的实例,实现onReceived的回调接口。为了节约系统资源,我们使用动态注册注销广播的方法。
package com.example.smstest; import com.example.receive.SMSBroadcastReceiver; import android.os.Bundle; import android.app.Activity; import android.content.IntentFilter; import android.view.Menu; import android.widget.EditText; public class MainActivity extends Activity { private EditText edtPassword; private SMSBroadcastReceiver mSMSBroadcastReceiver; private static final String ACTION = "android.provider.Telephony.SMS_RECEIVED"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); edtPassword = (EditText) findViewById(R.id.password); } @Override protected void onStart() { super.onStart(); //生成广播处理 mSMSBroadcastReceiver = new SMSBroadcastReceiver(); //实例化过滤器并设置要过滤的广播 IntentFilter intentFilter = new IntentFilter(ACTION); intentFilter.setPriority(Integer.MAX_VALUE); //注册广播 this.registerReceiver(mSMSBroadcastReceiver, intentFilter); mSMSBroadcastReceiver.setOnReceivedMessageListener(new SMSBroadcastReceiver.MessageListener() { @Override public void onReceived(String message) { edtPassword.setText(message); } }); } @Override protected void onDestroy() { super.onDestroy(); //注销短信监听广播 this.unregisterReceiver(mSMSBroadcastReceiver); } }
上面提供了一种获取短信息验证码并自动填写的实现方式,就是直接通过短信广播监听短信。但是,这种方式有它的缺陷:当你的手机安装了其他一些短信应用(例如QQ通讯录)或者手机本身限制了权限的情况下,这种方式有可能会不起作用,无法做到自动填写,而且就算把优先级设高,也不能保证不会被别的应用“抢先”。
后来查资料知道,可以通过监听短信数据库的方式实现。监听短信数据库主要是通过ContentObserver这个类来完成。ContentObserver主要是通过Uri来监测特定的Databases的表,当ContentObserver所观察的Uri发生变化时,便会触发它。思路就是监听短信数据库中特定号码的未读短信。我们可以通过百度找到许多demo,但是我发现很多demo中存在着Bug,在接收到短信后引起崩溃。还有一种情况,当真机连接着电脑,电脑装有类似豌豆荚之类的软件的时候,手机收到短信后,豌豆荚之类的可能会把该短信的状态改成“已读”,这样也会导致崩溃。
通过调试,终于把Bug修复了,布局和短信权限就不再赘述。在MainActivity中增加一个内部类SmsContent。
/** * 监听短信数据库 */ class SmsContent extends ContentObserver { private Cursor cursor = null; public SmsContent(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); //读取收件箱中指定号码的短信 cursor = managedQuery(Uri.parse("content://sms/inbox"), new String[]{"_id", "address", "read", "body"}, " address=? and read=?", new String[]{"1065811201", "0"}, "_id desc");//按id排序,如果按date排序的话,修改手机时间后,读取的短信就不准了 MyLog.l("cursor.isBeforeFirst() " + cursor.isBeforeFirst() + " cursor.getCount() " + cursor.getCount()); if (cursor != null && cursor.getCount() > 0) { ContentValues values = new ContentValues(); values.put("read", "1"); //修改短信为已读模式 cursor.moveToNext(); int smsbodyColumn = cursor.getColumnIndex("body"); String smsBody = cursor.getString(smsbodyColumn); MyLog.v("smsBody = " + smsBody); edtPassword.setText(MatchesUtil.getDynamicPassword(smsBody)); } //在用managedQuery的时候,不能主动调用close()方法, 否则在Android 4.0+的系统上, 会发生崩溃 if(Build.VERSION.SDK_INT < 14) { cursor.close(); } } }
记得在onCreate中注册短信变化监听
SmsContent content = new SmsContent(new Handler()); //注册短信变化监听 this.getContentResolver().registerContentObserver(Uri.parse("content://sms/"), true, content);
记得注销监听
this.getContentResolver().unregisterContentObserver(content);
其中,下发的验证码短信一般都是一个字符串,其中包含6位数字,我们需要把这6位数字提取出来,我们可以用正则表达式写一个静态方法。
/** * 从字符串中截取连续6位数字 * 用于从短信中获取动态密码 * @param str 短信内容 * @return 截取得到的6位动态密码 */ public static String getDynamicPassword(String str) { Pattern continuousNumberPattern = Pattern.compile("[0-9\\.]+"); Matcher m = continuousNumberPattern.matcher(str); String dynamicPassword = ""; while(m.find()){ if(m.group().length() == 6) { System.out.print(m.group()); dynamicPassword = m.group(); } } return dynamicPassword; }
至此,android获取短信验证码并自动填写的功能就实现了。
补充:对于上面短信数据库监听中有个直接关闭游标的操作(现在已经更正):cursor.close();
但是,如果这样直接关闭的话,会引起崩溃。例如,当获取了短信密码,自动填写上了之后,按home键返回桌面,然后再进入应用,会引起应用崩溃。报的错是:
android.database.StaleDataException: Attempted to access a cursor after it has been closed
后来通过查资料得知,是用managedQuery的时候, 不能主动调用close()方法, 否则在Android 4.0+的系统上, 会发生崩溃。对版本进行一个判断再执行关闭游标的操作。
//在用managedQuery的时候,不能主动调用close()方法, 否则在Android 4.0+的系统上, 会发生崩溃 if(Build.VERSION.SDK_INT < 14) { cursor.close(); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。