实际应用开发中,会经常用到短信验证的功能,这个时候如果再让用户就查看短信.然后再回到界面进行短信的填写,难免有多少有些不方便,作为开发者.本着用户至上的原则我们也应该来实现验证码的自动填写功能,实现短信验证码自动填写有两种方式:

第一种,实现ContentObserver,把我们自己的Observer注册到短信服务,短信应用收到新的短信的时候会发送给我我们自己的Observer,然后我们在自己的Observer中.通过代码发送给我们的需要填充的界面就行了.这种方式是利用了观察者模式 .

SmsObserver的代码:

/**
 * 
 */
package com.example.testgetsmscode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * @author imgod 观察者模式
 */
public class SmsObserver extends ContentObserver {

	private Context context;
	private Handler handler;
	private String compileValue;

	/**
	 * @param context
	 *            上下文对象
	 * @param handler
	 *            handler对象,需要把code发送回去
	 * @param codeLength
	 *            验证码的长度,一个数字
	 */
	public SmsObserver(Context context, Handler handler, int codeLength) {
		super(handler);
		// TODO Auto-generated constructor stub
		this.context = context;
		this.handler = handler;
		compileValue = "\\d{" + codeLength + "}";
	}

	// 09-26 08:09:52.182: E/smsobserver(1239): selfChange:falseUri:content://sms/1
	// 09-26 08:09:53.692: E/smsobserver(1239): selfChange:falseUri:content://sms/raw
	// 收到短信一般来说都是执行了两次onchange方法.第一次一般都是raw的这个.这个时候虽然收到了短信.但是短信还没有写入到收件箱里面
	// 然后才是另外一个,后面的数字是该短信在收件箱中的位置
	@Override
	public void onChange(boolean selfChange, Uri uri) {
		Log.e("smsobserver", "selfChange:" + selfChange + "Uri:" + uri.toString());

		if (uri.toString().equals("content://sms/raw")) {
			return;
		}

		// 降序查询我们的数据库,原作者代码竟然uri是"content://sms/inbox",而且还加了个查询条件(时间降序..)..感觉有点多此一举..
		Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
		if (cursor != null) {
			if (cursor.moveToFirst()) {
				String address = cursor.getString(cursor.getColumnIndex("address"));
				String body = cursor.getString(cursor.getColumnIndex("body"));
				Log.e("smsobserver", "get sms:address:" + address + "body:" + body);
				cursor.close();// 最后用完游标千万记得关闭

				// 在这里我们的短信提供商的号码如果是固定的话.我们可以再加一个判断,这样就不会受到别的短信应用的验证码的影响了
				// 不然的话就在我们的正则表达式中,加一些自己的判断,例如短信中含有自己应用名字啊什么的...
				// if (!address.equals("13676900000")) {
				// return;
				// }

				// 正则表达式的使用,从一段字符串中取出六位连续的数字
				Pattern pattern = Pattern.compile(compileValue);
				Matcher matcher = pattern.matcher(body);
				if (matcher.find()) {
					// String
					Log.e("smsobserver", "code:" + matcher.group(0));
					Log.e("smsobserver", "contentObserver get code time:" + System.currentTimeMillis());

					// 利用handler将得到的验证码发送给主界面
					Message msg = Message.obtain();
					msg.what = MainActivity.msg_received_code;
					msg.obj = matcher.group(0);
					handler.sendMessage(msg);
				} else {
					Log.e("smsobserver", "没有在短信中获取到合格的验证码");
				}

			} else {
				Log.e("smsobserver", "movetofirst为false了");
			}
		} else {
			Log.e("smsobserver", "cursor为null了");
		}
	}

}


主界面中的使用方法:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
	}

	/**
	 * 将我们的ContentObserver注册一下
	 */
	private void initView() {
		// TODO Auto-generated method stub
		et_main = (EditText) findViewById(R.id.et_main);
		// 验证码长度为6
		mSmsObserver = new SmsObserver(MainActivity.this, mHandler, 6);
		Uri uri = Uri.parse("content://sms");
		getContentResolver().registerContentObserver(uri, true, mSmsObserver);
	}

注册之后,肯定要在界面销毁的时候取消注册咯:

