读取短信并复制验证码小工具

获取短信有两种方式,第一种是通过广播接收器,第二种通过读取短信数据库

第一种:

静态注册了一个广播,通过接收包含android.provider.Telephony.SMS_RECEIVED动作的广播,获取新收到的短信内容。

        <receiver
            android:name=".GetSMS"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

想要接收广播,并且读取广播内容,声明权限自然就少不了。6.0之后的系统则需要进行运行时权限处理。

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

当新收到短信后,广播接收器就会收到一个有序广播,回调onReceive(Context context,Intent intent)方法。在该方法中执行读取短信的逻辑。

public class GetSMS extends BroadcastReceiver {
    @TargetApi(24)
    public void onReceive(Context context, Intent intent){
        Bundle bundle=intent.getExtras();
        String format=intent.getStringExtra("format");
        Object[] obj=(Object[]) bundle.get("pdus");
        for(Object object:obj){
            SmsMessage smsMessage=SmsMessage.createFromPdu((byte[])object,format);
            Toast.makeText(context,smsMessage.getDisplayMessageBody(),Toast.LENGTH_SHORT).show();
            Log.d("GetSMS",smsMessage.getDisplayMessageBody());
        }
    }
}

分析下上面的代码,通过intent.getExtras()获取Intent中附加的Bundle数据,并通过Bundle取出key为“pdus”对象数组,这里就涉及到短信的格式了,PDU(Protocol Data Unit,协议数据单元),PDU模式下不仅能发送中文短信,也能发送英文短信。PDU收发短信有三种编码可用:7-bit、8-bit和UCS2编码。7-bit编码用于发送普通的ASCII字符,即英文短信,最多可发送160字符。8- bit编码通常用于发送数据消息。UCS2编码用于发送Unicode字符,可发送中文字符,最多发送70字符。如果发送的短信过长,就会得到多组PDU格式的短信,因此可以解释从Bundle取出的“pdus”为对象数组。然后将对象数组中的每个object转化为SmsMessage。可以通过SmsMessage对象得到短信中的内容。

SmsMessage.createFromPdu((byte[ ])object,format) 中的(byte[ ])object可以如下理解:

byte[ ] A=new byte[10];

Object B=A;

byte[ ]  A=(byte[ ])B;  也就是说接收到的短信数据原本就是字节数组。另外:

createFromPdu(byte[ ] pdu) 这个方法不推荐使用了,然后查阅安卓的参考说从 API level 23 以后应该用createFromPdu(byte[], String)代替. 继续查阅得知第二个参数应该是短信的类型, 大概是GSM与CDMA短信的解码方式不同,所以应该传入这个参数, 那么, 我应该怎么得到这个格式字符串呢?

在安卓源码中的Telephony类中有相关的示例方法.按照这个方法代码示例可以解决这个问题.

public static SmsMessage[] getMessagesFromIntent(Intent intent) {
    Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
    String format = intent.getStringExtra("format");
     int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,SubscriptionManager.getDefaultSmsSubId());

    Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId);

    int pduCount = messages.length;
    SmsMessage[] msgs = new SmsMessage[pduCount];

    for (int i = 0; i < pduCount; i++) {
        byte[] pdu = (byte[]) messages[i];
        msgs[i] = SmsMessage.createFromPdu(pdu, format);
        msgs[i].setSubId(subId);
    }
    return msgs;
}

得到SmsMessage对象后,就可以调用getDisplayMessageBody获得短信文本。

BUT!!!因为很多手机系统都是国内厂商定制的,这个系统广播会被拦截,因此系统发出的这个有序广播无论你的接收器权限多么高,都无法接收到。这就需要用第二种方法读取短信啦。

第二种:

先了解一下什么是ContentObserver。

ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。

注册/取消注册ContentObserver方法,抽象类ContentResolver类中的方法原型如下:

public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)

功能:为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。

参数:uri 需要观察的Uri(需要在UriMatcher里注册,否则该Uri也没有意义了)notifyForDescendents 为false 表示精确匹配,即只匹配该Uri为true 表示可以同时匹配其派生的Uri,举个栗子:

假设UriMatcher 里注册的Uri共有一下类型:

1 、content://com.test.zz/teacher

2 、content://com.test.zz/teacher/#

