You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native模块无法获取手机号问题排查求助

排查React Native PhoneNumberHintModule无法获取手机号的问题

我基于Smart Lock for Passwords API实现了React Native原生模块PhoneNumberHintModule用于获取手机号,原生模块代码和RN端事件监听代码如下,但始终无法成功获取手机号,事件回调也没触发,请帮忙排查原因。

原生模块代码

public class PhoneNumberHintModule extends ReactContextBaseJavaModule {

    private static ReactApplicationContext reactContext;
    private static final int CREDENTIAL_PICKER_REQUEST = 1;
    private static Promise mPromise;
    private static final String EVENT_PHONE_NUMBER_SELECTED = "phoneNumberSelected";

    private final BaseActivityEventListener activityEventListener = new BaseActivityEventListener() {
        @Override
        public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
            PhoneNumberHintModule.this.onActivityResult(requestCode, resultCode, data);
        }
    };

    public PhoneNumberHintModule(ReactApplicationContext context) {
        super(context);
        reactContext = context;
        if (reactContext != null) {
            reactContext.addActivityEventListener(activityEventListener);
        }
    }

    @NonNull
    @Override
    public String getName() {
        return "PhoneNumberHintModule";
    }

    @ReactMethod
    public void getPhoneNumberHint(final Promise promise) {
        mPromise = promise;
        final Activity currentActivity = getCurrentActivity();

        if (currentActivity == null) {
            promise.reject("ACTIVITY_NOT_AVAILABLE", "Activity is not available");
            return;
        }

        Context context = getReactApplicationContext();
        HintRequest hintRequest = new HintRequest.Builder()
                .setPhoneNumberIdentifierSupported(true)
                .build();

        IntentSender intent = Credentials.getClient(context).getHintPickerIntent(hintRequest).getIntentSender();

        try {
            currentActivity.startIntentSenderForResult(
                    intent,
                    CREDENTIAL_PICKER_REQUEST,
                    null,
                    0,
                    0,
                    0,
                    new Bundle()
            );
        } catch (IntentSender.SendIntentException e) {
            e.printStackTrace();
            sendErrorToReactNative("SEND_INTENT_ERROR", e.getMessage());
        }
    }

    private void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == CREDENTIAL_PICKER_REQUEST) {
            if (resultCode == Activity.RESULT_OK) {
                handleCredentialResult(data);
            } else {
                sendErrorToReactNative("RESULT_NOT_OK", "Result code is not OK");
            }
        }
    }

    private void handleCredentialResult(Intent data) {
        if (data != null) {
            Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
            if (credential != null) {
                String phoneNumber = credential.getId();
                if (phoneNumber != null) {
                    emitPhoneNumberSelectedEvent(phoneNumber);
                    if (mPromise != null) {
                        mPromise.resolve(phoneNumber);
                    }
                } else {
                    sendErrorToReactNative("PHONE_NUMBER_NULL", "Phone number is null");
                }
            } else {
                sendErrorToReactNative("CREDENTIAL_NULL", "Credential is null");
            }
        } else {
            sendErrorToReactNative("DATA_NULL", "Intent data is null");
        }
    }

    private void emitPhoneNumberSelectedEvent(String phoneNumber) {
        if (reactContext != null) {
            reactContext
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit(EVENT_PHONE_NUMBER_SELECTED, phoneNumber);
        }
    }

    private void sendErrorToReactNative(String code, String message) {
        if (mPromise != null) {
            mPromise.reject(code, message);
        }
    }
}

React Native端代码

useEffect(() => {
    const subscription = phoneNumberHintModuleEventEmitter.addListener(
      'phoneNumberSelected',
      phoneNumber => {
        console.log('Received phone number:', phoneNumber);
      },
    );

    return () => {
      subscription.remove();
    };
  }, []);

  const handleOpenHint = async () => {
    try {
      const phoneNumber = await PhoneNumberHintModule.getPhoneNumberHint();
      console.log('Selected phone number:', phoneNumber);
    } catch (error) {
      console.error('Error fetching phone number hint:', error);
    }
  };

可能的问题及修复方案

1. 静态变量导致的上下文/Promise污染

代码中reactContext和mPromise使用了静态变量,当模块被多次初始化或多组件调用时,会出现上下文覆盖、Promise复用的问题,直接导致回调失效。

  • 修复:将静态变量改为实例变量:
    // 移除static修饰符
    private ReactApplicationContext reactContext;
    private Promise mPromise;
    

2. ActivityEventListener注册时机错误

构造函数中注册监听时,Activity可能尚未绑定到ReactContext,导致onActivityResult无法被捕获。

  • 修复:通过重载onHostResume/onHostPause方法,在Activity生命周期节点注册/移除监听:
    @Override
    public void onHostResume() {
        super.onHostResume();
        if (getReactApplicationContext() != null) {
            getReactApplicationContext().addActivityEventListener(activityEventListener);
        }
    }
    
    @Override
    public void onHostPause() {
        super.onHostPause();
        if (getReactApplicationContext() != null) {
            getReactApplicationContext().removeActivityEventListener(activityEventListener);
        }
    }
    

3. Credential解析方式过时

新版Smart Lock API中,直接通过Credential.EXTRA_KEY获取数据可能失效,建议使用HintResult解析返回的Intent:

  • 修复:修改handleCredentialResult方法:
    private void handleCredentialResult(Intent data) {
        if (data != null) {
            try {
                HintResult hintResult = Credentials.getClient(getReactApplicationContext()).getHintResultFromIntent(data);
                if (hintResult != null) {
                    String phoneNumber = hintResult.getCredential().getId();
                    if (phoneNumber != null) {
                        emitPhoneNumberSelectedEvent(phoneNumber);
                        if (mPromise != null) {
                            mPromise.resolve(phoneNumber);
                            mPromise = null; // 清空Promise避免内存泄漏
                        }
                    } else {
                        sendErrorToReactNative("PHONE_NUMBER_NULL", "Phone number is null");
                    }
                } else {
                    sendErrorToReactNative("HINT_RESULT_NULL", "Hint result is null");
                }
            } catch (ApiException e) {
                e.printStackTrace();
                sendErrorToReactNative("API_EXCEPTION", e.getMessage());
            }
        } else {
            sendErrorToReactNative("DATA_NULL", "Intent data is null");
        }
    }
    

4. 权限配置缺失

确保AndroidManifest.xml中添加了必要权限,Android 10+还需动态申请部分权限:

<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

5. RN端事件发射器初始化错误

确认phoneNumberHintModuleEventEmitter是基于NativeEventEmitter正确创建的:

import { NativeEventEmitter, NativeModules } from 'react-native';
const { PhoneNumberHintModule } = NativeModules;
const phoneNumberHintModuleEventEmitter = new NativeEventEmitter(PhoneNumberHintModule);

6. Promise未清空导致内存泄漏

处理完Promise后需主动清空,避免后续调用复用旧Promise:

private void sendErrorToReactNative(String code, String message) {
    if (mPromise != null) {
        mPromise.reject(code, message);
        mPromise = null; // 添加此行
    }
}

内容的提问来源于stack exchange,提问作者Murshid Ansari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 20:35:13