点击访问原文
您还可以加入全栈技术交流群(QQ群号:254842154)
注册账号时,通常会需要用户填写邮箱或手机号,这两个信息对于同一个网站或App来说需要有这两个特性:唯一性,有效性。
唯一性是方便我们能准确地“认识”到用户,就像公民身份证号一样,一个用户只能对应一个身份证号,同样地,一个邮箱或手机号只能在网站上被注册一次,被用作登录的账号。
有效性是为了确保用户信息是经过验证的,这一点需要用户自己来承担。通常,我们通过向用户填写的邮箱发送激活邮件,或者向用户的手机里发送短信验证码来保证。另外,在有效性得到保证后,对用户来说也是有极大好处的,其中之一就是可以很方便地重设密码。
验证邮箱有效性流程:填写注册信息(包括邮箱) -> 注册完成 -> 系统发送激活邮件到邮箱 -> 用户登录邮箱点击激活链接 -> 验证成功。激活链接里一般会有一个激活码,后台保证该激活码的有效时间。
验证手机号有效性流程:填写注册信息(包括手机号) -> 注册完成 ->系统发送验证码到手机 -> 用户接收短信并填写验证码 -> 验证成功。
邮箱验证的过程不在本文的讨论范围,以后有空再另写文章。这里重点讨论使用手机App的场景下,如何提高用户接收短信验证码的体验。我认为可以从以下3方面入手:
短信内容
短信内容里要包含以下信息:公司名称,验证码。还有一些附加信息,如告知验证码的过期时间,公司的网站地址,甚至插播一些广告(好吧,微信朋友圈都开始打广告了,我们也别太矜持了)。这些信息的排列最好能有一个优先级。建议排序:公司名称 -> 验证码 -> 其他信息。
手机在接收到短信时,通常都会有信息预览的功能(假如该功能关闭了,那就没辙了,不过收件箱里也是有预览的)。Android手机会在顶部通知栏中显示,或弹框,iPhone手机也会弹框。预览功能只会显示短信内容的很有限部分,多余的必须点击进去才能看到,所以较好的短信应该是长成这样的:
【公司名称】验证码是:123877。xxx
公司名称最好是一个简称,这样能尽量在预览部分能看到验证码数字。
下面给出一些实际的例子:
【滴滴打车】(1445)滴滴打车验证码
验证码:119244,请于10分钟内完成手机号验证操作。【创投圈vc.cn】
验证码
验证码最好为纯数字,这样方便用户快速输入,另外,验证码的长度不宜过长,建议为6位数字。数字和字母组合就算了吧,我们没必要在这个环节难为用户。
自动填充
用户在收到短信验证码后,需要简单地记一下验证码然后把输入法切换到数字状态,输入验证码。这个过程假如不出BUG,可以在5s内完成。
现在问,可否帮用户省去这5s的时间?答案当然是可以的。
这是一个非常细节的体验,我们不做,用户也不会觉得有任何问题,但是假如我们做了,就会给用户一种“智能”的感觉。
下面以Android为例,看看我是如何帮用户省去这5s时间的。原理非常简单:手机收到验证码短信后,程序自动识别验证码并填充验证码输入框。
思路是有了,实现的方式也有多种:
1、开启一个线程,隔一段时间就去查询收件箱是否有变化,有变化再读取出来做处理。
2、注册一个短信变化的广播,收到广播后再去读取出来做处理。
3、通过ContentObserver(内容观察者)来捕获特定Uri的变化。
方案1,显然不可以取,太耗资源。
方案2,实际应用起来是会有问题的,因为短信的广播是有序广播,假如有其他应用先捕获广播并终止传递,那么我们就永远也收不到这条短信变化的广播了。
所以,综合比较起来,方案3比较可取。经过笔者实践也是没有问题的。
先来点介绍性文字吧。
“ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObserver也分为“表“ContentObserver、“行”ContentObserver,当然这是与它所监听的Uri MIME Type有关的。”
下面给出方案3的关键性代码:
1、AndroidManifest.xml文件中配置短信读取权限
<uses-permission android:name="android.permission.READ_SMS" />
2、监听短信变化的类
public class SmsContentObserver extends ContentObserver{
private static final String TAG = "SmsContentObserver";
private Context mContext;
private Handler mHandler;
public SmsContentObserver(Context context,Handler handler){
super(handler);
this.mContext = context;
this.mHandler = handler;
}
//短信格式:【白羽毛金融】您的FellowPlus验证码是:394421
@Override
public void onChange(boolean selfChange) {
Uri inBoxUri = Uri.parse("content://sms/inbox");
Cursor c = mContext.getContentResolver().query(inBoxUri, null, null, null,"date desc");
if(c != null){
while(c.moveToNext()){
String body = c.getString(c.getColumnIndex("body"));
if(body.startsWith("【白羽毛金融】")){
String verifyCode = body.substring(body.length() - 6 - 1 , body.length());
Message msg = Message.obtain();
msg.what = 1;
msg.obj = verifyCode;
mHandler.sendMessage(msg);
break;
}
}
c.close();
}
}
}
当短信有变化时,把短信内容读取出来,并按时间由高到低排序。根据自己项目需要的短信验证码的格式匹配(我这里的匹配方法写的很LOW,-_-||)。找到后,可以不用继续读取短信直接break。这里通过Handler的去更新UI。
3、注册/取消注册监听
通常是在onResum中去注册短信监听,在onStop中取消注册。
@Override
protected void onStop() {
super.onStop();
unRegisterSmsContentObservers();
}
@Override
protected void onResume() {
super.onResume();
registerSmsContentObservers();
}
private void registerSmsContentObservers() {
Uri smsUri = Uri.parse("content://sms");
//smsContentObserver是SmsContentObserver的一个实例,可以在onCreate中初始化
getContentResolver().registerContentObserver(smsUri, true,smsContentObserver);
}
private void unRegisterSmsContentObservers() { getContentResolver().unregisterContentObserver(smsContentObserver);
}
注意,虽然我们监听的是收件箱inbox,但是在注册时需要监听所有短信的变化。这句很重要:
Uri.parse("content://sms")
不能写成
Uri.parse("content://sms/inbox")
其实,通过ContentObserver还能做很多事情。更多功能等待大家去发现。
更高级的体验
在上述场景中,还是需要用户去填写手机号,接收验证码。我在考虑,有没有这样一种体验,它让用户感知到这不是一个接收验证码的过程的,但是又能很好地验证手机号的有效性呢?答案是有的,我们可以这样做:自动读取手机号,服务器发验证码,客户端接收后自动到服务端验证,最后通过。这个过程没有界面,用户唯一需要做的,就是要授权app一些权限(如发送短信的权限,读取短信的权限,android会弹框提示授权)。这视乎是一个很好的体验。
目前我只发现一个应用是这样做的,招商证券的android客户端。
另外,我还发现有些网站的验证码是通过回拨一个电话过来,然后电话里有一个机器人读验证码,你需要记下来然后填写。例如,美团外卖就是这样做的。我认为这样做是有特别原因的,比如,送外卖的需要确保手机就在你身边并且能接听电话,因为外卖来了你要接电话去取。
欢迎大家跟我交流。
网友评论
大佬可知如何解决?
当其他的非验证短信发过来的时候,你这里读取到的是以前的短信验证码(如果有的话)。
把while改为if就好了,只从最新的一条短信中读取。
我在思考三个问题:
1、可不可以不验证?或者什么样的产品才需要验证?
2、验证其实是在确定产品与手机/手机号的对话是真实有效的,那么有没有其他更好的方式,甚至不需要让用户参与。
3、手机验证码是有短信成本的。