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