【问题标题】:Firebase C++ Messaging Crashing on StartupFirebase C++ 消息在启动时崩溃
【发布时间】:2016-12-23 07:14:39
【问题描述】:

所以,我正在尝试在我的 Unreal 项目中使用 Firebase c++ 库,但我遇到了一些非常一致的崩溃:它在我第一次运行它时崩溃,在新卸载它后它运行良好,之后可以正常工作

这是我从 firebase 崩溃日志中获得的堆栈跟踪:

E/art (7271): No implementation found for void com.google.firebase.messaging.cpp.RegistrationIntentService.nativeOnTokenReceived(java.lang.String)(已尝试 Java_com_google_firebase_messaging_cpp_RegistrationIntentService_nativeOnTokenReceived 和 Java_com_google_firebase_messaging_cpp_RegistrationIntentService_nativeOnTokenReceived__Ljava_lang_String_2)

E/未捕获异常(7271):

E/UncaughtException(7271): java.lang.UnsatisfiedLinkError: No implementation found for void com.google.firebase.messaging.cpp.RegistrationIntentService.nativeOnTokenReceived(java.lang.String) (试过 Java_com_google_firebase_messaging_cpp_RegistrationIntentService_nativeOnTokenReceived 和 Java_com_google_firebase_messaging_cpp_RegistrationIntentService_native >

E/UncaughtException(7271):在 com.google.firebase.messaging.cpp.RegistrationIntentService.nativeOnTokenReceived(Native Method)

E/UncaughtException(7271):在 com.google.firebase.messaging.cpp.RegistrationIntentService.onHandleIntent(RegistrationIntentService.java:31)

E/UncaughtException(7271):在 android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)

E/UncaughtException(7271):在 android.os.Handler.dispatchMessage(Handler.java:102)

E/UncaughtException(7271): 在 android.os.Looper.loop(Looper.java:145)

E/UncaughtException(7271):在 android.os.HandlerThread.run(HandlerThread.java:61)

它说没有实现 nativeOnTokenReceived,但它是在 firebase c++ sdk 库中实现的。 当 RegistrationIntentService 从 FcmInstanceIDListenerService 发送意图时发生崩溃,这发生在 firebase 提供新令牌时,这总是在重新安装或清除应用程序数据后在应用程序启动时发生(我不确定是否有可能使其发生在与启动时间不同)。

但是,RegistrationIntentService 已激活 onHandleIntent,并且在我的 c++ 侦听器类初始化时调用 nativeOnTokenReceived 没有任何问题,在应用程序的过程中。有人知道是什么原因导致了这次崩溃吗?

在使用 ndk-build 之前,Unreal 的构建过程将静态 .a 库从 sdk 打包成单个 .so 可能是相关的。

这是从 sdk 的 libmessaging_java.jar 中提取的 RegistrationIntentService 和 FcmInstanceIDListenerService 的代码

FcmInstanceIDListenerService.java
    package com.google.firebase.messaging.cpp;

     import android.content.Intent;
     import com.google.firebase.iid.FirebaseInstanceIdService;

     public class FcmInstanceIDListenerService
       extends FirebaseInstanceIdService
     {
       public void onTokenRefresh()
      {
        Intent intent = new Intent(this, RegistrationIntentService.class);
         startService(intent);
       }
     }

RegistrationIntentService.java
    package com.google.firebase.messaging.cpp;

    import android.app.IntentService;
    import android.content.Intent;
    import com.google.firebase.iid.FirebaseInstanceId;

    public class RegistrationIntentService
       extends IntentService
     {
       private static final String TAG = "FirebaseRegService";

       public RegistrationIntentService()
       {
         super("FirebaseRegService");
       }

       protected void onHandleIntent(Intent intent)
       {
         DebugLogging.log("FirebaseRegService", String.format("onHandleIntent token=%s", new Object[] {
           FirebaseInstanceId.getInstance().getToken() }));
         String token = FirebaseInstanceId.getInstance().getToken();
         if (token != null) {
           nativeOnTokenReceived(token);
         }
       }

       private static native void nativeOnTokenReceived(String paramString);
     }