3、 content://com.test.zz/teacher/new (派生的Uri)

假设我们当前需要观察的Uri为content://com.test.zz/teacher,如果发生数据变化的Uri为content://com.test.zz/teacher/new,当notifyForDescendents为 false,那么该ContentObserver会监听不到,但是当notifyForDescendents 为ture,能捕捉该Uri的数据库变化。

public final voidunregisterContentObserver(ContentObserver observer)

功能:取消对给定Uri的观察

参数: observer ContentObserver的派生类实例

voidonChange(boolean selfChange)

功能:当观察到的Uri发生变化时,回调该方法去处理。所有ContentObserver的派生类都需要重载该方法去处理逻辑。

观察特定Uri的步骤如下:

1、 创建我们特定的ContentObserver派生类,必须调用父类构造方法,必须重载onChange()方法去处理回调后的功能实现

2、 利用context.getContentResolover()获得ContentResolove对象,接着调用registerContentObserver()方法去注册内容观察者

3、 由于ContentObserver的生命周期不同步于Activity和Service等,因此,在不需要时,需要手动的调用unregisterContentObserver()去取消注册。

短信的Uri共有一下几种:

content://sms/inbox 收件箱

content://sms/sent 已发送

content://sms/draft 草稿

content://sms/outbox 发件箱 (正在发送的信息)

content://sms/failed 发送失败

content://sms/queued 待发送列表 (比如开启飞行模式后,该短信就在待发送列表里)

了解了上面的知识后就可以开始第二种方法的编码啦。代码如下:

public class GetService extends Service {
    private String SMS_URI="content://sms/";
    private SMSObserver smsObserver;

    public GetService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    public int onStartCommand(Intent intent,int flags,int id){
        Uri uri=Uri.parse(SMS_URI);
        smsObserver=new SMSObserver(handler);
        getContentResolver().registerContentObserver(uri,true,smsObserver);
        return START_STICKY;
    }
    public class SMSObserver extends ContentObserver {
        public SMSObserver(Handler handler){
            super(handler);
        }
        public void onChange(boolean a){
            super.onChange(a);
            StringBuilder sbuilder=new StringBuilder();
            StringBuilder sbuilder2=new StringBuilder();
            Uri uri=Uri.parse(SMS_URI);
            Cursor cursor=getContentResolver().query(uri,new String[]{"body"},"read=?",new String[]{"0"},null);
            if(cursor.moveToFirst()){
                do{
                    String body=cursor.getString(cursor.getColumnIndex("body"));
                    sbuilder.append(body);
                }while(cursor.moveToNext());
            }
            String sms=sbuilder.toString();
            Pattern pattern=Pattern.compile("[0-9]{3,}");
            Matcher matcher=pattern.matcher(sms);
            if(matcher.find()){
                sbuilder2.append(matcher.group());
            }
            ClipboardManager clipboardManager=(ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            clipboardManager.setPrimaryClip(ClipData.newPlainText(null,sbuilder2.toString()));
            Toast.makeText(getApplicationContext(),"已将验证码复制到剪贴板",Toast.LENGTH_SHORT).show();
            sbuilder.setLength(0);
            sbuilder2.setLength(0);
            cursor.close();
        }
    }
    public Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    public void onDestroy(){
        super.onDestroy();
        if(smsObserver!=null){
            getContentResolver().unregisterContentObserver(smsObserver);
        }
    }
}

着重分析onChange方法中的逻辑。通过ContentResolver查询未读短信。read=0表示短信未读,为1表示短信已读。遍历Cursor,获得短信的内容。是不是感觉这个方法更简单?接着通过正则表达式来截取短信内容中的验证码。

那么如何将得到的验证码复制到剪贴板呢?利用ClipboardManager就能实现。利用newPlainText(label, text)返回ClipData对象,数据是文字text,描述是label,MIME类型是MIMETYPE_TEXT_PLAIN,将验证码封装在ClipData对象中,利用setPrimaryClip(ClipData data)将数据保存在剪贴板中。这样就成功实现了读取短信,复制验证码的功能啦。

sms主要结构:

  

  _id:短信序号,如100

  

  thread_id:对话的序号,如100,与同一个手机号互发的短信,其序号是相同的

  

  address:发件人地址,即手机号

  

