Android实现获取短信验证码并自动填充


Posted in Java/Android onMay 21, 2023

前言

最近弄了个短信自动填充功能,一开始觉得很简单,不就是动态注册个广播接收器去监听短信消息不就可以了吗?结果没这么简单,问题就出在机型的适配上。小米的短信权限、荣耀、OPPO的短信监听都是坑,暂时就用这三个手机测了,其他的遇到了再补充。

下面简单讲讲:

权限

申请权限

短信属于隐私权限,Android 6.0后需要动态申请权限。首先在manifest里面注册权限:

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

在需要用的地方,动态申请下:

String[] smsPermission = {Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_SMS};

小米短信权限问题

本来这样权限问题就搞定了,但是在小米手机上就不行。小米手机会把短信归类到通知类权限里:

Android实现获取短信验证码并自动填充

在 ContextCompat.checkSelfPermission 的时候会直接返回true,并且不会弹出权限对话框,而是在实际使用的时候才会咨询用户,按理说好像和我们逻辑没有冲突,但是在使用receiver进行监听前,不是得确保有权限么?实际效果也是,在没有权限时,不能获取到短信的广播。

小米短信权限解决

在网上找了找办法,好像也没多少博文,但是大致有了思路:不是用的时候才申请么?那我就先用一下,再去用receiver监听。下面是方法:

// 读取一下试试,能读取到就有权限
boolean flag = false;
try {
    Uri uri = Uri.parse("content://sms/inbox");
    ContentResolver cr = context.getContentResolver();
    String[] projection = new String[]{"_id"};
    Cursor cur = cr.query(uri, projection, null, null, "date desc");
    if (null != cur) {
        cur.close();
    }
    lag = true;
}catch (Exception e) {
    e.printStackTrace();
}

这里仅针对小米手机啊,对小米手机的判断我只是用 android.os.Build.MANUFACTURER 简单判断了下,如果有更高要求请查找资料。

使用Receiver进行监听

编写SmsReceiver

这里也是网上随便找了个代码,能用,但是在荣耀手机上却是偶尔能收到一次,后面几次就收不到了,打了log也没进入到onReceive中,这就很离奇了,排查了很久。同样的代码,在小米手机上是没问题的,那就只可能是适配问题了。

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

public class SmsReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //Toast.makeText(context, "收到信息", Toast.LENGTH_LONG).show();
        Log.d("SmsReceiver", "onReceive: " + intent.getAction());
        if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")){
            //intent.getExtras()方法就是从过滤后的意图中获取携带的数据,
            // 这里携带的是以“pdus”为key、短信内容为value的键值对
            // android设备接收到的SMS是pdu形式的
            Bundle bundle = intent.getExtras();
            SmsMessage msg;
            if (null != bundle){
                //生成一个数组,将短信内容赋值进去
                Object[] smsObg = (Object[]) bundle.get("pdus");
                //遍历pdus数组,将每一次访问得到的数据方法object中
                for (Object object:smsObg){
                    //获取短信
                    msg = SmsMessage.createFromPdu((byte[])object);
                    //获取短信内容
                    String content = msg.getDisplayMessageBody();
                    Log.d("SmsReceiver", "onReceive: content = " + content);
                    //获取短信发送方地址
                    String from = msg.getOriginatingAddress();
                    Log.d("SmsReceiver", "onReceive: from = " + from);

                    // TODO ...
                }
            }
        }
    }
}

使用方法:

// 使用广播进行监听
IntentFilter smsFilter = new IntentFilter();
smsFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
smsFilter.addAction("android.provider.Telephony.SMS_DELIVER");
if (smsReceiver == null) {
    smsReceiver = new SmsReceiver();
}
smsReceiver.setCallbackContext(callbackContext);
context.registerReceiver(smsReceiver, smsFilter);

接触监听,最好在收到短信的时候就取消注册广播:

context.unregisterReceiver(smsReceiver);

解决OPPO手机无法接收短信广播问题

本来小米荣耀都搞定了,给测试一测,结果又不行了。收不到广播,用下面的ContentObserver还总拿不到对的数据。找了下资料,发现OPPO手机需要在短信APP进行设置。

