Android實現(xiàn)獲取短信驗證碼并自動填充
前言
最近弄了個短信自動填充功能,一開始覺得很簡單,不就是動態(tài)注冊個廣播接收器去監(jiān)聽短信消息不就可以了嗎?結(jié)果沒這么簡單,問題就出在機型的適配上。小米的短信權(quán)限、榮耀、OPPO的短信監(jiān)聽都是坑,暫時就用這三個手機測了,其他的遇到了再補充。
下面簡單講講:
權(quán)限
申請權(quán)限
短信屬于隱私權(quán)限,Android 6.0后需要動態(tài)申請權(quán)限。首先在manifest里面注冊權(quán)限:
<uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" />
在需要用的地方,動態(tài)申請下:
String[] smsPermission = {Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_SMS};
小米短信權(quán)限問題
本來這樣權(quán)限問題就搞定了,但是在小米手機上就不行。小米手機會把短信歸類到通知類權(quán)限里:

在 ContextCompat.checkSelfPermission 的時候會直接返回true,并且不會彈出權(quán)限對話框,而是在實際使用的時候才會咨詢用戶,按理說好像和我們邏輯沒有沖突,但是在使用receiver進行監(jiān)聽前,不是得確保有權(quán)限么?實際效果也是,在沒有權(quán)限時,不能獲取到短信的廣播。
小米短信權(quán)限解決
在網(wǎng)上找了找辦法,好像也沒多少博文,但是大致有了思路:不是用的時候才申請么?那我就先用一下,再去用receiver監(jiān)聽。下面是方法:
// 讀取一下試試,能讀取到就有權(quán)限
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進行監(jiān)聽
編寫SmsReceiver
這里也是網(wǎng)上隨便找了個代碼,能用,但是在榮耀手機上卻是偶爾能收到一次,后面幾次就收不到了,打了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()方法就是從過濾后的意圖中獲取攜帶的數(shù)據(jù),
// 這里攜帶的是以“pdus”為key、短信內(nèi)容為value的鍵值對
// android設(shè)備接收到的SMS是pdu形式的
Bundle bundle = intent.getExtras();
SmsMessage msg;
if (null != bundle){
//生成一個數(shù)組,將短信內(nèi)容賦值進去
Object[] smsObg = (Object[]) bundle.get("pdus");
//遍歷pdus數(shù)組,將每一次訪問得到的數(shù)據(jù)方法object中
for (Object object:smsObg){
//獲取短信
msg = SmsMessage.createFromPdu((byte[])object);
//獲取短信內(nèi)容
String content = msg.getDisplayMessageBody();
Log.d("SmsReceiver", "onReceive: content = " + content);
//獲取短信發(fā)送方地址
String from = msg.getOriginatingAddress();
Log.d("SmsReceiver", "onReceive: from = " + from);
// TODO ...
}
}
}
}
}使用方法:
// 使用廣播進行監(jiān)聽
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);
接觸監(jiān)聽,最好在收到短信的時候就取消注冊廣播:
context.unregisterReceiver(smsReceiver);
解決OPPO手機無法接收短信廣播問題
本來小米榮耀都搞定了,給測試一測,結(jié)果又不行了。收不到廣播,用下面的ContentObserver還總拿不到對的數(shù)據(jù)。找了下資料,發(fā)現(xiàn)OPPO手機需要在短信APP進行設(shè)置。
ps. 后面發(fā)現(xiàn)華為、榮耀都是這樣,會對驗證碼進行保護。可以使用ContentObserver 監(jiān)聽,能觸發(fā)onChange,但是拿不到Uri,不過可以使用查詢,拿到倒敘的第一條數(shù)據(jù),取出其中的date屬性,比對監(jiān)聽時的時間,如果短信兩分鐘有效,那就看看第一條數(shù)據(jù)是不是在兩分鐘內(nèi),如果不是,那就是沒拿到,問題就出在用戶開啟了短信驗證碼保護,可以提示用戶自行輸入驗證碼(畢竟這個不是我們的鍋)。
解決方法: 在短信 -> 短信設(shè)置里面 -> 打開禁止后臺應(yīng)用讀取驗證碼
解決榮耀無法連續(xù)監(jiān)聽短信的問題
既然上面的方法沒用了,只能找新的辦法嘍,網(wǎng)上很多提供了兩種辦法,第二種就是通過ContentResolver去監(jiān)聽短信添加的更新動作,其實也和廣播類似,代碼如下:
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,用于注冊內(nèi)容觀察者
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: ");
// 當(dāng)收到短信時調(diào)用一次,當(dāng)短信顯示到屏幕上時又調(diào)用一次,所以需要return掉一次調(diào)用
if(uri.toString().equals("content://sms/raw")){
return;
}
// 讀取短信收件箱,只讀取未讀短信,即read=0,并按照默認(rèn)排序
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()) {
// 讀取短信發(fā)送人
String address = cursor.getString(cursor.getColumnIndex(Telephony.Sms.ADDRESS));
Log.d("ReadSmsObserver", "onChange: address = " + address);
// 讀取短息內(nèi)容
String smsBody = cursor.getString(cursor.getColumnIndex(Telephony.Sms.BODY));
Log.d("ReadSmsObserver", "onChange: smsBody = " + smsBody);
// TODO 傳遞出去,最好切下線程
}
// 關(guān)閉cursor的方法
cursor.close();
}
}用的時候要注冊和取消注冊:
// 使用ContentResolver進行監(jiān)聽
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去監(jiān)聽短信變化,明明onChange觸發(fā)了,但是去拿短信就是拿不到最新的,開了上面的設(shè)置也不行,弄了好久。
最后想的解決辦法是,兩種方式同時監(jiān)聽,在onChange觸發(fā)后等待三秒鐘(開始試了1s還不行),看看有沒有onReceive,如果有就直接使用onReceive的短信,如果沒有再驗證onChange內(nèi)拿到的短信,看看是不是有效時間內(nèi)的,連倒敘第一個都在有效時間外,那就是沒拿到了,直接舍棄了。
PS. 后續(xù)更新,感覺這些問題都可能是手機系統(tǒng)開了短信驗證碼保護。
思路是這樣,做起來不麻煩,用個handler就可以解決,讀者自行處理吧。
以上就是Android實現(xiàn)獲取短信驗證碼并自動填充的詳細(xì)內(nèi)容,更多關(guān)于Android獲取短信驗證碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中TelephonyManager類的用法案例詳解
這篇文章主要介紹了Android中TelephonyManager類的用法,以獲取Android手機硬件信息為例詳細(xì)分析了TelephonyManager類的使用技巧,需要的朋友可以參考下2015-09-09
Android創(chuàng)建簡單發(fā)送和接收短信應(yīng)用
收發(fā)短信應(yīng)該是每個手機最基本的功能之一了,即使是許多年前的老手機也都會具備這項功能,而Android 作為出色的智能手機操作系統(tǒng),自然也少不了在這方面的支持。今天我們開始自己創(chuàng)建一個簡單的發(fā)送和接收短信的應(yīng)用,需要的朋友可以參考下2016-04-04
Android中post請求傳遞json數(shù)據(jù)給服務(wù)端的實例
下面小編就為大家分享一篇Android中post請求傳遞json數(shù)據(jù)給服務(wù)端的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android SQLite數(shù)據(jù)庫連接實現(xiàn)登錄功能
這篇文章主要為大家詳細(xì)介紹了Android SQLite數(shù)據(jù)庫連接實現(xiàn)登錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-10-10
Android?ContentObserver?監(jiān)聽短信思路詳解
ContentObserver允許在Android中監(jiān)控特定數(shù)據(jù)的變化,可用于短信等應(yīng)用的數(shù)據(jù)監(jiān)聽,開發(fā)者可通過繼承ContentObserver并實現(xiàn)onChange方法來定義當(dāng)目標(biāo)內(nèi)容變化時的響應(yīng)行為,感興趣的朋友一起看看吧2024-09-09
Caused by: android.os.NetworkOnMainThreadException錯誤解決辦法
這篇文章主要介紹了Caused by: android.os.NetworkOnMainThreadException錯誤解決辦法,本文提供了2種解決方法,需要的朋友可以參考下2014-07-07
[Android] 通過GridView仿微信動態(tài)添加本地圖片示例代碼
本篇文章主要介紹了[Android] 通過GridView仿微信動態(tài)添加本地圖片示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01

