Android獲取短信驗證碼並寫入文件
自動化測試中碰到一個問題就是如何在電腦端獲取手機的驗證碼,初步的方案是:手機上安裝一個apk,獲取短信驗證碼的內容,然后寫入手機的文件中,電腦端讀取手機文件,從而獲取到實際的驗證碼。
由於是第一次使用Android studio,也沒接觸過java,所以在此記錄這個簡單的程序完成過程和在此期間遇到的坑以及如何解決的。
1、環境搭建
- 下載Android studio
https://developer.android.google.cn/studio/
本次使用的版本是2020.3.1 for Windows 64-bit (912 MiB);
- 創建project
新建一個Empty Activity;

填寫基本信息,名稱,存儲路徑,編程語言,最小SKD版本等;

生成的項目目錄結構如圖

- 安裝sdk
菜單欄--Tools--SDK Manager

勾選右下角的Show Package Details,安裝android 8.0及以上版本,目前到Android12,安裝過程比較久,等待安裝完成。
2、JAVA程序編寫
添加靜態權限
添加動態權限
添加廣播接收器
短信處理與寫入文件
- 添加靜態權限
編輯清單文件(AndroidMainfest.xml,此文件在mainfests文件夾下),添加短信接收讀取權限,添加sd卡操作文件權限,添加位置如圖所示:

完整代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.msgcheck">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MsgCheck">
<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>
</activity>
<receiver
android:name=".SmsReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
- 添加動態權限
Google在 Android 6.0 開始引入了權限申請機制,將所有權限分成了正常權限和危險權限。應用的相關功能每次在使用危險權限時需要動態的申請並得到用戶的授權才能使用。
讀取和寫入短信屬於危險權限,必須添加動態權限才能使用;
在MainActivity的onCreate中,增加如下代碼:

完整代碼如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
boolean isReadSMS = ActivityCompat.checkSelfPermission(
MainActivity.this,
android.Manifest.permission.READ_SMS)!= PackageManager.PERMISSION_GRANTED;
boolean isRECEIVE_SMS = ActivityCompat.checkSelfPermission(
MainActivity.this,android.Manifest.permission.RECEIVE_SMS)
!=PackageManager.PERMISSION_GRANTED;
boolean isWRITE_EXTERNAL_STORAGE = ActivityCompat.checkSelfPermission(
MainActivity.this,android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
!=PackageManager.PERMISSION_GRANTED;
boolean isMOUNT_UNMOUNT_FILESYSTEMS = ActivityCompat.checkSelfPermission(
MainActivity.this,android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
!=PackageManager.PERMISSION_GRANTED;
if (isReadSMS||isRECEIVE_SMS || isWRITE_EXTERNAL_STORAGE || isMOUNT_UNMOUNT_FILESYSTEMS){
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{
android.Manifest.permission.READ_SMS,
android.Manifest.permission.RECEIVE_SMS,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS
}, 1);
}//動態申請權限
}
}
- 添加廣播接收器
新建一個文件SmsReceiver,與MainActivity同一目錄;

編輯清單文件(AndroidMainfest.xml,此文件在mainfests文件夾下),增加receiver

