基于Expo的React Native实现来电及挂断时弹出弹窗
实现Expo Android应用来电状态监听及弹窗功能
方案一:Bare Workflow(已 eject 的 Expo 项目)
这种方式可以直接编写原生代码实现广播接收器,步骤如下:
- 配置权限与广播接收器
在android/app/src/main/AndroidManifest.xml中添加权限和广播接收器声明:
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 如需获取通话详情可添加此权限 --> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <receiver android:name=".PhoneStateReceiver"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> </receiver>
- 编写广播接收器类
在android/app/src/main/java/com/[你的应用包名]/下创建PhoneStateReceiver.java,负责监听来电状态并通知JS层:
package com.[你的应用包名]; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.modules.core.DeviceEventManagerModule; public class PhoneStateReceiver extends BroadcastReceiver { private static ReactApplicationContext reactContext; private String lastState = TelephonyManager.EXTRA_STATE_IDLE; public static void setReactContext(ReactApplicationContext context) { reactContext = context; } @Override public void onReceive(Context context, Intent intent) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); if (state != null && !state.equals(lastState)) { lastState = state; if (reactContext != null) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("phoneStateChanged", state + "," + incomingNumber); } } } }
- 初始化接收器上下文
在MainApplication.java的onCreate方法中添加上下文初始化:
@Override public void onCreate() { super.onCreate(); PhoneStateReceiver.setReactContext(getReactNativeHost().getReactApplicationContext()); }
- JS层监听事件并弹窗
使用React Native的NativeEventEmitter监听原生事件,触发弹窗:
import { Alert, NativeEventEmitter, NativeModules, PermissionsAndroid } from 'react-native'; // 动态申请电话权限(Android 6+ 必需) async function requestPhonePermission() { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE, { title: '电话权限申请', message: '应用需要访问电话状态来监听来电', buttonPositive: '允许', buttonNegative: '取消', }, ); return granted === PermissionsAndroid.RESULTS.GRANTED; } // 组件内监听事件 componentDidMount() { requestPhonePermission().then(granted => { if (granted) { const eventEmitter = new NativeEventEmitter(NativeModules.DeviceEventManagerModule); this.subscription = eventEmitter.addListener('phoneStateChanged', data => { const [state, number] = data.split(','); if (state === 'RINGING') { Alert.alert('来电提醒', `来电号码:${number || '未知'}`); } else if (state === 'IDLE') { Alert.alert('通话结束', '已挂断电话'); } }); } }); } // 组件卸载时取消订阅 componentWillUnmount() { this.subscription?.remove(); }
方案二:Managed Workflow(未 eject 的 Expo 项目)
通过自定义Config Plugin集成原生代码,无需手动eject:
- 创建Config Plugin文件
在项目根目录创建withPhoneStateReceiver.js,用来自动修改Android配置和添加原生代码:
const { withAndroidManifest, withDangerousMod } = require('@expo/config-plugins'); const fs = require('fs'); const path = require('path'); module.exports = function withPhoneStateReceiver(config) { // 修改AndroidManifest.xml添加权限和接收器 config = withAndroidManifest(config, async config => { const manifest = config.modResults.manifest; // 添加权限 manifest['uses-permission'].push({ $: { 'android:name': 'android.permission.READ_PHONE_STATE' } }); // 添加广播接收器 manifest.application[0].receiver.push({ $: { 'android:name': '.PhoneStateReceiver' }, 'intent-filter': [{ action: [{ $: { 'android:name': 'android.intent.action.PHONE_STATE' } }] }] }); return config; }); // 添加广播接收器Java文件 config = withDangerousMod(config, [ 'android', async config => { const srcPath = path.join(config.modRequest.platformProjectRoot, 'app/src/main/java/com/[你的应用包名]'); if (!fs.existsSync(srcPath)) fs.mkdirSync(srcPath, { recursive: true }); const receiverCode = `package com.[你的应用包名]; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.modules.core.DeviceEventManagerModule; public class PhoneStateReceiver extends BroadcastReceiver { private static ReactApplicationContext reactContext; private String lastState = TelephonyManager.EXTRA_STATE_IDLE; public static void setReactContext(ReactApplicationContext context) { reactContext = context; } @Override public void onReceive(Context context, Intent intent) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); if (state != null && !state.equals(lastState)) { lastState = state; if (reactContext != null) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("phoneStateChanged", state + "," + incomingNumber); } } } }`; fs.writeFileSync(path.join(srcPath, 'PhoneStateReceiver.java'), receiverCode); return config; } ]); // 修改MainApplication.java初始化接收器 config = withDangerousMod(config, [ 'android', async config => { const mainAppPath = path.join(config.modRequest.platformProjectRoot, 'app/src/main/java/com/[你的应用包名]/MainApplication.java'); let content = fs.readFileSync(mainAppPath, 'utf8'); // 添加导入语句 if (!content.includes('import com.[你的应用包名].PhoneStateReceiver;')) { content = content.replace('import com.facebook.react.ReactApplication;', '$&\nimport com.[你的应用包名].PhoneStateReceiver;'); } // 添加初始化代码 if (!content.includes('PhoneStateReceiver.setReactContext')) { content = content.replace('@Override\npublic void onCreate() {', '$&\n PhoneStateReceiver.setReactContext(getReactNativeHost().getReactApplicationContext());'); } fs.writeFileSync(mainAppPath, content); return config; } ]); return config; };
- 配置App.json
在app.json的expo.plugins中添加自定义插件:
{ "expo": { "plugins": ["./withPhoneStateReceiver.js"] } }
- JS层代码
与Bare Workflow的JS层代码完全一致,包括权限申请和事件监听弹窗逻辑。
内容的提问来源于stack exchange,提问作者Bala
相关产品推荐
相关产品推荐

