我正在开发一个 Android 应用程序,其中服务器发送一个 OTP,用户需要在应用程序中输入这个 OTP,以注册我的应用程序。我想要的是,我的应用程序应该能够自动读取服务器发送的 OTP。我怎样才能做到这一点?在这方面的任何帮助或指导将不胜感激。
9 回答
我建议您不要使用任何第三方库从 SMS Inbox 自动获取 OTP。如果您对广播接收器及其工作原理有基本的了解,这可以很容易地完成。只需尝试以下方法:
- 创建单个接口,即 SmsListner
package com.wnrcorp.reba;
public interface SmsListener {
public void messageReceived(String messageText);
}
- 创建单个广播接收器,即 SmsReceiver
package com.wnrcorp.reba;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
public class SmsReceiver extends BroadcastReceiver {
private static SmsListener mListener;
Boolean b;
String abcd, xyz;
@Override
public void onReceive(Context context, Intent intent) {
Bundle data = intent.getExtras();
Object[] pdus = (Object[]) data.get("pdus");
for (int i = 0; i < pdus.length; i++) {
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i]);
String sender = smsMessage.getDisplayOriginatingAddress();
// b=sender.endsWith("WNRCRP"); //Just to fetch otp sent from WNRCRP
String messageBody = smsMessage.getMessageBody();
abcd = messageBody.replaceAll("[^0-9]", ""); // here abcd contains otp
which is in number format
//Pass on the text to our listener.
if (b == true) {
mListener.messageReceived(abcd); // attach value to interface
object
} else {}
}
}
public static void bindListener(SmsListener listener) {
mListener = listener;
}
}
- 在android清单文件中添加监听器即广播接收器
<receiver android:name=".SmsReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
并添加权限
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
- 当收件箱中收到 otp 时,您将在其中自动获取 otp 的活动。就我而言,我正在获取 otp 并在 edittext 字段上进行设置。
public class OtpVerificationActivity extends AppCompatActivity {
EditText ed;
TextView tv;
String otp_generated, contactNo, id1;
GlobalData gd = new GlobalData();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_otp_verification);
ed = (EditText) findViewById(R.id.otp);
tv = (TextView) findViewById(R.id.verify_otp);
/*This is important because this will be called every time you receive
any sms */
SmsReceiver.bindListener(new SmsListener() {
@Override
public void messageReceived(String messageText) {
ed.setText(messageText);
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
InputMethodManager imm =
(InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
} catch (Exception e) {}
if (ed.getText().toString().equals(otp_generated)) {
Toast.makeText(OtpVerificationActivity.this, "OTP Verified
Successfully!", Toast.LENGTH_SHORT).show();
}
});
}
}
}
OtpVerificationActivity 的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_otp_verification"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.wnrcorp.reba.OtpVerificationActivity">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/firstcard"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
card_view:cardCornerRadius="10dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OTP Confirmation"
android:textSize="18sp"
android:textStyle="bold"
android:id="@+id/dialogTitle"
android:layout_margin="5dp"
android:layout_gravity="center"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/otp"
android:layout_margin="5dp"
android:hint="OTP Here"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Verify"
android:textSize="18sp"
android:id="@+id/verify_otp"
android:gravity="center"
android:padding="10dp"
android:layout_gravity="center"
android:visibility="visible"
android:layout_margin="5dp"
android:background="@color/colorPrimary"
android:textColor="#ffffff"
/>
</LinearLayout>
</android.support.v7.widget.CardView>
</RelativeLayout>
您可以尝试使用一个简单的库,例如
通过 gradle 安装并添加权限后,在 onCreate 活动等方法中启动 SmsVerifyCatcher:
smsVerifyCatcher = new SmsVerifyCatcher(this, new OnSmsCatchListener<String>() {
@Override
public void onSmsCatch(String message) {
String code = parseCode(message);//Parse verification code
etCode.setText(code);//set code in edit text
//then you can send verification code to server
}
});
此外,覆盖活动生命周期方法:
@Override
protected void onStart() {
super.onStart();
smsVerifyCatcher.onStart();
}
@Override
protected void onStop() {
super.onStop();
smsVerifyCatcher.onStop();
}
/**
* need for Android 6 real time permissions
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
smsVerifyCatcher.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
public String parseCode(String message) {
Pattern p = Pattern.compile("\\b\\d{4}\\b");
Matcher m = p.matcher(message);
String code = "";
while (m.find()) {
code = m.group(0);
}
return code;
}
由于谷歌限制使用 READ_SMS 权限,这里是没有 READ_SMS 权限的解决方案。
短信检索器 API
基本功能是避免使用Android关键权限READ_SMS,使用此方法完成任务。Blow 是您需要的步骤。
将发送 OTP 发送到用户的号码,检查 SMS Retriever API 是否能够获取消息
SmsRetrieverClient client = SmsRetriever.getClient(SignupSetResetPasswordActivity.this);
Task<Void> task = client.startSmsRetriever();
task.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Android will provide message once receive. Start your broadcast receiver.
IntentFilter filter = new IntentFilter();
filter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION);
registerReceiver(new SmsReceiver(), filter);
}
});
task.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Failed to start retriever, inspect Exception for more details
}
});
广播接收器代码
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.Status;
public class SmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
Bundle extras = intent.getExtras();
Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
switch (status.getStatusCode()) {
case CommonStatusCodes.SUCCESS:
// Get SMS message contents
String otp;
String msgs = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
// Extract one-time code from the message and complete verification
break;
case CommonStatusCodes.TIMEOUT:
// Waiting for SMS timed out (5 minutes)
// Handle the error ...
break;
}
}
}
}
最后一步。将此接收器注册到您的清单中
<receiver android:name=".service.SmsReceiver" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
您的短信必须如下。
<#> Your OTP code is: 6789
QWsa8754qw2
这里 QWsa8754qw2 是你自己的应用程序的 11 字符哈希码。按照这个链接
- 不超过 140 字节
- 以前缀 <#> 开头
- 以标识您的应用的 11 个字符的哈希字符串结尾
要导入com.google.android.gms.auth.api.phone.SmsRetriever
,不要忘记将此行添加到您的应用程序 build.gradle:
implementation "com.google.android.gms:play-services-auth-api-phone:16.0.0"
我实现了这样的东西。但是,当消息进来时,这是我所做的,我只检索六位数的代码,将其捆绑在一个意图中,并将其发送到需要它的活动或片段并验证代码。该示例向您展示了获取短信的方式。查看下面的代码以了解如何使用LocalBrodcastManager进行发送,如果您的消息包含更多文本,例如问候语,请将其标准化以更好地帮助您。例如“您的验证码是:84HG73”,您可以创建一个像这样的正则表达式模式,([0-9]){2}([A-Z]){2}([0-9]){2}
这意味着两个整数、两个 [大写] 字母和两个整数。祝你好运!
从消息中丢弃所有不需要的信息后
Intent intent = new Intent("AddedItem");
intent.putExtra("items", code);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
以及接收它的片段/活动
@Override
public void onResume() {
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(receiver, new IntentFilter("AddedItem"));
super.onResume();
}
@Override
public void onPause() {
super.onDestroy();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(receiver);
}
以及用于处理您收集的有效负载的代码
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction()) {
final String message = intent.getStringExtra("message");
//Do whatever you want with the code here
}
}
};
是不是有点帮助。我通过使用回调做得更好
抱歉回复晚了,但如果有帮助,我仍然想发布我的答案。它适用于 6 位数的 OTP。
@Override
public void onOTPReceived(String messageBody)
{
Pattern pattern = Pattern.compile(SMSReceiver.OTP_REGEX);
Matcher matcher = pattern.matcher(messageBody);
String otp = HkpConstants.EMPTY;
while (matcher.find())
{
otp = matcher.group();
}
checkAndSetOTP(otp);
}
Adding constants here
public static final String OTP_REGEX = "[0-9]{1,6}";
对于 SMS 侦听器,可以遵循以下课程
public class SMSReceiver extends BroadcastReceiver
{
public static final String SMS_BUNDLE = "pdus";
public static final String OTP_REGEX = "[0-9]{1,6}";
private static final String FORMAT = "format";
private OnOTPSMSReceivedListener otpSMSListener;
public SMSReceiver(OnOTPSMSReceivedListener listener)
{
otpSMSListener = listener;
}
@Override
public void onReceive(Context context, Intent intent)
{
Bundle intentExtras = intent.getExtras();
if (intentExtras != null)
{
Object[] sms_bundle = (Object[]) intentExtras.get(SMS_BUNDLE);
String format = intent.getStringExtra(FORMAT);
if (sms_bundle != null)
{
otpSMSListener.onOTPSMSReceived(format, sms_bundle);
}
else {
// do nothing
}
}
}
@FunctionalInterface
public interface OnOTPSMSReceivedListener
{
void onOTPSMSReceived(@Nullable String format, Object... smsBundle);
}
}
@Override
public void onOTPSMSReceived(@Nullable String format, Object... smsBundle)
{
for (Object aSmsBundle : smsBundle)
{
SmsMessage smsMessage = getIncomingMessage(format, aSmsBundle);
String sender = smsMessage.getDisplayOriginatingAddress();
if (sender.toLowerCase().contains(ONEMG))
{
getIncomingMessage(smsMessage.getMessageBody());
} else
{
// do nothing
}
}
}
private SmsMessage getIncomingMessage(@Nullable String format, Object aObject)
{
SmsMessage currentSMS;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && format != null)
{
currentSMS = SmsMessage.createFromPdu((byte[]) aObject, format);
} else
{
currentSMS = SmsMessage.createFromPdu((byte[]) aObject);
}
return currentSMS;
}
使用 SMS Retriever API,无需声明即可读取 OTP android.permission.READ_SMS
。
- 启动短信检索器
private fun startSMSRetriever() {
// Get an instance of SmsRetrieverClient, used to start listening for a matching SMS message.
val client = SmsRetriever.getClient(this /* context */);
// Starts SmsRetriever, which waits for ONE matching SMS message until timeout
// (5 minutes). The matching SMS message will be sent via a Broadcast Intent with
// action SmsRetriever#SMS_RETRIEVED_ACTION.
val task: Task<Void> = client.startSmsRetriever();
// Listen for success/failure of the start Task. If in a background thread, this
// can be made blocking using Tasks.await(task, [timeout]);
task.addOnSuccessListener {
Log.d("SmsRetriever", "SmsRetriever Start Success")
}
task.addOnFailureListener {
Log.d("SmsRetriever", "SmsRetriever Start Failed")
}
}
- 通过广播接收消息
public class MySMSBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action && intent.extras!=null) {
val extras = intent.extras
val status = extras.get(SmsRetriever.EXTRA_STATUS) as Status
when (status.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get SMS message contents
val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String
Log.e("Message", message);
// Extract one-time code from the message and complete verification
// by sending the code back to your server.
}
CommonStatusCodes.TIMEOUT -> {
// Waiting for SMS timed out (5 minutes)
// Handle the error ...
}
}
}
}
}
/**Don't forgot to define BroadcastReceiver in AndroidManifest.xml.*/
<receiver android:name=".MySMSBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
- 将验证消息中的一次性代码发送到您的服务器
确保您的短信格式如下:
<#> Your ExampleApp code is: 123ABC78
fBzOyyp9h6L
- 不超过 140 字节
- 以前缀 <#> 开头
以标识您的应用的 11 个字符的哈希字符串结尾
您可以使用以下代码计算应用哈希:
import android.content.Context import android.content.ContextWrapper import android.content.pm.PackageManager import android.util.Base64 import android.util.Log import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.util.* /** * This is a helper class to generate your message hash to be included in your SMS message. * * Without the correct hash, your app won't recieve the message callback. This only needs to be * generated once per app and stored. Then you can remove this helper class from your code. * * For More Detail: https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string * */ public class AppSignatureHelper(private val context: Context) : ContextWrapper(context) { companion object { val TAG = AppSignatureHelper::class.java.simpleName; private const val HASH_TYPE = "SHA-256"; const val NUM_HASHED_BYTES = 9; const val NUM_BASE64_CHAR = 11; } /** * Get all the app signatures for the current package * @return */ public fun getAppSignatures(): ArrayList<String> { val appCodes = ArrayList<String>(); try { // Get all package signatures for the current package val signatures = packageManager.getPackageInfo( packageName, PackageManager.GET_SIGNATURES ).signatures; // For each signature create a compatible hash for (signature in signatures) { val hash = hash(packageName, signature.toCharsString()); if (hash != null) { appCodes.add(String.format("%s", hash)); } } } catch (e: PackageManager.NameNotFoundException) { Log.e(TAG, "Unable to find package to obtain hash.", e); } return appCodes; } private fun hash(packageName: String, signature: String): String? { val appInfo = "$packageName $signature"; try { val messageDigest = MessageDigest.getInstance(HASH_TYPE); messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8)); var hashSignature = messageDigest.digest(); // truncated into NUM_HASHED_BYTES hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES); // encode into Base64 var base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP); base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR); Log.e(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash)); return base64Hash; } catch (e: NoSuchAlgorithmException) { Log.e(TAG, "hash:NoSuchAlgorithm", e); } return null; } }
所需的梯度:
implementation "com.google.android.gms:play-services-auth-api-phone:16.0.0"
参考:
https ://developers.google.com/identity/sms-retriever/overview
https://developers.google.com/identity/sms-retriever/request
https://developers.google.com/identity/sms-检索器/验证
我知道已经晚了,但为了让其他人保持简单,这是我之前写的解决方案。使用这个图书馆链接。你不必搞砸任何事情。添加依赖后只需使用此方法。
OtpFetcher.getInstance().verifyOtpByMatchingString(this, "OTP", 21000, object : OtpListener {
override fun onReceived(messageItem: MessageItem) {
Toast.makeText(this@MainActivity, "" + messageItem, Toast.LENGTH_SHORT).show()
}
override fun onTimeOut() {
Toast.makeText(this@MainActivity, "TimeOut", Toast.LENGTH_SHORT).show()
}
})
例如,您必须传递消息的上下文、搜索字符串
您在消息中期待 OTP 传递“OTP”并超时您想听 OTP 多长时间,就是这样。您将在 OnRecieved CallBack 中以简单格式收到您的消息。
是的,这现在也可以在浏览器中实现。Chrome 在 84 及更高版本中发布此功能。借助 WEBOTP API,我们可以在移动设备上检测网络上的 OTP。
这是与 Angular PWA 应用程序的 Web-OTP 集成代码:https ://github.com/Rohit3230/webOtpAutoReadByAngular
寻找 Angular PWA 应用程序的实时工作 URL。https://rohit3230.github.io/webOtpAutoReadByAngular/
**activity_main.xml**
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="com.example.mukundwn.broadcastreceiver.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
**MainActivity.java**
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private BroadcastReceiver broadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
broadcastReceiver =new MyBroadcastReceiver();
}
@Override
protected void onStart()
{
super.onStart();
IntentFilter intentFilter=new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(broadcastReceiver,intentFilter);
}
@Override
protected void onStop()
{
super.onStop();
unregisterReceiver(broadcastReceiver);
}
}
**MyBroadcastReceiver.java**
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
/**
* Created by mukundwn on 12/02/18.
*/
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"hello received an sms",Toast.LENGTH_SHORT).show();
}
}
**Manifest.xml**
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mukundwn.broadcastreceiver">
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<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/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVE"></action>
</intent-filter>
</receiver>
</application>
</manifest>