name為剛剛新建的廣播接收器文件,當有新短信時,會執行SmsReceiver中onReceive的代碼;
- 短信處理與寫入文件
SmsReceiver中包含了短信接收后的內容處理和寫入到指定文件的功能,完整代碼如下:
package com.example.msgcheck;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SmsReceiver extends BroadcastReceiver {
//設置靜態常量TAG,android中有5種級別的log:Log.v(),Log.d(),Log.i(),Log.w(),Log.e(),i(info)輸出提示信息;
protected static final String TAG = "log";
@Override
public void onReceive(Context context, Intent intent) {
//Toast.makeText(context, "收到信息", Toast.LENGTH_LONG).show();
String str = "驗證碼";
if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")){
//intent.getExtras()方法就是從過濾后的意圖中獲取攜帶的數據,
// 這里攜帶的是以“pdus”為key、短信內容為value的鍵值對
// android設備接收到的SMS是pdu形式的
Bundle bundle = intent.getExtras();
SmsMessage msg = null;
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.i(TAG,msg.getDisplayMessageBody());
//獲取短信發送方地址
String from = msg.getOriginatingAddress();
Log.i(TAG,from);
if (content.indexOf(str)!=-1){
Log.i(TAG,"包含驗證碼");
String codetext = getCode(msg.getDisplayMessageBody());
Toast.makeText(context, codetext, Toast.LENGTH_LONG).show();
Log.i(TAG,codetext);
//調用written方法將6位數字驗證碼寫入code.txt文件
this.written("code.txt",codetext);
}else{
Log.i(TAG,"無需獲取驗證碼");
}
}
}
}
}
//匹配驗證碼
public static String getCode(String body){
//將正則表達式賦予生成的Pattern類
Pattern p = Pattern.compile("[0-9]{6,6}(?![0-9])");
//將要匹配的內容賦予生成的Matcher類
Matcher m = p.matcher(body);
//嘗試在目標字符串里查找下一個匹配字符串
if(m.find()){
System.out.println(m.group());
//返回當前查找而獲得的與組匹配的所有字符串內容
return m.group(0);
}
return null;
}
//將內容寫入文件
public void written(String f,String content){
try{
// Environment.getExternalStorageDirectory(),獲取sd卡的內存位置;
// 當沒有sd卡時,獲取的是手機的內存,即手機插上電腦后,顯示的磁盤根目錄;
File file = new File(Environment.getExternalStorageDirectory(),f);
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
//將數據寫入緩沖區,並沒有寫入目的文件
bw.write(content);
//刷新緩沖流,也就是會把數據寫入到目的文件里
bw.flush();
Log.i(TAG,"寫入成功");
}catch (Exception e){
e.printStackTrace();
}
}
}
3、實機測試
手機需要開啟開發者模式,當紅框中顯示手機型號時,說明可以進行調試,點擊綠色的運行按鈕,會將debug的apk自動下載到手機中並運行;

去任何一個網站進行賬號注冊,輸入手機號,點擊發送驗證碼,手機在收到短信通知時,在app中會顯示浮窗toast,說明已經通過程序獲取到短信內容;
用usb連接電腦,在手機的存儲路徑根目錄下能找到code.txt文件,且里面的內容就是剛剛獲取到的驗證碼,測試成功。
4、apk打包
1)打開build.gradle文件(module)
2)修定軟件版本
versionCode是app的大版本號,為數值類型,默認為1我這里改為2。
versionName是app的具體版本號,為際符串類型,默認為1.0我這里改為2.3。
minSdk 26:最小sdk版本;
targetSdk 29:目標sdk版本;
3)指定生成的apk文件名
在android內部defaultConfig同層下添加以不內容(outputFileName改成自己想要的apk名)
android.applicationVariants.all {
variant -> variant.outputs.all {
// 此處指定生成的apk文件名
outputFileName = "SecTest.apk"
}
}
4)打包
直接點“Build APK(s)”生成的是使用默認的debug.keystore簽名的Debug版apk(生成在app\build\outputs\apk\debug目錄下),真正發布軟件時我們需要生成自己密鑰簽名的release版apk。
點擊菜單欄----Build----Generate Signed APK,創建一個新的key,密碼自行設置;
簽名文件為:名字.jks
最后點擊菜單欄--build--build apks,即可在自定義的release目錄中找到apk文件。
5、代碼上傳到github或者gitee
菜單欄--VCS--create git repository,一般將項目當前目錄作為當前的git本地倉庫;
這時菜單欄的vcs會變為git,點擊git--Manage Remotes,添加遠程倉庫地址;
然后依次執行commit、pull、push等Git方面操作。
