今天想來寫關於「簡訊讀取」的功能
雖然現代這個時代手機簡訊(SMS)幾本上已淪落為詐騙訊息集散地(笑)
不過換個角度想,以現在機號約等於個人身份的情況下,透過簡訊驗證手機號碼其實還是蠻可靠的
而在手機驗證的UX中,我們也會期望APP在收到簡訊的瞬間就幫我們將號碼填進去...不能說是一般常識,但只能說「有的話更好」吧!
於是今天的實作就是要來模擬「接收簡訊動態驗證碼並填入」的一個實作,來看範例吧
然後Github
->https://github.com/thumbb13555/AndroidBlogExamples/tree/main/SMSReader
1. 前言
首先今天的這個功能我的重點會放在「如何讀取簡訊」,所以關於那個四位數的UI的函式庫我也只是隨便找個來用而已
在這邊我用的是這個
-> https://github.com/ChaosLeung/PinView
不過如果是做開發的話,我個人會使用這兩個
-> https://github.com/Wynsbin/VerificationCodeInputView
-> https://github.com/wei-gong/VerifyCodeView
總之這部分自己評斷使用,我就不深入說明
2. 載入需要的庫&介面
好的,那麼接下來就是載入需要的函式庫與介面
剛剛也說了,這次我的重點會在讀簡訊,而非函式庫
所以這部分我就簡單帶過
首先一樣build.gradle加入
implementation 'io.github.chaosleung:pinview:1.4.4'
記得Sync(笑)
然後介面簡單直接給
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.chaos.view.PinView android:id="@+id/pin_view" style="@style/PinWidget.PinView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:cursorVisible="true" android:hint="0000" android:inputType="number" android:textColor="#000000" app:lineColor="#4DB6AC" app:cursorColor="#4DB6AC" app:cursorWidth="2dp" app:hideLineWhenFilled="false" app:itemCount="4" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:viewType="line" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.54" /> </androidx.constraintlayout.widget.ConstraintLayout>
3. 寫入程式
再來來看一下這次的檔案結構
除了MainActivity之外,我另外寫了SMSContent這隻檔案作為讀取簡訊的模組
但在這之前..首先我們要替讀取簡訊加入權限
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.SMSReader" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="android.app.lib_name" android:value="" /> </activity> </application> </manifest>
再來我直接PO讀取簡訊的程式內容
SMSContent.java
public class SMSContent extends ContentObserver { private final OnCallback callback; private final Context context; public SMSContent(Handler handler,Context context,OnCallback callback) { super(handler); this.context = context; this.callback = callback; } @Override public void onChange(boolean selfChange, @Nullable Uri uri) { super.onChange(selfChange, uri); Cursor cursor = null; ContentResolver resolver = context.getContentResolver(); //確認權限後,設置讀取SMS箱 if (ContextCompat.checkSelfPermission(context, "android.permission.READ_SMS") == PackageManager.PERMISSION_GRANTED) { cursor = resolver.query(Uri.parse("content://sms"), null, null , null, "_id desc"); } if (cursor != null && cursor.getCount() > 0) { //讀取簡訊後,並將之設為已讀 ContentValues values = new ContentValues(); values.put("read", "1"); cursor.moveToNext(); assert uri != null; if ("content://sms/raw".equals(uri.toString())) { //取得動態簡訊內容 int smsBodyColumn = cursor.getColumnIndex("body"); String code = getDynamicCode(cursor.getString(smsBodyColumn)); wait(1500); callback.callback(code); cursor.close(); } } } /**驗證簡訊內容*/ public String getDynamicCode(String str) { //檢測簡訊內容,以正規表達式抓出數字 Pattern continuousNumberPattern = Pattern.compile("[0-9\\.]"); Matcher m = continuousNumberPattern.matcher(str); //填入數字 StringBuilder stringBuilder = new StringBuilder(); while (m.find()) { stringBuilder.append(m.group()); } return stringBuilder.toString(); } /**收到簡訊後的延遲時間*/ private void wait(int ms) { try { TimeUnit.MILLISECONDS.sleep(ms); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } public interface OnCallback{ void callback(String code); } }
...我想我已經把說明寫在註解了,應該沒什麼好講的吧哈哈XD
不過還是說明一下,基本上繼承了ContentObserver的類,他都會去偵測系統通知所發生的變化
而簡訊的收發基本就是系統變化的一類,因此我們複寫onChange去抓取該狀態
再來的內容我在程式內的註解就都有寫了,再麻煩自個兒研究一下囉:D
最後完成這部分的模組後,我們就拿這程式來使用囉
MainActivity.java
public class MainActivity extends AppCompatActivity { final int REQUEST_CODE_ASK_PERMISSIONS = 123; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PinView pinView = findViewById(R.id.pin_view); ActivityCompat.requestPermissions(this, new String[]{"android.permission.READ_SMS"} , REQUEST_CODE_ASK_PERMISSIONS); SMSContent content = new SMSContent(new Handler(), this, new SMSContent.OnCallback() { @Override public void callback(String code) { pinView.setText(code); } }); this.getContentResolver().registerContentObserver(Uri.parse("content://sms/") , true, content); } }
首先在粉底白字的部分,這部分是取得讀取簡訊的權限;不過一開始我也說了這次的文章重點不在這裡,所以回調部分我就不寫了
再來是黃(橘)底白字的部分就是告訴系統密切注意簡訊的收發狀態(就是監聽他的意思
最後綠底白字就是使用我寫好的模組並應用之
寫到這邊,按下執行就能夠跑囉~
今天撰寫的功能雖然是讀取簡訊,不過其實這項功能的核心重點是ContentObserver
ContentObserver是系統監測,能監測的除了簡訊之外,飛行模式的開啟等等也都是可被監測的內容
這部分我就以後再寫吧!
好的,最後..
留言列表