
axios拦截器
上周,我谈到了使用SMS激活您的应用程序,这是验证用户帐户的一种非常有效的方法。 不过,我忽略了两件事。 其中之一是能够自动捕获传入的SMS。 这仅在Android上可行,但对用户来说很酷,因为它省去了键入激活文本的麻烦。
广播接收器
为了获取传入的SMS,我们需要一个广播接收器,它是一个独立的Android类,可以接收特定的事件类型。 这常常使有时会从广播接收器派生impl类的开发人员感到困惑…这是一个错误…
诀窍是您可以将任何本机Android类放到native/android
目录中。 它将与其余的本机代码一起编译,并且“正常工作”。 所以我把这个类放在native/android/com/codename1/sms/intercept
:
package com.codename1.sms.intercept;
import android.content.*;
import android.os.Bundle;
import android.telephony.*;
import com.codename1.io.Log;
public class SMSListener extends BroadcastReceiver {
@Override
public void onReceive(Context cntxt, Intent intent) {
// based on code from https://stackoverflow.com/questions/39526138/broadcast-receiver-for-receive-sms-is-not-working-when-declared-in-manifeststat
if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
Bundle bundle = intent.getExtras();
SmsMessage[] msgs = null;
if (bundle != null){
try{
Object[] pdus = (Object[]) bundle.get("pdus");
msgs = new SmsMessage[pdus.length];
for(int i=0; i<msgs.length; i++){
msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
String msgBody = msgs[i].getMessageBody();
SMSCallback.smsReceived(msgBody);
}
} catch(Exception e) {
Log.e(e);
SMSCallback.smsReceiveError(e);
}
}
}
}
}
上面的代码是非常标准的原生Android代码,它只是一个回调,其中的大多数逻辑类似于此stackoverflow问题中提到的原生Android代码。
但是,我们还有更多要做的事情。 为了以本地方式实现此功能,我们需要按照该问题中的说明,在manifest.xml
文件中注册权限和接收者。 这是他们的本机清单的样子:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bulsy.smstalk1">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
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="com.bulsy.smstalk1.SmsListener"
android:enabled="true"
android:permission="android.permission.BROADCAST_SMS"
android:exported="true">
<intent-filter android:priority="2147483647">//this doesnt work
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
我们只需要广播许可XML和许可XML。 两者都可以通过构建提示来实现。 前者很容易:
android.xpermissions=<uses-permission android:name="android.permission.RECEIVE_SMS" />
后者并不难,请注意,为了方便起见,我将多行代码并成一行:
android.xapplication=<receiver android:name="com.codename1.sms.intercept.SMSListener" android:enabled="true" android:permission="android.permission.BROADCAST_SMS" android:exported="true"> <intent-filter android:priority="2147483647"><category android:name="android.intent.category.DEFAULT" /> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver>
在这里,它的格式很好:
<receiver android:name="com.codename1.sms.intercept.SMSListener"
android:enabled="true"
android:permission="android.permission.BROADCAST_SMS"
android:exported="true">
<intent-filter android:priority="2147483647">
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
听力与权限
您会注意到,这些不包括您期望的类似这样的实际绑定或权限提示。 为此,我们需要一个本机接口。
堆栈溢出中的本机样本在活动中绑定了侦听器,但在这里我们希望应用程序代码决定何时应绑定侦听器:
public interface NativeSMSInterceptor extends NativeInterface {
public void bindSMSListener();
public void unbindSMSListener();
}
这很容易!
注意,对于所有其他操作系统, isSupported()
返回false,因此我们不需要询问这是否是“ Android”,我们可以使用isSupported()
。
实现也很容易:
package com.codename1.sms.intercept;
import android.Manifest;
import android.content.IntentFilter;
import com.codename1.impl.android.AndroidNativeUtil;
public class NativeSMSInterceptorImpl {
private SMSListener smsListener;
public void bindSMSListener() {
if(AndroidNativeUtil.checkForPermission(Manifest.permission.RECEIVE_SMS, "We can automatically enter the SMS code for you")) { (1)
smsListener = new SMSListener();
IntentFilter filter = new IntentFilter();
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
AndroidNativeUtil.getActivity().registerReceiver(smsListener, filter); (2)
}
}
public void unbindSMSListener() {
AndroidNativeUtil.getActivity().unregisterReceiver(smsListener);
}
public boolean isSupported() {
return true;
}
}
1个 | 这将触发Android 6及更高版本上的权限提示。 即使以XML声明了权限,对于6岁以上的用户来说还是不够的。 请注意,即使您在Android 6上运行,您仍需要声明XML权限! |
2 | 在这里,我们实际上绑定了侦听器,这使我们可以获取一条短信,而不会在收到的每条短信中进行监听 |
回呼
到现在为止,该代码还不太实用,因此请对其进行抽象。 但是首先,我们需要实现上面的代码向其中发送SMS和错误的回调类:
package com.codename1.sms.intercept; (1)
import com.codename1.util.FailureCallback;
import com.codename1.util.SuccessCallback;
import static com.codename1.ui.CN.*;
/**
* This is an internal class, it's package protect to hide that
*/
class SMSCallback {
static SuccessCallback<String> onSuccess;
static FailureCallback onFail;
public static void smsReceived(String sms) {
if(onSuccess != null) {
SuccessCallback<String> s = onSuccess;
onSuccess = null;
onFail = null;
SMSInterceptor.unbindListener();
callSerially(() -> s.onSucess(sms)); (2)
}
}
public static void smsReceiveError(Exception err) {
if(onFail != null) {
FailureCallback f = onFail;
onFail = null;
SMSInterceptor.unbindListener();
onSuccess = null;
callSerially(() -> f.onError(null, err, 1, err.toString()));
} else {
if(onSuccess != null) {
SMSInterceptor.unbindListener();
onSuccess = null;
}
}
}
}
1个 | 请注意,程序包与本机代码和其他类相同。 这允许回调类受到包保护,因此不会通过API公开(该类没有public修饰符) |
2 | 我们按顺序将回调包装在调用中,以匹配默认情况下使用EDT的Codename One约定。 该调用可能会到达Android本机线程,因此有必要对其进行规范化,并且不要将Android本机线程公开给用户代码。 |
一个简单的API
难题的最后一部分是一个简单的API,该API可以将整个内容包装起来,并且还隐藏了特定于Android的事实。 在上一期文章中,我们将介绍完整的API,但是现在这是隐藏本机接口的用户级API。 使用这样的类通常是一种好的做法,因为它使我们可以灵活地使用实际的基础本机接口。
package com.codename1.sms.intercept;
import com.codename1.system.NativeLookup;
import com.codename1.util.FailureCallback;
import com.codename1.util.SuccessCallback;
/**
* This is a high level abstraction of the native classes and callbacks rolled into one.
*/
public class SMSInterceptor {
private static NativeSMSInterceptor nativeImpl;
private static NativeSMSInterceptor get() {
if(nativeImpl == null) {
nativeImpl = NativeLookup.create(NativeSMSInterceptor.class);
if(!nativeImpl.isSupported()) {
nativeImpl = null;
}
}
return nativeImpl;
}
public static boolean isSupported() {
return get() != null;
}
public static void grabNextSMS(SuccessCallback<String> onSuccess) {
SMSCallback.onSuccess = onSuccess;
get().bindSMSListener();
}
static void unbindListener() {
get().unbindSMSListener();
}
}
下次
下次,我将结合用户体验,将所有内容打包到易于使用的cn1lib中。
就本机接口的使用而言,我在此处涉及到的一些内容可能有些“冗长”,因此,如果不清楚,只需在注释中提出即可。
翻译自: https://www.javacodegeeks.com/2017/09/intercept-incoming-sms-android.html
axios拦截器