Android开发 静态注册、动态注册、短信中心库监控获取手机验证码,自动复制到剪切板或或填入输入框。
友情提醒初学者:这是广播接收器的类,写在xml中静态注册或写在启动类的Oncreate方法下动态注册即可!有新短信通知就会触发。若使用正常的验证码填入功能,请处理完毕后在界面销毁处注销监听。

第一种方式:广播接收者 静/动注册监听短信广播 获取验证码
第二种方式:内容观察者 观察短信库的变化,根据日期获取最新短信,并解决onChange回调多次的情况。

一.通过广播接收者监控获取验证码

1.SMSReceiver.java 类代码 - -静态、动态都可用

import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SMSReceiver extends BroadcastReceiver {
    private static final String TAG = "SMSReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        //调用短信内容获取类
        getMsg(context, intent);
    }
    
    /**
     * 短信内容的获取
     * @param context
     * @param intent
     */
    private void getMsg(Context context, Intent intent) {
        //解析短信内容 pdus短信单位pdu
        if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
            Object[] pdus = (Object[]) intent.getExtras().get("pdus");
            assert pdus != null;
            for (Object pdu : pdus) {
                //封装短信参数的对象
                SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu);
                String number = sms.getOriginatingAddress(); //获取发送短信的手机号
                String body = sms.getMessageBody(); //获取接收短信手机号的完整信息
                System.out.println("发短信的手机号:"+number+" "+"监听到的短信息:" + body);
                
                //写处理逻辑 可以加线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                        //在这里写线程相关的处理逻辑
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                
                //写处理逻辑,调用相关类 匹配获取验证码并复制到剪贴板
                getCode(context, body);
            }
        }
    }
    
    /**
     * 匹配出验证码并复制到剪贴板
     * @param context
     * @param body
     */
    private void getCode(Context context, String body) {
        //获取剪贴板管理器:
        ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        Pattern pattern1 = Pattern.compile("(\\d{4,6})");//正则匹配4-6位数字
        Matcher matcher1 = pattern1.matcher(body);//进行匹配
        if (matcher1.find()) {//匹配成功
            String code = matcher1.group(0);
            // 创建普通字符型ClipData
            ClipData mClipData = ClipData.newPlainText("Label", code);
            // 将mClipData内容放到手机系统剪贴板
            cm.setPrimaryClip(mClipData);
            Toast.makeText(context, "验证码复制成功,请粘贴", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "onReceive: " + code);
        } else {
            Toast.makeText(context, "未检测到验证码,请手动输入", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "onReceive: " + "未检测到验证码");
        }
    }
}

2.动态申请权限的相关类

在项目代码中的目标获取验证码处,动态申请读取短信相关权限的代码,也可在进入软件后界面的Oncreate下搞代码,动态申请权限。强制用户同意存储及短信权限。

2.1XML中添加存储读写及短信权限

<!-- 修改或删除您的USB存储设备中的内容 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--发送及接收读取短信的权限-->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />

2.2在合适/启动界面申请获取权限