  person:发件人,如果发件人在通讯录中则为具体姓名,陌生人为null

  

  date:日期,long型,如1346988516,可以对日期显示格式进行设置

  

  protocol:协议0SMS_RPOTO短信,1MMS_PROTO彩信

  

  read:是否阅读0未读,1已读

  

  status:短信状态-1接收,0complete,64pending,128failed

  

  type:短信类型1是接收到的,2是已发出

  

  body:短信具体内容

  

  service_center:短信服务中心号码编号

demo地址:https://github.com/zycoJamie/GetCode

时间: 2024-10-10 20:54:09

读取短信并复制验证码小工具的相关文章

Android自动读取短信验证码

Android自动读取短信验证码 extends:http://www.cnblogs.com/jiayaguang/p/4366384.html 实现自动获取手机的短信验证码,原理通过监听短信数据库的变化来解析短信,获取验证码. 直接附上代码: 1.建立一个监听数据库的类 import java.util.regex.Matcher; import java.util.regex.Pattern; import android.app.Activity; import android.data

Android之发送短信和接收验证码

最近项目需求需要发送短信和接收验证码并将验证码显示在输入框中 以下是我的记录 前提---权限 <uses-permission android:name="android.permission.SEND_SMS"></uses-permission> <uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission> <

Android读取短信和联系人

读取短信和联系人经常会用到,要了解的是这是内容提供者的知识点 先说短信,要从数据库中获取数据,就要了解其结构 短信数据库在data-->data中的下面路径 它的表结构如下,有3个是我们要注意的 Java代码为 //获取内容提供者 ContentResolver contentResolver = getContentResolver(); //获取短信表的路径 Uri uri = Uri.parse("content://sms"); //设置要查询的列名 String[] l

java:使用正则提取字符串中的数字(例如提取短信中的验证码)

使用java正则可以很方便的从字符串中提取符合条件的内容. 1.提取字符串中所有的手机号: private void getPhoneNum(String smsBody) { Pattern pattern = Pattern.compile("(13|14|15|18)\\d{9}"); Matcher matcher = pattern.matcher(smsBody); while (matcher.find()) { System.out.println(matcher.gr

iOS使用技能 - 短信,语言验证码的获取与验证小结

最近有学习一个小技能,这里小结一下,分享给大家,互相交流. 首先是大体步骤: 在mob官网注册,然后添加短信验证的应用 使用cocoapods导入框架 Podfile文件: platform :ios, "6.0" target '短信验证'do # Mob产品公共库pod 'MOBFoundation_IDFA'# SMSSDK必须 pod 'SMSSDK' end 3.在AppDelegate注册应用AppKey 4.获取验证码 5.提交验证码 6.注意点:适配要记得开启https

视频短信、企业营销小视频营销策略与方案视频制作

本文原文来自于速码云官方网站:http://www.4006026717.com/News/NewsPaper.asp?id=329 顾名思义,视频短信就是类似发送短信模式的视频发送,也有人称之为:视频彩信.影信等称呼,总得来说,就是可以将一定体积的小视频按照手机号进行一对一精准发送. 视频短信其实和彩信很相似,彩信可以支持图文彩信.音频彩信和视频彩信发送,通过手机号码进行精准发送,但是支持大小为90kb以内(理论值是90kb,80kb以上的内容发送失败率很高,一般建议80kb以内.). 视频短

Java生成验证码小工具

无意中看到一个生成简易验证码的小工具类: 工具类代码: import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import java.util.Random; i

Android中读取短信信息

Android中读取的短信文件有 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /**  * 所有的短信  */ public static final String SMS_URI_ALL = "content://sms/"; /**  * 收件箱短信  */ public static final String SMS_URI_INBOX = "content://sms/inbox"; /**  * 发件箱短信  */ p

自动读取短信中心号码

前言 短信中心号码概念 短信中心号码类似信息服务器,如果信息中心号码不正确,短消息是无法发送成功的,各个地区都有自己的信息中心号码,其中移动以+86138开头,关于该号码的获取这举例说明. 获取方法 1.调用Phone中的getSmscAddress(Message message)方法,其中参数的message为获取到结果后发送消息给mHandler,并查询结果AsyncResult的result属性中. phone=PhoneFactory.getDefaultPhone(); phone.