在Android中使用SMS Retriever API进行自动短信验证
自动短信验证可以在一个叫做SMS Retriever API 的API的帮助下完成。使用这个API,用户不需要手动输入验证码,API也不需要任何额外的应用权限。
在本教程中,我们将学习如何在Android应用中实现这一功能。
前提条件
要继续学习本教程,读者应该。
- 掌握了
Kotlin编程语言。 - 知道如何在Android studio中使用
XML来设计布局。 - 对
Android Broadcasts有一定的了解。
目标
在本教程结束时,读者应该明白。
- 什么是短信验证过程。
- 如何在你的应用程序中使用自动短信验证功能。
什么是SMS Retriever API?
SMS Retriever是一个API,允许你验证用户的短信而不强迫他们输入验证码。通过这个API,你可以为你的应用程序提取验证码。这是在不要求完整的短信阅读权限的情况下完成的。
当用户设备收到一条信息时,谷歌游戏服务会检查应用程序的哈希值。然后,它通过SMS Retriever API将消息文本发送给你的应用程序。然后该应用程序读取并提取短信中的代码。这个代码通常被送回服务器进行验证。
短信验证过程
对于手机号码验证,你需要首先实现客户端。之后,在服务器端,完成验证程序。通常,你把用户的电话号码发送到执行验证的服务器。然后服务器向所提供的电话号码发送一个OTP(一次性密码)代码。
SMS Retriever API监听含有OTP代码的短信。收到代码后,它将其发送回服务器以完成验证过程。
为什么使用自动SMS Retriever API?
- 谷歌废除了所有使用
CALL_LOG和READ_SMS权限的应用程序。这是因为它们侵犯了用户的隐私。这导致在2021年1月19日将使用这些权限的应用程序从play store移除。 - 它提供了一个更顺畅和毫不费力的用户体验。
第1步:创建一个新的Android studio项目

