Android出海实战:使用Google SMSRetrieverAPI 实现短信自动回填

867 阅读7分钟

写在伊始

众所周知,登录注册界面实现短信自动回填是优化用户体验的重要手段,如果我们有RECEIVE_SMS权限,可以通过自定义BroadcastReceiver监听Action(com.google.android.gms.auth.api.phone.SMS_RETRIEVED)来实现短信自动回填功能。但是,RECEIVE_SMS是敏感权限,在很多国家,尤其你还是金融应用,是禁止获取这个权限的。那么,我们又如何实现这个短信自动回填功能呢?

今天的主角:Google SMSRetrieverAPI 解决了我们在无RECEIVE_SMS权限下实现短信自动回填功能的难题。怎么样,Google大人还是异常贴心的。但此种方式有一个美中不足之处,老铁们先看看是否可以接受!!!

美中不足

为了标识唯一应用。Google SMSRetrieverAPI 要求短信内容格式固定,必须在短信内容后添加用于标识应用的 11 个字符的哈希字符串,示例如下:

短信固定格式(#号可以不要,11位的sha256一定需要)
<#> Your ExampleApp code is: 123456
FA+9qCX9VVd

怎么样,短信尾部带个FA+9qCX9VVd(用户怎么看都不懂的玩意)。你的产品经理同意不哈哈。我成功说服了我们产品经理,此功能已上线。

当然,Google对短信内容和项目本身还有一些其他要求:

  • 短信内容不能超过140个字节
  • minSdkVersion 为 19 或更高
  • compileSdkVersion 28 或更高版本

回归正题,开始实现此功能。

计算应用的哈希字符串

哈希字符串分为两种情况:

  • 如果您在创建应用时选择的使用Google签名应用,那么我们需要使用deployment_cert.der文件来生成哈希字符串
  • 如果您使用自己创建的签名keystore,那么我们直接使用您自己的签名文件来生成哈希字符串即可

我们分别来看一下这两种方式都如何生成哈希字符串

Google签名(deployment_cert.der)

生成步骤较为复杂,官网如下:计算应用的哈希字符串

给大家写了一个脚本,大家可以直接使用脚本生成。github地址:google.ssh。我们直接使用如下命令生成:

./google.ssh deployment_cert.der 您的包名

分别输入您项目中签名的秘钥,如遇到Trust this certificate? [no]:提问,直接输入yes回车。

您自己的keystore签名

我们直接使用工具类AppSignatureHashHelper生成即可,代码如下:

public class AppSignatureHashHelper extends ContextWrapper {
    public static final String TAG = AppSignatureHashHelper.class.getSimpleName();

    private static final String HASH_TYPE = "SHA-256";
    public static final int NUM_HASHED_BYTES = 9;
    public static final int NUM_BASE64_CHAR = 11;

    public AppSignatureHashHelper(Context context) {
        super(context);
    }

    public ArrayList<String> getAppSignatures() {
        ArrayList<String> appSignaturesHashs = new ArrayList<>();

        try {
            String packageName = getPackageName();
            PackageManager packageManager = getPackageManager();
            Signature[] signatures = packageManager.getPackageInfo(packageName,
                    PackageManager.GET_SIGNATURES).signatures;

            for (Signature signature : signatures) {
                String hash = hash(packageName, signature.toCharsString());
                if (hash != null) {
                    appSignaturesHashs.add(String.format("%s", hash));
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Package not found", e);
        }
        return appSignaturesHashs;
    }

    @TargetApi(19)
    private static String hash(String packageName, String signature) {
        String appInfo = packageName + " " + signature;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE);
            messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8));
            byte[] hashSignature = messageDigest.digest();

            hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES);
            String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP);
            base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR);

            return base64Hash;
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "No Such Algorithm Exception", e);
        }
        return null;
    }
}

在应用中调用直接调用AppSignatureHashHelper类的getAppSignatures方法生成即可。

不管哪种方式获取到这个哈希值以后,将其提供后端,按照如上格式将其添加到短信内容中。

添加play-services-auth依赖库

dependencies {
  implementation 'com.google.android.gms:play-services-auth:19.2.0'
  implementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1'
}

创建监听短信的BroadcastReceiver

class SMSReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
            val extras = intent.extras
            val status = extras?.get(SmsRetriever.EXTRA_STATUS) as? Status?
            when (status?.statusCode) {
                CommonStatusCodes.SUCCESS -> {
                 	/*<#> Your ExampleApp code is: 123456 FA+9qCX9VVd*/
                    val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String?
                    //然后我们根据我们短信下发的具体格式写正则来获取到我们的实际字符串
                    //比如我们就是纯数字 6位
                    val pattern = Pattern.compile("[0-9]{6}")
                		val matcher = pattern.matcher(message)
                		if (matcher.find()) { 
                			// match the 6-digit verification code
                    	val smsContent = matcher.group()
                		}
                }

                CommonStatusCodes.TIMEOUT -> {

                }

                CommonStatusCodes.API_NOT_CONNECTED -> {
                }

                CommonStatusCodes.NETWORK_ERROR -> {
                }

                CommonStatusCodes.ERROR -> {
                }
            }
        }
    }
}

在清单文件中注册SMSReceiver

<receiver
      android:name=".receiver.SMSReceiver"
      android:exported="true"
      android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
         <intent-filter>
             <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
         </intent-filter>
</receiver>

在您使用的界面开启监听服务

private fun startSMSListener() {
    val client = SmsRetriever.getClient(this)
    client.startSmsRetriever()
}

运行项目,给您的Demo发送短信,效果如图

success.jpg

github地址:GitHub - loveAndroidAndroid/SMSRetrieverAPIDemo

写在结尾

当用户手机接收到短信以后,系统会自动根据由签名文件和包名生成的11位哈希值来通知您的应用,从而使我们可以获取到短信内容。但是,com.google.android.gms.auth.api.phone.permission.SEND权限设置在Google Play服务 19.8.31或更高版本中可用。也就是说如果用户手机低于Google Play服务19.8.31,则此功能是不能使用的,不过这也可兼容大部分手机了。

App出海交流群,寻找志同道合的你

经常分享Google Play,App Store市场政策解决方案,大家共同深入了解应用市场App上架、下架背后的原因,如政策违规、安全漏洞、版权问题、市场策略调整等,确保海外App合规、安全并提升我们的用户体验。也不限于分享其他App出海快讯,一起见证行业的蓬勃发展。

欢迎您来到App出海交流群讨论出海的任何问题!想进群的可以点击这里,或者关注公众号趣浪出海,我拉您进来。

这是一个高质量的属于App出海浪潮儿的技术交流中心!

在这里,都是一群热衷于在海外发展的人群:

1、你的各种谷歌&苹果应用上架问题都能得到解决。

2、你可以了解到各种最新的谷歌&苹果政策更新。

3、你可以获得每日的应用上架数据,了解谷歌&苹果审核严松状态。

4、你可以获得各种资源信息,助力企业出海。

推荐阅读(更多问题咨询请点击这里 或者关注vx 趣浪出海)

Google Play上架审核时间过长?要不要催审!

这都能封!开发者行为导致Google账号关联?

另类封号!别让你的Google老账号为你的粗心买单

GooglePlay账号关联审查机制详解