Android實現獲取短信驗證碼並自動填充
前言
最近弄瞭個短信自動填充功能,一開始覺得很簡單,不就是動態註冊個廣播接收器去監聽短信消息不就可以瞭嗎?結果沒這麼簡單,問題就出在機型的適配上。小米的短信權限、榮耀、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};
小米短信權限問題
本來這樣權限問題就搞定瞭,但是在小米手機上就不行。小米手機會把短信歸類到通知類權限裡:
在 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獲取短信驗證碼的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android四大組件之廣播BroadcastReceiver詳解
- 詳解Android的四大應用程序組件
- Android廣播實現App開機自啟動
- Android實現一鍵鎖屏功能
- 詳解Android ContentProvider的基本原理和使用