首页 > 解决方案 > 使用锁定的设备 Headless JS 杀死 React Native Android 应用程序:在桥被破坏后调用 JS 函数(从后台启动应用程序)

问题描述

我正在使用react-native-callkeep来显示原生 Android 调用 UI。向上滑动接听电话时,我想调用自己的本机模块来解锁设备。当应用程序处于后台和前台时,这可以工作并解锁设备,但当应用程序在锁定设备之前被用户杀死时则不行。

当设备接收到只有数据的 Firebase Cloud Message 时,调用 UI 正确显示,但是当向上滑动接听电话时,我在 Logcat 中看到了这个日志:

ReactNative:在桥被破坏后调用 JS 函数:RCTDeviceEventEmitter.emit(["RNCallKeepPerformAnswerCallAction",{"callUUID":"d2a035dc-3bcc-4ece-a509-e93a8bc62ab3"}])

在此之前,我在调用时也会看到此错误,RNCallKeep.registerPhoneAccount(...)如下所述:

2021-07-03 16:13:32.874 29401-29470/com.xyz E/unknown:ReactNative: CatalystInstanceImpl caught native exception
java.lang.NullPointerException: Attempt to invoke interface method 'boolean com.facebook.react.bridge.ReadableMap.hasKey(java.lang.String)' on a null object reference
at io.wazo.callkeep.RNCallKeepModule.isSelfManaged(RNCallKeepModule.java:120)
at io.wazo.callkeep.RNCallKeepModule.registerPhoneAccount(RNCallKeepModule.java:630)
at io.wazo.callkeep.RNCallKeepModule.registerPhoneAccount(RNCallKeepModule.java:166)
at java.lang.reflect.Method.invoke(Native Method)
at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:151)
at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
at android.os.Looper.loop(Looper.java:223)
at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
at java.lang.Thread.run(Thread.java:923)

这是我在收到消息时由 Headless JS 运行的 Javascript 代码:

    RNCallKeep.registerPhoneAccount()
    RNCallKeep.displayIncomingCall()
    RNCallKeep.setForegroundServiceSettings()
    RNCallKeep.registerAndroidEvents()
    RNCallKeep.addEventListener('answerCall', () => {
        NativeModules.DeviceLock.unlock(); // call to my native module here on swiping up
    })

这是我在回调中调用的本机模块:

    public class DeviceLock extends ReactContextBaseJavaModule {
    
        @Override
        public String getName() {
            return "DeviceLock";
        }
    
        private ReactContext mReactContext;
        private PowerManager.WakeLock sCpuWakeLock;
        private Activity activity;
    
        public DeviceLock(ReactApplicationContext reactContext) {
            super(reactContext);
            mReactContext = reactContext;
        }
    
        /* React Methods */
        @ReactMethod
        public void unlock() {
            activity = mReactContext.getCurrentActivity();
    
            PowerManager pm = (PowerManager) 
            mReactContext.getSystemService(Context.POWER_SERVICE);
            int flags = PowerManager.ACQUIRE_CAUSES_WAKEUP | 
            PowerManager.ON_AFTER_RELEASE;
            sCpuWakeLock = pm.newWakeLock(flags, activity.getClass().getName());
            sCpuWakeLock.acquire();
    
            activity.runOnUiThread(() -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                    activity.setShowWhenLocked(true);
                    activity.setTurnScreenOn(true);
                }
    
                activity.getWindow().addFlags(
                        WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    
            });
        }
    }

我也尝试在设备收到 FCM 消息后立即调用上面的本机代码而不调用RNCallkeep方法,但它没有效果。

我相信这个问题与手机/应用程序可用的 React Context 有关。错误消息是什么意思,我怎样才能成功调用我的本机模块,为什么当应用程序在后台但在被杀死时不能工作?

更新

我了解 Android 对我们作为开发人员的限制,以鼓励与实施可能不安全或危险的功能相关的最佳实践。我的主要问题是理解为什么我的代码会在应用程序被杀死而设备没有被锁定时工作,而不是在应用程序被杀死并且设备锁定时工作。

如果我需要一个替代解决方案,即我使用全屏 Intent 或类似的通知,我愿意使用它,只要它是功能性的并且允许我的用户以实用的方式与应用程序交互。总之,我需要一个解决方案,以便当应用程序被终止时我可以在锁定的设备上显示和界面,从而允许用户接听或拒绝呼叫并相应地在锁定屏幕上启动应用程序。我知道这是可能的,因为它在许多调用应用程序中都是这样工作的,例如 WhatApp。

更新 2

我已经尝试过这种方法,DeviceEventEmitter在 React 上下文可用时通知 JS 后台任务,然后只尝试解锁设备,但我现在明白问题是Activity在屏幕启动时首先从后台任务启动锁定:

public class MainActivity extends ReactActivity implements ReactInstanceManager.ReactInstanceEventListener {

    private DeviceEventManagerModule.RCTDeviceEventEmitter mEmitter = null;
    private static final String TAG = "MainActivity";

    @Override
    protected String getMainComponentName() {
        return "fleeting";
    }

    public void onReactContextInitialized(ReactContext context) {
        Log.d(TAG, "Here's your valid ReactContext ················································");
        if (mEmitter == null) {
            mEmitter = context.getJSModule((DeviceEventManagerModule.RCTDeviceEventEmitter.class));
        }
        if (mEmitter != null){
            WritableMap eventData = Arguments.createMap();
            eventData.putString("data", "data");
            mEmitter.emit("reactContext", eventData);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        RNBootSplash.init(R.drawable.bootsplash, MainActivity.this);
                         
        getReactInstanceManager().addReactInstanceEventListener(this);
    }
}

然后在 JS 中:

DeviceEventEmitter.addListener('reactContext', () => {
        console.log('React Application Context Available ========== 
 LAUNCH APP HERE');
    });

标签: javaandroidreact-native

解决方案


推荐阅读