/*
	 * (non-Javadoc) 取消注册
	 * 
	 * @see android.app.Activity#onDestroy()
	 */
	@Override
	protected void onDestroy() {
		super.onDestroy();
		getContentResolver().unregisterContentObserver(mSmsObserver);
	}

在ContentObserver构造方法中,我们传递了一个handler对象,目的就是通过handler把我们得到的短信验证码给接收过来:

public static final int msg_received_code = 1;
	public Handler mHandler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			if (msg.what == msg_received_code) {
				String code = (String) msg.obj;
				et_main.setText(code);
				Log.e("mainactivity", "activity get code time:" + System.currentTimeMillis());

			}
		};
	};

最后的最后,一定不要忘记加权限:

<!-- 千万不要忘记声明权限,没有声明权限的时候,竟然还是可以得到收到短信通知的.但是读取不到短信的内容 -->
    <uses-permission android:name="android.permission.READ_SMS" />

顺便一提:这种方式实现的验证码自动填写功能,在android2.3上竟然没有效果,在4.0上没问题,欢迎纠正和补充..

第二种,我们自己的应用创建一个广播接收器,接受短信变化的广播,然后在收到广播的时候,再把验证码提取出来发送给我们的需要填充验证码的地方就行了


广播接收器的代码:

/**
 * 
 */
package com.example.testsendandreceivesms;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.telephony.SmsMessage;
import android.util.Log;

/**
 * @author imgod
 *
 */
public class SmsReceiveBroadcastReceiver extends BroadcastReceiver {

	private Context context;
	private Handler handler;
	private int codeLength = 0;

	/**
	 * 
	 */
	public SmsReceiveBroadcastReceiver(Context context, Handler handler, int codeLength) {
		// TODO Auto-generated constructor stub
		this.context = context;
		this.handler = handler;
		this.codeLength = codeLength;
	}

	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		Bundle bundle = intent.getExtras();
		Object[] objects = (Object[]) bundle.get("pdus");
		if (objects != null) {
			String phone = null;
			StringBuffer content = new StringBuffer();
			for (int i = 0; i < objects.length; i++) {
				SmsMessage sms = SmsMessage.createFromPdu((byte[]) objects[i]);
				phone = sms.getDisplayOriginatingAddress();
				content.append(sms.getDisplayMessageBody());
			}
			Log.e("smsbc", "phone:" + phone + "\ncontent:" + content.toString());
			checkCodeAndSend(content.toString());
		}
	}

	/**
	 * @param content
	 */
	private void checkCodeAndSend(String content) {
		// 话说.如果我们的短信提供商的短信号码是固定的话.前面可以加一个判断
		// 正则表达式验证是否含有验证码
		Pattern pattern = Pattern.compile("\\d{" + codeLength + "}");// compile的是规则
		Matcher matcher = pattern.matcher(content);// matcher的是内容
		if (matcher.find()) {
			String code = matcher.group(0);
			Log.e("smsbc", "短信中找到了符合规则的验证码:" + code);
			handler.obtainMessage(MainActivity.BC_SMS_RECEIVE, code).sendToTarget();
			Log.e("smsbc", "广播接收器接收到短信的时间:" + System.currentTimeMillis());
		} else {
			Log.e("smsbc", "短信中没有找到符合规则的验证码");
		}
	}

}


主界面中的使用:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		broadcastReceiver = new SmsReceiveBroadcastReceiver(MainActivity.this, handler, 6);
		IntentFilter intentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
		registerReceiver(broadcastReceiver, intentFilter);
		initView();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.app.Activity#onDestroy()
	 */
	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		unregisterReceiver(broadcastReceiver);
	}

handler:

private Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			if (msg.what == BC_SMS_RECEIVE) {
				String code = (String) msg.obj;
				tv_sms_code.setText(code);
				Log.e("mainactivity", "界面收到短信的时间:" + System.currentTimeMillis());
			}
		};
	};

特别注意权限:  

<uses-permission android:name="android.permission.RECEIVE_SMS"/>

这种实现方式在android2.3上和android4.0上都表现良好,别的没测试,

和上一种方法一样,也是需要注册和取消注册操作的,而且,因为同样都是利用了handler把得到的内容发送回来.所以发送内容的效率产不多,不过在接受短信的效率上要比上一种方式要好.毕竟上一种方式是短信应用收到短信,然后再向他发送,而使用广播的话.直接就是从源头上进行接收短信了