【问题讨论】:

  • 嘿嘿。除了编写自己的本机函数之外,您是否找到过任何解决方案?如果没有,你能在这里分享你的解决方案吗?我目前遇到了同样的问题。
  • 当然,有机会我会写一点。预计几个小时后会出现

标签: android c++ firebase firebase-cloud-messaging unreal-engine4


【解决方案1】:

所以,我想我在这里发布我对这个问题的解决方案..

首先,我真的不知道为什么会出现问题,但我感觉这与 Unreal 的构建系统有关。 (有趣的事实:当我尝试在虚幻引擎中使用 Google 的 Protobuf 库时,我也遇到了无法解释的错误。)

因此,由于 JNI 找不到所需的库定义函数,我编写了自己的函数来替换它。

基本上,当您使用 Firebase Messaging C++ SDK 时,您的项目中需要包含两个组件:c++ 静态库和标头,以及一个 java 库,libmessaging_java.jar jar文件中定义了几个类,大部分都很好很好,但有几个需要编辑,反编译就可以了 (我用this tool

所以,在RegistrationIntentService 里面我声明了

private static native void nativeOnNewToken(String paramString);

并用它替换了对nativeOnTokenReceived的调用

ListenerService我声明

private static native void nativeOnNewMessage(String paramString1, String paramString2, String paramString3, Map<String, String> paramMap);

并将其添加到writeMessageToInternalStorage()

  private void writeMessageToInternalStorage(String from, String msgId, String error, Map<String, String> data)
   {
   //Added code
    nativeOnNewMessage(from, msgId, error, data);

    //I'm passing the message directly to the C++ code, rather than storing
    //it in a buffer, and processing every once in a while
    //like the sdk normally does; so surround this crap with if(false)

    if(false){
    //end added code
         try
         {
           JSONObject json = messageToJson(from, msgId, error, data);
           DebugLogging.log("FIREBASE_LISTENER", json.toString());
           writeStorageFile(json.toString());
         } catch (JSONException e) {
           e.printStackTrace();
         }
     //added code
        }
    ///end added code
   }

现在,消息和令牌正在发送到我的函数,所以我需要声明它们:

#include "FirebaseMessageListener.h"
#include "stdio.h"

#include <string>
#include <map>

#if PLATFORM_ANDROID
//jni calls from the listener services
    extern "C" void Java_com_google_firebase_messaging_cpp_ListenerService_nativeOnNewMessage(JNIEnv* jenv, jobject thiz, jstring from, jstring mssgID, jstring error, jobject data) {
        UE_LOG(FirebaseLog, Log, TEXT("Entering nativeOnNewMessage *****"));
        printf("Entering nativeOnNewMessage *****");

        std::map<std::string, std::string> data_out;
        std::string messageID;
        std::string fromID;

        //code iterating through map from java, based off code from here:https://android.googlesource.com/platform/frameworks/base.git/+/a3804cf77f0edd93f6247a055cdafb856b117eec/media/jni/android_media_MediaMetadataRetriever.cpp 
        // data is a Map<String, String>.
        if (data) {
            // Get the Map's entry Set.
            jclass mapClass = jenv->FindClass("java/util/Map");
            if (mapClass == NULL) {
                return;
            }
            jmethodID entrySet =
                jenv->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
            if (entrySet == NULL) {
                return;
            }
            jobject set = jenv->CallObjectMethod(data, entrySet);
            if (set == NULL) {
                return;
            }
            // Obtain an iterator over the Set
            jclass setClass = jenv->FindClass("java/util/Set");
            if (setClass == NULL) {
                return;
            }
            jmethodID iterator =
                jenv->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
            if (iterator == NULL) {
                return;
            }
            jobject iter = jenv->CallObjectMethod(set, iterator);
            if (iter == NULL) {
                return;
            }
            // Get the Iterator method IDs
            jclass iteratorClass = jenv->FindClass("java/util/Iterator");
            if (iteratorClass == NULL) {
                return;
            }
            jmethodID hasNext = jenv->GetMethodID(iteratorClass, "hasNext", "()Z");
            if (hasNext == NULL) {
                return;
            }
            jmethodID next =
                jenv->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
            if (next == NULL) {
                return;
            }
            // Get the Entry class method IDs
            jclass entryClass = jenv->FindClass("java/util/Map$Entry");
            if (entryClass == NULL) {
                return;
            }
            jmethodID getKey =
                jenv->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
            if (getKey == NULL) {
                return;
            }
            jmethodID getValue =
                jenv->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
            if (getValue == NULL) {
                return;
            }
            // Iterate over the entry Set
            while (jenv->CallBooleanMethod(iter, hasNext)) {
                jobject entry = jenv->CallObjectMethod(iter, next);
                jstring key = (jstring)jenv->CallObjectMethod(entry, getKey);
                jstring value = (jstring)jenv->CallObjectMethod(entry, getValue);
                const char* keyStr = jenv->GetStringUTFChars(key, NULL);
                if (!keyStr) {  // Out of memory
                    return;
                }
                const char* valueStr = jenv->GetStringUTFChars(value, NULL);
                if (!valueStr) {  // Out of memory
                    jenv->ReleaseStringUTFChars(key, keyStr);
                    return;
                }
                data_out.insert(std::pair<std::string, std::string>(std::string(keyStr), std::string(valueStr)));
                jenv->DeleteLocalRef(entry);
                jenv->ReleaseStringUTFChars(key, keyStr);
                jenv->DeleteLocalRef(key);
                jenv->ReleaseStringUTFChars(value, valueStr);
                jenv->DeleteLocalRef(value);
            }
        }

        if (from != nullptr) {
            const char* valueStr = jenv->GetStringUTFChars(from, NULL);
            if (!valueStr) {  // Out of memory
                return;
            }
            fromID = std::string(valueStr);
            jenv->ReleaseStringUTFChars(from, valueStr);
        }

        if (mssgID != nullptr) {
            const char* valueStr = jenv->GetStringUTFChars(mssgID, NULL);
            if (!valueStr) {  // Out of memory
                return;
            }
            messageID = std::string(valueStr);
            jenv->ReleaseStringUTFChars(mssgID, valueStr);
        }

        FirebaseMessageListener::Get()->onNewMessage(fromID, messageID, data_out);
    }

    extern "C" void Java_com_google_firebase_messaging_cpp_RegistrationIntentService_nativeOnNewToken(JNIEnv* jenv, jobject thiz, jstring inJNIStr) {
        UE_LOG(FirebaseLog, Log, TEXT("Entering nativeOnNewToken *****"));
        printf("Entering nativeOnNewToken *****");
        //first, put the token into a c string
        jboolean isCopy;
        const char* token = jenv->GetStringUTFChars(inJNIStr, &isCopy);
        FirebaseMessageListener::Get()->onNewToken(token);
    }

#endif

这些将消息传递给我创建的单例类。你可以在这里对它们做任何你想做的事情,但我将它们传递给firebase::messaging::Listener reference,以尝试保持与 Firebase SDK 的兼容性(以防我让它正常工作)

class RIFT411_API FirebaseMessageListener
{
private:
    static FirebaseMessageListener* self;

    //private so that new instance can only be made through call to Get()
    FirebaseMessageListener();
#if PLATFORM_ANDROID
    JNIEnv * env = FAndroidApplication::GetJavaEnv();
    jobject activity = FAndroidApplication::GetGameActivityThis();
#endif

    //signals to return the key
    void getToken();

    //send the messages to the firebase sdk implemented listener, so we'll have an easier time if we ever want to move back
    firebase::messaging::Listener *listener = nullptr;

public:
    static FirebaseMessageListener* Get();

    void onNewToken(const char* token);
    void onNewMessage(std::string, std::string, std::map<std::string, std::string> &data);

    void Init(firebase::messaging::Listener *listener);

    ~FirebaseMessageListener();
};

//

FirebaseMessageListener* FirebaseMessageListener::self = nullptr;


FirebaseMessageListener* FirebaseMessageListener::Get()
{
    if (self == nullptr) {
        self = new FirebaseMessageListener();
    }
    return self;
}

void FirebaseMessageListener::getToken() {
#if PLATFORM_ANDROID
    //This has to happen in the main thread, or some of the FindClass() calls will fail

    UE_LOG(FirebaseLog, Log, TEXT("Trying to grab token *****"));
    printf("Trying to grab token *****");

    env = FAndroidApplication::GetJavaEnv();
    activity = FAndroidApplication::GetGameActivityThis();

    jclass cls_intent = (env)->FindClass("android/content/Intent");
    if (NULL == cls_intent) { UE_LOG(FirebaseLog, Error, TEXT("Failure getting cls_intent")); return; }

    jclass cls_service = FAndroidApplication::FindJavaClass("com/google/firebase/messaging/cpp/RegistrationIntentService");
    //jclass cls_service = (env)->FindClass("com/google/firebase/messaging/cpp/RegistrationIntentService");
    if (NULL == cls_service) { UE_LOG(FirebaseLog, Error, TEXT("Failure getting cls_service")); }


    // Get the Method ID of the constructor which takes an int
    jmethodID mid_Init = (env)->GetMethodID(cls_intent, "<init>", "(Landroid/content/Context;Ljava/lang/Class;)V");
    if (NULL == mid_Init) { UE_LOG(FirebaseLog, Error, TEXT("Failure getting mid_Init")); return;}

    // Call back constructor to allocate a new instance, with an int argument
    jobject obj_intent = (env)->NewObject(cls_intent, mid_Init, activity, cls_service);
    if (NULL == obj_intent) { UE_LOG(FirebaseLog, Error, TEXT("Failure getting obj_intent")); return; }

    if (NULL == activity) { UE_LOG(FirebaseLog, Error, TEXT("Failure getting activity")); return; }

    jclass cls_activity = (env)->GetObjectClass(activity);
    if (NULL == cls_activity) { UE_LOG(FirebaseLog, Error, TEXT("Failure getting cls_activity")); return; }

    jmethodID mid_start = (env)->GetMethodID(cls_activity, "startService", "(Landroid/content/Intent;)Landroid/content/ComponentName;");
    if (NULL == mid_start) { UE_LOG(FirebaseLog, Error, TEXT("Failure getting mid_start")); return; }


    UE_LOG(FirebaseLog, Log, TEXT("MADE IT TO THE END!"));
    (env)->CallObjectMethod(activity, mid_start, obj_intent);

#endif
}

void FirebaseMessageListener::onNewToken(const char* token) {
    UE_LOG(FirebaseLog, Log, TEXT("Recieving new token in Unreal! Hooray! ***** %s"), *FString(token));
    printf("Recieving new Message in Unreal! Hooray! *****\n");
    if (listener != nullptr) {
        listener->OnTokenReceived(token);
    }
}

void FirebaseMessageListener::onNewMessage(std::string from, std::string MessageID, std::map<std::string, std::string> &data) {
    UE_LOG(FirebaseLog, Log, TEXT("Recieving new Message in Unreal! Hooray! *****"));
    printf("Recieving new Message in Unreal! Hooray! *****\n");
    if (!data.empty()) {
        UE_LOG(FirebaseLog, Log, TEXT("data:"));
        typedef std::map<std::string, std::string>::const_iterator MapIter;
        for (MapIter it = data.begin(); it != data.end(); ++it) {
            FString s1 = FString(it->first.c_str());
            FString s2 = FString(it->second.c_str());
            UE_LOG(FirebaseLog, Log, TEXT("  %s: %s"), *s1, *s2);
        }
    }

    ::firebase::messaging::Message message;

    message.data = data;
    message.from = from;
    message.message_id = MessageID;


    if (listener != nullptr) {
        listener->OnMessage(message);
    }
}

void FirebaseMessageListener::Init(firebase::messaging::Listener *newlistener) {
    this->listener = newlistener;
    FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady([=]()
    {
        getToken();
    }, TStatId(), NULL, ENamedThreads::GameThread);

}

FirebaseMessageListener::FirebaseMessageListener()
{
    self = this;
}

FirebaseMessageListener::~FirebaseMessageListener()
{
}

与此有关的一件事是,您不会从应用关闭时收到的通知中获取数据。这些数据被打包在一个 Intent 中,我几乎有一个很好的方法来获取它,我可能会在完成后发布它

【讨论】:

  • 感谢您发布此信息。我检查了messaging.a,它似乎不包含Java_com_..._nativeOnTokenReceived 的定义,所以我自己实现了它,这似乎有效。实际上,我的感觉是 Firebase 的 C++ SDK 存在问题。问题是我收到这种方式太早了,在任何来自虚幻的东西加载之前。我想这也是为什么你有 CreateAndDispatchWhenReady / getToken 部分?
  • 我这样做是不久前的事了,所以我不记得我的所有推理了。我很确定CreateAndDispatchWhenReady 旨在确保getToken() 在游戏线程上运行,尽管它也会延迟它直到虚幻的结束,这可能是它工作的原因似乎真的很奇怪messaging.a 没有 ...OnTokenRecieved() 的定义。它必须在某个地方定义
  • 我也想知道,但最后它允许我的定义没有抱怨,而且效果很好。我现在还要做的是向 GameActivity 添加一个小的 startService(new Intent(RegistrationIntentService.class)) 函数(通过 UPL,因为我有 Firebase 作为插件),然后在实际游戏请求令牌时通过一个简单的 JNI 调用触发该函数。
  • 所以,我终于又开始着手解决这个问题了。我做的第一件事是更新到最新版本的 c++ sdk,它似乎无需修改就可以工作
【解决方案2】:

com.google.firebase.messaging.cpp 只监听 FCM 服务发送的消息并将它们转发到 Android C++ 库 (libmessaging.a)。看起来问题是由于 firebase-messaging aar 未包含在您的应用程序中,这导致未加载 com.google.firebase.messaging.cpp 包,因为它取决于 firebase-messaging aar 中的类不存在。

例如,当使用 gradle 构建时,可以使用以下 https://github.com/firebase/quickstart-cpp/blob/master/messaging/testapp/build.gradle#L93 包含 aar

现在,如 https://groups.google.com/d/msg/firebase-talk/eGNr36dpB70/VXVqBfL1BAAJ 中所述,在 Unreal 项目中包含 aars 有点棘手。基本上,您需要解压缩(aars 只是 zip 文件)firebase-messaging aar(查看 ${ANDROID_HOME}/extras/google/firebase/firebase-messaging)及其依赖项(您需要阅读每个 *. pom 文件以获取依赖项)并包含从您的 Unreal 项目中的每个 aar 提取的各种 .jar、AndroidManifest.xml 文件和资源。确保将每个 AndroidManifest.xml 合并到最终应用程序的 AndroidManifest.xml 中非常重要,以便包含 Firebase 使用的所有服务和内容提供程序。

【讨论】:

  • 您好,感谢您回答这么老的问题!
  • 我应该在问题中提到这一点,但我将 gradle “插入”到我的 Unreal 构建过程的构建的尾部,以处理 AAR 下载和合并,所以我全部,他们正在接收消息当它试图通过调用 ndk sdk 中包含的静态库中的本机函数来将该消息发送到 c 代码时崩溃了。 (但只有当它调用那个初始函数时)我从来没有设法弄清楚为什么那个 fcn 崩溃了,但我确实通过在我的项目代码中编写自己的函数来解决它接收消息。
  • 你的答案有很多信息我花了一段时间才找到,所以我希望它可以帮助某人:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-12-12
  • 2021-12-02
  • 1970-01-01
  • 2020-06-09
  • 2019-08-16
  • 2019-05-12
  • 2013-09-22
相关资源
最近更新 更多