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方面操作。