ps. 后面发现华为、荣耀都是这样,会对验证码进行保护。可以使用ContentObserver 监听,能触发onChange,但是拿不到Uri,不过可以使用查询,拿到倒叙的第一条数据,取出其中的date属性,比对监听时的时间,如果短信两分钟有效,那就看看第一条数据是不是在两分钟内,如果不是,那就是没拿到,问题就出在用户开启了短信验证码保护,可以提示用户自行输入验证码(毕竟这个不是我们的锅)。

解决方法: 在短信 -> 短信设置里面 -> 打开禁止后台应用读取验证码

解决荣耀无法连续监听短信的问题

既然上面的方法没用了,只能找新的办法喽,网上很多提供了两种办法,第二种就是通过ContentResolver去监听短信添加的更新动作,其实也和广播类似,代码如下:

import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.provider.Telephony;
import android.util.Log;

import androidx.annotation.RequiresApi;

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class ReadSmsObserver extends ContentObserver {

    private final Context context;

    public ReadSmsObserver(Handler handler, Context context) {
        super(handler);
        this.context = context;
    }

    private static final String SMS_INBOX_URI = "content://sms/inbox";//API level>=23,可直接使用Telephony.Sms.Inbox.CONTENT_URI,用于获取cusor
    // private static final String SMS_URI = "content://sms";//API level>=23,可直接使用Telephony.Sms.CONTENT_URI,用于注册内容观察者
    private static final String[] PROJECTION = new String[]{
            Telephony.Sms._ID,
            Telephony.Sms.ADDRESS,
            Telephony.Sms.BODY,
            Telephony.Sms.DATE
    };

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange);
        Log.d("ReadSmsObserver", "onChange: ");
        // 当收到短信时调用一次,当短信显示到屏幕上时又调用一次,所以需要return掉一次调用
        if(uri.toString().equals("content://sms/raw")){
            return;
        }
        // 读取短信收件箱,只读取未读短信,即read=0,并按照默认排序
        Cursor cursor = context.getContentResolver().query(Uri.parse(SMS_INBOX_URI), PROJECTION,
                Telephony.Sms.READ + "=?", new String[]{"0"}, Telephony.Sms.Inbox.DEFAULT_SORT_ORDER);
        if (cursor == null) return;
        // 获取倒序的第一条短信
        if (cursor.moveToFirst()) {
            // 读取短信发送人
            String address = cursor.getString(cursor.getColumnIndex(Telephony.Sms.ADDRESS));
            Log.d("ReadSmsObserver", "onChange: address = " + address);
            // 读取短息内容
            String smsBody = cursor.getString(cursor.getColumnIndex(Telephony.Sms.BODY));
            Log.d("ReadSmsObserver", "onChange: smsBody = " + smsBody);
            
            // TODO 传递出去,最好切下线程

        }
        // 关闭cursor的方法
        cursor.close();
    }
}

用的时候要注册和取消注册:

// 使用ContentResolver进行监听
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    if (smsObserver == null) {
        smsObserver = new ReadSmsObserver(new SmsHandler(), context);
    }
    smsObserver.setCallbackContext(callbackContext);
    context.getContentResolver().registerContentObserver(
        Uri.parse("content://sms/"), true, smsObserver);
}

取消注册:

context.getContentResolver().unregisterContentObserver(smsObserver);

解决OPPO手机无法拿到最新短信问题

很神奇啊,每次使用ContentObserver去监听短信变化,明明onChange触发了,但是去拿短信就是拿不到最新的,开了上面的设置也不行,弄了好久。

最后想的解决办法是,两种方式同时监听,在onChange触发后等待三秒钟(开始试了1s还不行),看看有没有onReceive,如果有就直接使用onReceive的短信,如果没有再验证onChange内拿到的短信,看看是不是有效时间内的,连倒叙第一个都在有效时间外,那就是没拿到了,直接舍弃了。

PS. 后续更新,感觉这些问题都可能是手机系统开了短信验证码保护。

思路是这样,做起来不麻烦,用个handler就可以解决,读者自行处理吧。

以上就是Android实现获取短信验证码并自动填充的详细内容,更多关于Android获取短信验证码的资料请关注三水点靠木其它相关文章!