第2步:添加必要的依赖项
我们将使用以下内容。
- Apache Commons - 这个库将帮助我们从SMS信息中提取代码。
- Google Play Services API - 这个库持有短信检索类。
- EventBus - 为了监听来自短信检索API的接收短信,我们将使用BroadcastReceiver。EventBus是一个发布者/订阅者模式库。我们用它来在我们的BroadcastReceiver和Activity类之间进行通信。
将这些添加到build.gradle文件中并同步项目。
implementation 'org.apache.commons:commons-lang3:3.11'
implementation 'com.google.android.gms:play-services-auth:19.2.0'
implementation 'org.greenrobot:eventbus:3.2.0'
第3步:为我们的项目设置XML布局
我们将在这部分创建一个编辑文本。这个编辑文本将显示从我们的SMS消息中获得的一次性代码。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:inputType="number"
android:layout_marginTop="80dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"/>
</LinearLayout>
发送手机号码到服务器
在这一步,你必须从EditText 。把它发送到你的验证服务器,它应该返回一次性代码。因为我还没有一个验证服务器,所以我们在这篇文章中不打算使用这个方法。我们将从另一部手机发送短信。该短信将包含一个四位数的代码。
这个代码将被提取并显示在我们在activity_main.xml 中添加的EditText上。
第4步:获取SmsRetriverClient的实例
我们首先要获得一个SmsRetrieverClient的实例。接着调用initSmsRetriever实例函数,并将onSuccessListener 和onFailureListener 加入到任务中。我们把所有这些都包在一个函数中。
private fun initSmsListener() {
smsClient.startSmsRetriever()
.addOnSuccessListener {
//You can perform your tasks here
}.addOnFailureListener { failure ->
failure.printStackTrace()
Toast.makeText(this, failure.message, Toast.LENGTH_SHORT).show()
}
}
上述函数在onCreate() 方法中被调用。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var smsClient: SmsRetrieverClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
smsClient = SmsRetriever.getClient(this)
initSmsListener()
}
我们的API将向应用程序传输一个SmsRetriever.SMS RETRIEVED ACTION 意向。这发生在设备收到包含代码的消息时。这个意图持有短信信息和后台处理状态。
为了处理这个问题,我们将创建一个BroadcastReceiver类。
class MessageBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action) {
val data = intent.extras
if (data != null) {
val status = data[SmsRetriever.EXTRA_STATUS] as Status
var timedOut = false
var otpCode: String? = null
when (status.statusCode) {
CommonStatusCodes.SUCCESS -> {
val appMessage = data[SmsRetriever.EXTRA_SMS_MESSAGE] as String
otpCode = appMessage
}
CommonStatusCodes.TIMEOUT -> {
timedOut = true
}
}
EventBus.getDefault().post(RetrievalEvent(timedOut, otpCode.toString()))
}
}
}
}
在onReceive() 方法上,首先我们检查SMS Retriever 背景处理的状态。我们还构建一个RetrievalEvent 类的实例。这是一个事件类,EventBus 将发送至我们的Subscriber 。RetrievalEvent 这个类将是一个数据类。
RetrievalEvent
data class RetrievalEvent (
val timedOut: Boolean,
val message: String
)
这个数据类的属性被设置为检索到的SMS消息。这是在后台处理成功的情况下进行的。如果在5分钟内没有收到消息,通常会发生超时。如果它发生,超时被设置为真。然后,将事件发送给监听用户。
第5步:在Android Manifest上注册BroadcastReceiver
在你的应用程序的AndroidManifest.xml 文件中,注册BroadcastReceiver 。
<?xml version="1.0" encoding="utf-8"?>
<manifest
... >
<application
... >
<receiver
android:name=".MessageBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
</application>
</manifest>
接下来,在我们的MainActivity class ,我们将注册、取消注册,并实现我们的订阅者。当一个事件被发布时,onReceiveSms() 方法将被调用。它通常被注解为@Subscribe 注解。
注册和取消注册的接收者通常分别在onStart() 和onStop() 方法上完成。substringAfterLast() 函数被用来提取通过SMS发送的代码。
注意:一定要记得注册和取消注册成员以避免内存泄漏。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var smsClient: SmsRetrieverClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
smsClient = SmsRetriever.getClient(this)
//uncomment this to generate your app hash string. You can view the hash string on your log cat when you run the app
/* val appSignatureHelper = SignatureHelper(this)
Log.d("SIGNATURE",appSignatureHelper.appSignature.toString())*/
initSmsListener()
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
private fun initSmsListener() {
smsClient.startSmsRetriever()
.addOnSuccessListener {
Toast.makeText(
this, "Waiting for sms message",
Toast.LENGTH_SHORT
).show()
}.addOnFailureListener { failure ->
Toast.makeText(
this, failure.localizedMessage,
Toast.LENGTH_SHORT
).show()
}
}
@Subscribe
fun onReceiveSms(retrievalEvent: RetrievalEvent) {
val code: String =
StringUtils.substringAfterLast(retrievalEvent.message, "is").replace(":", "")
.trim().substring(0, 4)
runOnUiThread {
if (!retrievalEvent.timedOut) {
binding.editText.setText(code)
} else {
Toast.makeText(this, "Failed", Toast.LENGTH_SHORT).show()
}
}
initSmsListener()
}
}
计算你的应用程序的哈希字符串
为了生成哈希字符串,你可以使用以下方法。
- 使用Play App Signing。
- 使用
SignatureHelper class。这个类将有助于生成我们应用程序的哈希字符串。在使用这个类获得哈希字符串后,一定要将其删除。
/**
* 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 receive the message callback. This only needs to be
* generated once per app and stored. Then you can remove this helper class from your code.
*/
class SignatureHelper(context: Context?) :
ContextWrapper(context) {
// For each signature create a compatible hash
/**
* Get all the app signatures for the current package
*/
val appSignature: ArrayList<String>
get() {
val appCodes = ArrayList<String>()
try {
// Get all package signatures for the current package
val myPackageName = packageName
val myPackageManager = packageManager
val signatures = myPackageManager.getPackageInfo(myPackageName,PackageManager.GET_SIGNATURES).signatures
// For each signature create a compatible hash
for (signature in signatures) {
val hash = hash(myPackageName, signature.toCharsString())
if (hash != null) {
appCodes.add(String.format("%s", hash))
}
}
} catch (e: PackageManager.NameNotFoundException) {
Log.d(TAG,"Package not found",e)
}
return appCodes
}
companion object {
private const val HASH_TYPE = "SHA-256"
const val HASHED_BYTES = 9
const val BASE64_CHAR = 11
private fun hash(pkgName: String, signature: String): String? {
val appInfo = "$pkgName $signature"
try {
val messageDigest = MessageDigest.getInstance(HASH_TYPE)
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
var myHashSignature = messageDigest.digest()
// truncated into HASHED_BYTES
myHashSignature = Arrays.copyOfRange(myHashSignature,0,HASHED_BYTES)
// encode into Base64
var base64Hash = Base64.encodeToString(myHashSignature,Base64.NO_PADDING or Base64.NO_WRAP)
base64Hash = base64Hash.substring(0, BASE64_CHAR)
Log.d(TAG, String.format("pkg: %s -- hash: %s", pkgName, base64Hash))
return base64Hash
} catch (error: NoSuchAlgorithmException) {
Log.e(TAG, "Algorithm not Found", error)
}
return null
}
}
}
最后,请记住,你应该在你的消息上使用如下格式。
- 信息应该小于140字节。
- 该消息应该有OTP代码。
- 你的消息应该以你的应用程序的11个字符的哈希字符串结束。
以下是一个例子
Your Sms Retriever Api code is: 6647
u0tUcRo4UQ7
演示屏幕
运行该应用程序时,可以看到以下内容。

结论
Automatic Retriever API是一个有助于检测和提取OTP代码的库。这个代码通常被送回服务器进行验证。这个API执行任务时不需要用户为应用程序提供权限。这使得用户的入职体验变得顺畅和吸引人。