public class LoginActivity extends Activity {
	//日志打印常量
	private String TAG;
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //在onCreate方法这里调用来动态获取存储权限以及短信权限
        getReadPermissions();
    }
    
    /**
     * 权限的验证及处理,相关方法
     */
    private void getReadPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED
                    | ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECEIVE_SMS) | ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {//是否请求过该权限
                    ActivityCompat.requestPermissions(this,
                            new String[]{Manifest.permission.RECEIVE_SMS,
                                    Manifest.permission.READ_SMS,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                    Manifest.permission.READ_EXTERNAL_STORAGE}, 10001);
                } else {//没有则请求获取权限,示例权限是:存储权限和短信权限,需要其他权限请更改或者替换
                    ActivityCompat.requestPermissions(this,
                            new String[]{Manifest.permission.RECEIVE_SMS,
                                    Manifest.permission.READ_SMS,
                                    Manifest.permission.READ_EXTERNAL_STORAGE,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE}, 10001);
                }
            } else {//如果已经获取到了权限则直接进行下一步操作
                Log.e(TAG, "onRequestPermissionsResult");
            }
        }

    }

    /**
     * 一个或多个权限请求结果回调
     * 当点击了不在询问,但是想要实现某个功能,必须要用到权限,可以提示用户,引导用户去设置
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 10001:
                for (int i = 0; i < grantResults.length; i++) {
//                   如果拒绝获取权限
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                        //判断是否勾选禁止后不再询问
                        boolean flag = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);
                        if (flag) {
                            getReadPermissions();
                            return;//用户权限是一个一个的请求的,只要有拒绝,剩下的请求就可以停止,再次请求打开权限了
                        } else { // 勾选不再询问,并拒绝
                            Toast.makeText(this, "请到设置中打开权限", Toast.LENGTH_LONG).show();
                            return;
                        }
                    }
                }
//                Toast.makeText(LoginActivity.this, "权限开启完成",Toast.LENGTH_LONG).show();
                break;
            default:
                break;
        }
    }

3.静态注册广播接收者

3.1AndroidManifest.xml添加权限及广播服务

<!--发送接收读取短信的权限 都加上吧-->
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <application
        android:allowBackup="true"
        android:hardwareAccelerated="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:largeHeap="true"
        android:theme="@style/AppTheme">
        。。。。。。。。
        <receiver
            android:name=".SMSReceiver这里是静态注册写法,写广播类所在的位置">
            <intent-filter  android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>
        。。。。。。。。
    </application>

4.动态注册广播接收者

4.1在启动界面动态注册

public class LoginActivity extends Activity {
	//日志打印常量
	private String TAG;
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //动态注册广播接收者且设置为最大优先级
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.provider.Telephony.SMS_RECEIVED" );
        filter.setPriority(Integer.MAX_VALUE);//设置动态优先级为最大,1000应该不是最大
        SMSReceiver receiver = new SMSReceiver();
        registerReceiver(receiver,filter);
    }

二.通过内容监控者读取短信库获取验证码

1.SMSobserver.java 类代码

解决SMSobserver的onChang回调或获取短信不是最新,以及获取重复短信问题。

import android.content.ClipboardManager;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SMSobserver extends ContentObserver {
    private Context mContext;
    private Handler mHandler;
    private String TAG;
    private static int id=0; //这里必须用静态的,防止程序多次意外初始化情况
    
    public SMSobserver(Context context, Handler handler) {
        super(handler);
        mContext = context;
        mHandler = handler;
    }
    
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        //过滤可能界面调用初始化两次的情况
        if (uri.toString().contains("content://sms/raw")) {
            return;
        }

        Uri inboxUri = Uri.parse("content://sms/inbox");
        Cursor cursor = mContext.getContentResolver().query(inboxUri, null, null, null, "date desc");
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                String _id = cursor.getString(cursor.getColumnIndex("_id"));
                //比较id 解决重复问题
                if (id < Integer.parseInt(_id)) {
                    id = Integer.parseInt(_id);//将获取到的当前id记录,防止重复
                    String address = cursor.getString(cursor.getColumnIndex("address"));
                    String body = cursor.getString(cursor.getColumnIndex("body"));
                    String date = cursor.getString(cursor.getColumnIndex("date"));
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                    String time1 = dateFormat.format(new Date(Long.parseLong(date)));
                    //写自己的处理逻辑,我这里是复制验证码到剪切板
                    if(body.contains("短信中包含某字符才匹配复制到剪切板")) {
                    Pattern pattern = Pattern.compile("(\\d{4,6})");
                    Matcher matcher = pattern.matcher(body);
                    if (matcher.find()) {
                        String code = matcher.group(0);
                        System.out.println("验证码:" + code);
                            ClipboardManager cmb = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
                            cmb.setText(code);
                            Toast.makeText(mContext.getApplicationContext(), "复制成功" + code, Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }
            cursor.close();
        }
    }
}

2.调用注册内容监控者

public class LoginActivity extends Activity {
    //回调显示
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                String code = (String) msg.obj;
                editname.setText(code);//要显示验证码的控件id
            }
        }
    };
	//防止多次初始化
	private SMSobserver smsobserver = new SMSobserver(this, mHandler);
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //调用注册内容监控者
        getContentResolver().registerContentObserver(Uri.parse("content://sms"),true, smsobserver);
    }

参考文章:全网文章。查了全网基本都没有解决回调和筛选重复的问题,我这个请参考。