Java/Android 相关文章推荐
实体类或对象序列化时,忽略为空属性的操作
Jun 30 Java/Android
详细了解MVC+proxy
Jul 09 Java/Android
SpringBoot整合Mybatis Generator自动生成代码
Aug 23 Java/Android
Java数据开发辅助工具Docker与普通程序使用方法
Sep 15 Java/Android
Java8中接口的新特性使用指南
Nov 01 Java/Android
Eclipse+Java+Swing+Mysql实现电影购票系统(详细代码)
Jan 18 Java/Android
关于EntityWrapper的in用法
Mar 22 Java/Android
SpringBoot整合Minio文件存储
Apr 03 Java/Android
Android在Sqlite3中的应用及多线程使用数据库的建议
Apr 24 Java/Android
Android Studio 计算器开发
May 20 Java/Android
Android中的Launch Mode详情
Jun 05 Java/Android
maven 解包依赖项中的文件的解决方法
Jul 15 Java/Android
Java结构型设计模式之组合模式详解
Sep 23 #Java/Android
Java Redisson多策略注解限流
Sep 23 #Java/Android
Android移动应用开发指南之六种布局详解
Sep 23 #Java/Android
Java中的Kafka为什么性能这么快及4大核心详析
Mybatis 一级缓存和二级缓存原理区别
Sep 23 #Java/Android
Java实现贪吃蛇游戏的示例代码
Sep 23 #Java/Android
Java获取字符串编码格式实现思路
Sep 23 #Java/Android
You might like
php 无限分类的树类代码
2009/12/03 PHP
php网站判断用户是否是手机访问的方法
2013/11/01 PHP
PHP PDOStatement对象bindpram()、bindvalue()和bindcolumn之间的区别
2014/11/20 PHP
PHP+JQuery+Ajax实现分页方法详解
2016/08/06 PHP
探究Laravel使用env函数读取环境变量为null的问题
2016/12/06 PHP
jQuery学习笔记之Helloworld
2010/12/22 Javascript
Javascript 页面模板化很多人没有使用过的方法
2012/06/05 Javascript
js获得鼠标的坐标值的方法
2013/03/13 Javascript
jQuery登陆判断简单实现代码
2013/04/21 Javascript
Document:getElementsByName()使用方法及示例
2013/10/28 Javascript
Jquery插件分享之气泡形提示控件grumble.js
2014/05/20 Javascript
JavaScript面对国际化编程时的一些建议
2015/06/24 Javascript
javascript实现添加附件功能的方法
2015/11/18 Javascript
js实现可控制左右方向的无缝滚动效果
2016/05/29 Javascript
JavaScript在form表单中使用button按钮实现submit提交方法
2017/01/23 Javascript
微信小程序scroll-view实现字幕滚动
2018/07/14 Javascript
layer.confirm点击第一个按钮关闭弹出框的方法
2019/09/09 Javascript
详解微信小程序中var、let、const用法与区别
2020/01/11 Javascript
[37:03]完美世界DOTA2联赛PWL S3 INK ICE vs GXR 第二场 12.16
2020/12/18 DOTA
python备份文件的脚本
2008/08/11 Python
本地文件上传到七牛云服务器示例(七牛云存储)
2014/01/11 Python
Python中输出ASCII大文字、艺术字、字符字小技巧
2015/04/28 Python
Python中协程用法代码详解
2018/02/10 Python
python 常见字符串与函数的用法详解
2018/11/23 Python
python用plt画图时,cmp设置方法
2018/12/13 Python
Python使用Selenium爬取淘宝异步加载的数据方法
2018/12/17 Python
python解析json串与正则匹配对比方法
2018/12/20 Python
美国销售第一的智能手机和平板电脑保护壳:OtterBox
2017/12/21 全球购物
限量版运动鞋和街头服饰:TheDrop
2020/09/06 全球购物
西班牙品牌鞋子、服装和配饰在线商店:Esdemarca
2021/02/17 全球购物
房地产活动策划方案
2014/05/14 职场文书
学习雷锋月活动总结
2014/07/03 职场文书
幼儿园教师师德师风演讲稿:我自豪我是一名幼师
2014/09/10 职场文书
财务总监岗位职责范本
2015/04/03 职场文书
jQuery ajax - getScript() 方法和getJSON方法
2021/05/14 jQuery
Python matplotlib可视化之绘制韦恩图
2022/02/24 Python