Im vorherigen Tutorial wurden "Custom Controls" mit einem einfachen Beispiel vorgestellt. In diesem Tutorial gehen wir einen Schritt weiter und implementieren Flurry Analytics als Custom Control.
Flurry Analytics ist ein kostenloses Analyse Service, mit dem die App-Performance gemessen, überwacht und analysiert werden kann. Für das Tutorial wurden die zum Erstellzeitpunkt letztgültigen SDKs "Flurry Android SDK vAndroid SDK 5.3.0" und "Flurry iPhone SDK viPhone SDK 6.3.0" verwendet.
- Zu beachten
- Hinweis: Wir verwenden in diesem Tutorial lediglich die Analytics Funktion und verzichten auf die Verwendung von Flurry Ads.
Quicklinks zu den einzelnen Abschnitten in diesem Tutorial:
Back-End
Damit Flurry als Analyseservice verwendet werden kann, muss die App zunächst am Flurry Server eingerichtet werden. Dafür muss auf dev.flurry.com ein Konto erstellt und für Android und iOS je ein getrenntes Projekt angelegt werden. Die eindeutigen ProjectApiKeys werden dann in den entsprechenden Konfigurationsdateien eingetragen.
Flurry Interface
Zunächst definieren wir mit einer abstrakten Klasse das Interface für unser Flurry-Control, also die C++ Methoden, mit denen wir auf das Flurry SDK zugreifen wollen.
Um die Projektstruktur übersichtlich zu halten, speichern wir alle Flurry relevanten Dateien im Unterordner flurry
ab.
source/flurry/flurry_control.h
// Copyright 2015 Spraylight #ifndef __FLURRY_CONTROL_H__ #define __FLURRY_CONTROL_H__ #include "murl_custom_controlable.h" #include "murl_map.h" namespace Murl { namespace App { class FlurryControl : public CustomControlable { public: static FlurryControl* Create(const String& name); enum FlurryEventRecordStatus { FlurryEventFailed = 0, FlurryEventRecorded, FlurryEventUniqueCountExceeded, FlurryEventParamsCountExceeded, FlurryEventLogCountExceeded, FlurryEventLoggingDelayed }; FlurryControl(const String& name) : CustomControlable(name) {} virtual ~FlurryControl() {} /** @brief Log a optionally timed event without parameters with the Flurry service. To end the timer, call endTimedEvent(String) with this eventId. @param eventId the name/id of the event @param timed true if the event should be timed, false otherwise @return the error code */ virtual SInt32 LogEvent(const String& eventId, const Bool timed=false) = 0; /** @brief Log a optionally timed event with one parameter with the Flurry service. To end the timer, call endTimedEvent(String) with this eventId. @param eventId the name/id of the event @param param the parameter key @param value the parameter value @param timed true if the event should be timed, false otherwise @return the error code */ virtual SInt32 LogEvent(const String& eventId, const String& param, const String& value, const Bool timed=false) = 0; /** @brief Log a optionally timed event with parameters with the Flurry service. The event is identified by the eventId, which is a String. A Map<String, String> of parameters can be passed in where the key is the parameter name, and the value is the value. Only up to 10 parameters can be passed in with the Map. If more than 10 parameters are passed, all parameters will be dropped and the event will be logged as if it had no parameters. @param eventId the name/id of the event @param parameters A Map<String, String> of the parameters which should be submitted with this event. @return the error code */ virtual SInt32 LogEvent(const String& eventId, const Map<String, String>& parameters, const Bool timed=false) = 0; /** @brief End a timed event. @param eventId the name/id of the event to end the timer on */ virtual void EndTimedEvent(const String& eventId) = 0; /** End a timed event with one parameter. Only up to 10 unique parameters total can be passed for an event, including those passed when the event was initiated. If more than 10 unique parameters total are passed, all parameter updates passed on this endTimedEvent call will be ignored. @param eventId the name/id of the event to end the timer on @param param the parameter key @param value the parameter value */ virtual void EndTimedEvent(const String& eventId, const String& param, const String& value) = 0; /** End a timed event with parameters. Only up to 10 unique parameters total can be passed for an event, including those passed when the event was initiated. If more than 10 unique parameters total are passed, all parameter updates passed on this endTimedEvent call will be ignored. @param eventId the name/id of the event to end the timer on @param parameters A Map<String, String> of the parameters which should be submitted with this event. */ virtual void EndTimedEvent(const String& eventId, const Map<String, String>& parameters) = 0; /** Use OnError to report errors that your application catches. Flurry will report the first 10 errors to occur in each session. @param errorId The name/id of the error. @param message The error message. */ virtual void OnError(const String& errorId, const String& message) = 0; /** Use onPageView to report page view count. To increment the total count, you should call this method whenever a new page is shown to the user. */ virtual void OnPageView() = 0; }; } } #endif // __FLURRY_CONTROL_H__
Wie zuvor deklarieren wir eine statische Create
Methode und einige pure virtual Methoden um Events, Fehler und Page-Views zu loggen. Der Funktionsparameter timed
wird als Default-Parameter mit dem Standardwert false
definiert. Die Angabe dieses Parameters ist beim Aufruf der Methode daher optional.
Default Implementierung
Als nächstes implementieren wir eine Default-Implementierung (FlurryControlDefault
), die lediglich eine passende Debug Meldung ausgibt.
source/flurry/flurry_control_default.h
source/flurry/flurry_control_default.cpp
#include "flurry_control.h" namespace Murl { namespace App { class FlurryControlDefault : public FlurryControl { public: static FlurryControl* Create(const String& name); FlurryControlDefault(const String& name) : FlurryControl(name) {} virtual ~FlurryControlDefault() {} virtual SInt32 LogEvent(const String& eventId, const Bool timed); virtual SInt32 LogEvent(const String& eventId, const String& param, const String& value, const Bool timed); virtual SInt32 LogEvent(const String& eventId, const Map<String, String>& parameters, const Bool timed); virtual void EndTimedEvent(const String& eventId); virtual void EndTimedEvent(const String& eventId, const String& param, const String& value); virtual void EndTimedEvent(const String& eventId, const Map<String, String>& parameters); virtual void OnError(const String& errorId, const String& message); virtual void OnPageView(); }; } }
// Copyright 2015 Spraylight #include "flurry_control_default.h" using namespace Murl; App::FlurryControl* App::FlurryControl::Create(const String& name) { return new App::FlurryControlDefault(name); } SInt32 App::FlurryControlDefault::LogEvent(const String& eventId, const Bool timed) { MURL_TRACE(0, "%s, timed=%s", eventId.Begin(), timed ? "TRUE" : "FALSE"); return FlurryEventRecorded; } SInt32 App::FlurryControlDefault::LogEvent(const String& eventId, const String& param, const String& value, const Bool timed) { MURL_TRACE(0, "%s, %s=%s, timed=%s", eventId.Begin(), param.Begin(), value.Begin(), timed ? "TRUE" : "FALSE"); return FlurryEventRecorded; } SInt32 App::FlurryControlDefault::LogEvent(const String& eventId, const Map<String, String>& parameters, const Bool timed) { MURL_TRACE(0, "%s, parametersCount=%d, timed=%s", eventId.Begin(), parameters.GetCount(), timed ? "TRUE" : "FALSE"); SInt32 size = parameters.GetCount(); for (SInt32 i = 0; i < size; i++) { const String& key = parameters.GetKey(i); const String& value = parameters[i]; MURL_TRACE(0, "parameter %d, %s=%s", i, key.Begin(), value.Begin()); } return FlurryEventRecorded; } void App::FlurryControlDefault::EndTimedEvent(const String& eventId) { MURL_TRACE(0, "%s", eventId.Begin()); } void App::FlurryControlDefault::EndTimedEvent(const String& eventId, const String& param, const String& value) { MURL_TRACE(0, "%s, %s=%s", eventId.Begin(), param.Begin(), value.Begin()); } void App::FlurryControlDefault::EndTimedEvent(const String& eventId, const Map<String, String>& parameters) { MURL_TRACE(0, "%s, parametersCount=%d", eventId.Begin(), parameters.GetCount()); SInt32 size = parameters.GetCount(); for (SInt32 i = 0; i < size; i++) { const String& key = parameters.GetKey(i); const String& value = parameters[i]; MURL_TRACE(0, "parameter %d, %s=%s", i, key.Begin(), value.Begin()); } } void App::FlurryControlDefault::OnError(const String& errorId, const String& message) { MURL_TRACE(0, "%s, %s", errorId.Begin(), message.Begin()); } void App::FlurryControlDefault::OnPageView() { MURL_TRACE(0, ""); }
Logik-Code
Mit der Default-Implementierung können wir ein FlurryControl
Objekt instanzieren und in unserer App verwenden.
#include "murl_app_types.h" #include "murl_logic_base_processor.h" #include "flurry/flurry_control.h" namespace Murl { namespace App { class FlurryLogic : public Logic::BaseProcessor { public: FlurryLogic(Logic::IFactory* factory); virtual ~FlurryLogic(); protected: virtual Bool OnInit(const Logic::IState* state); virtual Bool OnDeInit(const Logic::IState* state); virtual void OnProcessTick(const Logic::IState* state); void TestResult(SInt32 result); Logic::ButtonNode mBtn01; Logic::ButtonNode mBtn02; FlurryControl* mFlurryControl; }; } }
App::FlurryLogic::FlurryLogic(Logic::IFactory* factory) : BaseProcessor(factory) , mFlurryControl(0) { } App::FlurryLogic::~FlurryLogic() { } Bool App::FlurryLogic::OnInit(const Logic::IState* state) { state->GetLoader()->UnloadPackage("startup"); Graph::IRoot* root = state->GetGraphRoot(); AddGraphNode(mBtn01.GetReference(root, "b1/button")); AddGraphNode(mBtn02.GetReference(root, "b2/button")); if (!AreGraphNodesValid()) { return false; } Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler(); Output::IDeviceHandler* outputDeviceHandler = deviceHandler->GetOutputDeviceHandler(); // create Flurry control instance mFlurryControl = FlurryControl::Create("FlurryControl"); // add Flurry control to outputDeviceHandler if (!outputDeviceHandler->AddCustomControl(mFlurryControl)) { return false; } state->SetUserDebugMessage("Flurry Init succeeded!"); return true; } Bool App::FlurryLogic::OnDeInit(const Logic::IState* state) { Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler(); Output::IDeviceHandler* outputDeviceHandler = deviceHandler->GetOutputDeviceHandler(); // remove Flurry control from outputDeviceHandler if (!outputDeviceHandler->RemoveCustomControl(mFlurryControl)) { return false; } return true; } void App::FlurryLogic::OnProcessTick(const Logic::IState* state) { Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler(); String prefix = "test log event "; if (deviceHandler->WasRawKeyPressed(RAWKEY_1) || mBtn01->WasReleasedInside()) { Debug::Trace("Start Test"); SInt32 result; result = mFlurryControl->LogEvent(prefix + "001"); TestResult(result); result = mFlurryControl->LogEvent(prefix + "002", "param 01", "value 01"); TestResult(result); Map<String, String> parameters; parameters.Put("paramA", "valueA"); parameters.Put("paramB", "valueB"); parameters.Put("paramC", "valueC"); result = mFlurryControl->LogEvent(prefix + "003", parameters); TestResult(result); result = mFlurryControl->LogEvent(prefix + "t01", true); TestResult(result); result = mFlurryControl->LogEvent(prefix + "t02", "param 01","value 01", true); TestResult(result); result = mFlurryControl->LogEvent(prefix + "t03", parameters, true); TestResult(result); mFlurryControl->OnError("error 01", "test error"); mFlurryControl->OnPageView(); } if (deviceHandler->WasRawKeyPressed(RAWKEY_2) || mBtn02->WasReleasedInside()) { Debug::Trace("End timed Events"); mFlurryControl->EndTimedEvent(prefix + "t01"); mFlurryControl->EndTimedEvent(prefix + "t02", "param 02", "value 02"); Map<String, String> parameters; parameters.Put("paramD", "valueD"); parameters.Put("paramE", "valueE"); mFlurryControl->EndTimedEvent(prefix + "t03", parameters); } // Exit if (deviceHandler->WasRawKeyPressed(RAWKEY_ESCAPE) || deviceHandler->WasRawButtonPressed(RAWBUTTON_BACK)) { deviceHandler->TerminateApp(); } } void App::FlurryLogic::TestResult(SInt32 result) { if (result != App::FlurryControl::FlurryEventRecorded) Debug::Trace("Failed %d", result); }
Die Default-Implementierung liefert folgende Ausgabe.
Start Test Murl::App::FlurryControlDefault::LogEvent(), line 14: test log event 001, timed=FALSE Murl::App::FlurryControlDefault::LogEvent(), line 20: test log event 002, param 01=value 01, timed=FALSE Murl::App::FlurryControlDefault::LogEvent(), line 26: test log event 003, parametersCount=3, timed=FALSE Murl::App::FlurryControlDefault::LogEvent(), line 33: parameter 0, paramA=valueA Murl::App::FlurryControlDefault::LogEvent(), line 33: parameter 1, paramB=valueB Murl::App::FlurryControlDefault::LogEvent(), line 33: parameter 2, paramC=valueC Murl::App::FlurryControlDefault::LogEvent(), line 14: test log event t01, timed=TRUE Murl::App::FlurryControlDefault::LogEvent(), line 20: test log event t02, param 01=value 01, timed=TRUE Murl::App::FlurryControlDefault::LogEvent(), line 26: test log event t03, parametersCount=3, timed=TRUE Murl::App::FlurryControlDefault::LogEvent(), line 33: parameter 0, paramA=valueA Murl::App::FlurryControlDefault::LogEvent(), line 33: parameter 1, paramB=valueB Murl::App::FlurryControlDefault::LogEvent(), line 33: parameter 2, paramC=valueC Murl::App::FlurryControlDefault::OnError(), line 62: error 01, test error Murl::App::FlurryControlDefault::OnPageView(), line 67: End timed Events Murl::App::FlurryControlDefault::EndTimedEvent(), line 40: test log event t01 Murl::App::FlurryControlDefault::EndTimedEvent(), line 45: test log event t02, param 02=value 02 Murl::App::FlurryControlDefault::EndTimedEvent(), line 50: test log event t03, parametersCount=2 Murl::App::FlurryControlDefault::EndTimedEvent(), line 56: parameter 0, paramD=valueD Murl::App::FlurryControlDefault::EndTimedEvent(), line 56: parameter 1, paramE=valueE
Android Version
Im nächsten Schritt integrieren wir das "Flurry Analytics Android SDK" für die Android Version unserer App. Dafür sind folgende Schritte notwendig.
JAR-Archiv hinzufügen
Zunächst müssen wir das JAR-Archiv FlurryAnalytics_5.3.0.jar
zu unserem Projekt hinzufügen.
Wir speichern das JAR-Archiv in einem eigenen Verzeichnis android/jar
.
source/android/jar/FlurryAnalytics-5.3.0.jar
- Zu beachten
- Im Makefile kann nur eine Pfadangabe für JAR-Dateien gemacht werden. Es ist daher sinnvoll ein eigenes Verzeichnis dafür anzulegen und alle JAR-Archive dort zu speichern. Dasselbe gilt für Java Files, Proguard Fragmente, Manifest Fragmente etc.
Die Datei listen wir mit MURL_ANDROID_JAR_PATH
und MURL_ANDROID_JAR_FILES
im Common Makefile module_flurry.mk
auf. Zusätzlich definieren wir die notwendigen Berechtigungen für das Flurry SDK.
Initialisieren
Das Flurry SDK fordert, dass der FlurryAgent
in den App-Methoden onCreate()
, onStart()
und onStop()
passend aufgerufen werden muss. Das Interface MurlCustomControl
kann verwendet werden, um Callbacks auf diese App-Methoden zu erhalten.
Wir erstellen also die Java-Klasse FlurryControl
und implementieren das Interface MurlCustomControl
. Für die Konfiguration des Flurry SDKs erstellen wir auch noch die Klasse FlurryConfiguration
.
source/android/java/at/spraylight/flurry/FlurryControl.java
source/android/java/at/spraylight/flurry/FlurryConfiguration.java
// Copyright 2015 Spraylight GmbH package at.spraylight.flurry; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import at.spraylight.murl.MurlCustomControl; import com.flurry.android.FlurryAgent; public class FlurryControl implements MurlCustomControl { private final Activity mActivity; public FlurryControl(Activity activity) { mActivity = activity; } @Override public void onCreate(Bundle savedInstanceState) { Log.d(FlurryConfiguration.LOG_TAG, "FlurryControl::onCreate(): Init Session"); // **** CONFIGURE FLURRY *** FlurryConfiguration.configure(mActivity); // **** INIT FLURRY *** FlurryAgent.init(mActivity, FlurryConfiguration.API_Key); } @Override public void onStart() { Log.d(FlurryConfiguration.LOG_TAG, "FlurryControl::onStart(): Start Session"); FlurryAgent.onStartSession(mActivity); } @Override public void onStop() { Log.d(FlurryConfiguration.LOG_TAG, "FlurryControl::onStop(): End Flurry session"); FlurryAgent.onEndSession(mActivity); } @Override public void onRestart() { } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { } @Override public void onResume() { } @Override public void onPause() { } @Override public void onSaveInstanceState(Bundle outState) { } @Override public void onDestroy() { } }
package at.spraylight.flurry; import android.app.Activity; import android.util.Log; import com.flurry.android.FlurryAgent; /** Limits to the number of Events and Parameters: 300 Events for each app. Each event can have up to 10 parameters, and each parameter can have any number of values. */ public class FlurryConfiguration { public static final String LOG_TAG = "Flurry"; // Enter your own key here: public static final String API_Key = "XXXXXXXXXXXXXXXXXXXX"; /** This method is called before init. */ public static final void configure(Activity activity) { // Use setLogEnabled to enable/disable internal Flurry SDK logging. This should be called before init. FlurryAgent.setLogEnabled(false); // Sets the log level of the internal Flurry SDK logging. Valid inputs are Log.VERBOSE, Log.WARN etc. Default log level is Log.WARN. This should be called before init. FlurryAgent.setLogLevel(Log.DEBUG); //Use setLogEvents to enable/disable the event logging. This should be called before init. FlurryAgent.setLogEvents(true); // To disable detailed location reporting even when your app has permission //FlurryAgent.setReportLocation(true); //Use this to log the user's assigned ID or username in your system. This should be called before init, if possible. //FlurryAgent.setUserID(String); //Use this to log the user's age. Valid inputs are between 1 and 109. This should be called before init, if possible. //FlurryAgent.setAge(int); //Use this to log the user's gender. Valid inputs are Constants.MALE or Constants.FEMALE. This should be called before init, if possible. //FlurryAgent.setGender(Constants.MALE); //Sets the version name of the app. Flurry automatically uses the versionName attribute from manifest if not set! This name will appear in the https://dev.flurry.com as a filtering option by version. This should be called before init. // FlurryAgent.setVersionName(versionName); // Get the version of the Flurry SDK. // FlurryAgent.getAgentVersion(); // Get the release version of the Flurry SDK. // FlurryAgent.getReleaseVersion(); } }
Wir listen die neuen Dateien wieder im Makefile auf und registrieren die neue Klasse als MurlCustomControl
Klasse.
JNI-Bridge
Als nächstes definieren wir unsere JNI-Bridge Klasse mit den statischen Java-Methoden, die von C++ aus aufgerufen werden können.
source/android/java/at/spraylight/flurry/FlurryJniBridge.java
// Copyright 2015 Spraylight GmbH package at.spraylight.flurry; import java.util.HashMap; import java.util.Map; import android.util.Log; import com.flurry.android.FlurryAgent; import com.flurry.android.FlurryEventRecordStatus; public class FlurryJniBridge { static Map<String, Map<String,String>> mapContainer = new HashMap<String, Map<String,String>>(); // JNI from C++ to Java /** Log a possibly timed event with the Flurry service. The method logEventPrepare can be used to add parameters to the event. Only up to 10 unique parameters total can be passed for an event. If more than 10 parameters are passed, all parameters will be dropped and the event will be logged as if it had no parameters. To end the timer, call endTimedEvent(String) with this eventId. @param eventId the name/id of the event @param timed true if the event should be timed, false otherwise @return the error code 0: FlurryEventFailed 1: FlurryEventRecorded 2: FlurryEventUniqueCountExceeded 3: FlurryEventParamsCountExceeded 4: FlurryEventLogCountExceeded 5: FlurryEventLoggingDelayed */ public static int logEvent(String eventId, boolean timed) { Log.d(FlurryConfiguration.LOG_TAG, "FlurryJniBridge::logEvent(): eventId=" + eventId + " timed=" + timed); Map<String, String> parameters = mapContainer.remove(eventId); FlurryEventRecordStatus status = (parameters == null) ? FlurryAgent.logEvent(eventId, timed) : FlurryAgent.logEvent(eventId, parameters, timed); return status.ordinal(); } /** Log a possibly timed event with one parameter with the Flurry service. To end the timer, call endTimedEvent(String) with this eventId. @param eventId the name/id of the event @param param the parameter key @param value the parameter value @param timed true if the event should be timed, false otherwise @return the error code 0: FlurryEventFailed 1: FlurryEventRecorded 2: FlurryEventUniqueCountExceeded 3: FlurryEventParamsCountExceeded 4: FlurryEventLogCountExceeded 5: FlurryEventLoggingDelayed */ public static int logEvent(String eventId, String param, String value, boolean timed) { Log.d(FlurryConfiguration.LOG_TAG, "FlurryJniBridge::logEvent(): eventId=" + eventId + " param=" + param + " value=" + value + " timed=" + timed); Map<String, String> parameters = new HashMap<String, String>(2, 1); parameters.put(param, value); FlurryEventRecordStatus status = FlurryAgent.logEvent(eventId, parameters, timed); return status.ordinal(); } /** Prepare parameters for a logEvent or endTimedEvent execution. The data is collected in a Map. */ public static void logEventPrepare(String eventId, String param, String value) { Map<String, String> parameters = mapContainer.get(eventId); if (parameters == null) { parameters = new HashMap<String, String>(); mapContainer.put(eventId, parameters); } parameters.put(param, value); } /** End a timed event. The method logEventPrepare can be used to add up to 10 parameters to the event. Only up to 10 unique parameters total can be passed for an event, including those passed when the event was initiated. If more than 10 unique parameters total are passed, all parameter updates passed on this endTimedEvent call will be ignored. @param eventId the name/id of the event to end the timer on */ public static void endTimedEvent(String eventId) { Log.d(FlurryConfiguration.LOG_TAG, "FlurryJniBridge::endTimedEvent(): eventId=" + eventId); Map<String, String> parameters = mapContainer.remove(eventId); if (parameters == null) { FlurryAgent.endTimedEvent(eventId); } else { FlurryAgent.endTimedEvent(eventId, parameters); } } /** End a timed event with one parameter. Only up to 10 unique parameters total can be passed for an event, including those passed when the event was initiated. If more than 10 unique parameters total are passed, all parameter updates passed on this endTimedEvent call will be ignored. @param eventId the name/id of the event to end the timer on @param param the parameter key @param value the parameter value */ public static void endTimedEvent(String eventId, String param, String value) { Log.d(FlurryConfiguration.LOG_TAG, "FlurryJniBridge::endTimedEvent(): eventId=" + eventId+", "+param+"="+value); Map<String, String> parameters = new HashMap<String, String>(2, 1); parameters.put(param, value); FlurryAgent.endTimedEvent(eventId, parameters); } /** Use onError to report errors that your application catches. Flurry will report the first 10 errors to occur in each session. @param errorId The name/id of the error. @param message The error message. */ public static void onError(String errorId, String message) { Log.d(FlurryConfiguration.LOG_TAG, "FlurryJniBridge::onError(): errorId=" + errorId+", "+message); Throwable th = new Exception(""); FlurryAgent.onError(message, message, th); } /** Use onPageView to report page view count. To increment the total count, you should call this method whenever a new page is shown to the user. */ public static void onPageView() { Log.d(FlurryConfiguration.LOG_TAG, "FlurryJniBridge::onPageView()"); FlurryAgent.onPageView(); } }
Wir listen die neue Datei im Common Makefile und registrieren die Klasse als JNI-Klasse.
CPP Implementierung
Nun können wir die Android Implementierung in C++ für unser FlurryControl
erstellen und die entsprechenden JNI-Bridge Methoden aufrufen.
#include "flurry_control.h" #include "murl_platform_android_native_platform.h" #include "murl_platform_android_jni_bridge.h" namespace Murl { namespace App { class FlurryControlAndroid : public FlurryControl { public: static FlurryControl* Create(const String& name); FlurryControlAndroid(const String& name) : FlurryControl(name), mJniBridge(0) {} virtual ~FlurryControlAndroid() {} virtual SInt32 LogEvent(const String& eventId, const Bool timed); virtual SInt32 LogEvent(const String& eventId, const String& param, const String& value, const Bool timed); virtual SInt32 LogEvent(const String& eventId, const Map<String, String>& parameters, const Bool timed); virtual void EndTimedEvent(const String& eventId); virtual void EndTimedEvent(const String& eventId, const String& param, const String& value); virtual void EndTimedEvent(const String& eventId, const Map<String, String>& parameters); virtual void OnError(const String& errorId, const String& message); virtual void OnPageView(); protected: virtual Bool Init(IPlatform* platform); virtual Bool DeInit(); Platform::Android::JniBridge* mJniBridge; }; } }
// Copyright 2015 Spraylight #include "flurry_control_android.h" using namespace Murl; App::FlurryControl* App::FlurryControl::Create(const String& name) { return new App::FlurryControlAndroid(name); } SInt32 App::FlurryControlAndroid::LogEvent(const String& eventId, const Bool timed) { MURL_TRACE(0, "%s, timed=%d", eventId.Begin(), timed); SInt32 ret; mJniBridge->CallStaticJavaIntFunc<jstring, jboolean>("FlurryJniBridge.logEvent", ret, eventId, timed); return ret; } SInt32 App::FlurryControlAndroid::LogEvent(const String& eventId, const String& param, const String& value, const Bool timed) { MURL_TRACE(0, "%s, %s=%s, timed=%d", eventId.Begin(), param.Begin(), value.Begin(), timed); SInt32 ret; mJniBridge->CallStaticJavaIntFunc<jstring, jstring, jstring, jboolean>("FlurryJniBridge.logEvent", ret, eventId, param, value, timed); return ret; } SInt32 App::FlurryControlAndroid::LogEvent(const String& eventId, const Map<String, String>& parameters, const Bool timed) { MURL_TRACE(0, "%s, parametersCount=%d, timed=%s", eventId.Begin(), parameters.GetCount(), timed ? "TRUE" : "FALSE"); SInt32 size = parameters.GetCount(); for (SInt32 i = 0; i < size; i++) { const String& key = parameters.GetKey(i); const String& value = parameters[i]; mJniBridge->CallStaticJavaProc<jstring, jstring, jstring>("FlurryJniBridge.logEventPrepare", eventId, key, value); } SInt32 ret; mJniBridge->CallStaticJavaIntFunc<jstring, jboolean>("FlurryJniBridge.logEvent", ret, eventId, timed); return FlurryEventRecorded; } void App::FlurryControlAndroid::EndTimedEvent(const String& eventId) { MURL_TRACE(0, "%s", eventId.Begin()); mJniBridge->CallStaticJavaProc<jstring>("FlurryJniBridge.endTimedEvent", eventId); } void App::FlurryControlAndroid::EndTimedEvent(const String& eventId, const String& param, const String& value) { MURL_TRACE(0, "%s, %s=%s", eventId.Begin(), param.Begin(), value.Begin()); mJniBridge->CallStaticJavaProc<jstring, jstring, jstring>("FlurryJniBridge.endTimedEvent", eventId, param, value); } void App::FlurryControlAndroid::EndTimedEvent(const String& eventId, const Map<String, String>& parameters) { MURL_TRACE(0, "%s, parametersCount=%d", eventId.Begin(), parameters.GetCount()); SInt32 size = parameters.GetCount(); for (SInt32 i = 0; i < size; i++) { const String& key = parameters.GetKey(i); const String& value = parameters[i]; mJniBridge->CallStaticJavaProc<jstring, jstring, jstring>("FlurryJniBridge.logEventPrepare", eventId, key, value); } mJniBridge->CallStaticJavaProc<jstring>("FlurryJniBridge.endTimedEvent", eventId); } void App::FlurryControlAndroid::OnError(const String& errorId, const String& message) { MURL_TRACE(0, "%s, %s", errorId.Begin(), message.Begin()); mJniBridge->CallStaticJavaProc<jstring, jstring>("FlurryJniBridge.onError", errorId, message); } void App::FlurryControlAndroid::OnPageView() { mJniBridge->CallStaticJavaProc("FlurryJniBridge.onPageView"); MURL_TRACE(0, ""); } // ************************** INIT / DEINIT ***************************************************** Bool App::FlurryControlAndroid::Init(IPlatform* platform) { Platform::Android::NativePlatform* nativePlatform = dynamic_cast<Platform::Android::NativePlatform*>(platform); if (nativePlatform == 0) { MURL_TRACE(0, "Failed to get Android platform handle."); return false; } mJniBridge = nativePlatform->GetJniBridge(); if (mJniBridge == 0) { MURL_TRACE(0, "Failed to get JNI bridge."); return false; } return true; } Bool App::FlurryControlAndroid::DeInit() { mJniBridge = 0; return true; }
Wir listen die neue Datei im Makefile als SRC-File, wobei abhängig von der Plattform die Android- oder die Default-Version verwendet wird.
Damit können wir eine Android-Version unserer App erstellen, die das Flurry Analytics SDK verwendet. Die Android Implementierung liefert folgende Ausgabe im Android Device Monitor:
D/Flurry(12945): FlurryControl::onCreate(): Init Session D/Flurry(12945): FlurryControl::onStart(): Start Session D/Flurry(12945): FlurryJniBridge::logEvent(): eventId=test log event 001 timed=false D/Flurry(12945): FlurryJniBridge::logEvent(): eventId=test log event 002 param=param 01 value=value 01 timed=false D/Flurry(12945): FlurryJniBridge::logEvent(): eventId=test log event 003 timed=false D/Flurry(12945): FlurryJniBridge::logEvent(): eventId=test log event t01 timed=true D/Flurry(12945): FlurryJniBridge::logEvent(): eventId=test log event t02 param=param 01 value=value 01 timed=true D/Flurry(12945): FlurryJniBridge::logEvent(): eventId=test log event t03 timed=true D/Flurry(12945): FlurryJniBridge::onError(): errorId=error 01, test error D/Flurry(12945): FlurryJniBridge::onPageView() D/Flurry(12945): FlurryJniBridge::endTimedEvent(): eventId=test log event t01 D/Flurry(12945): FlurryJniBridge::endTimedEvent(): eventId=test log event t02, param 02=value 02 D/Flurry(12945): FlurryJniBridge::endTimedEvent(): eventId=test log event t03 D/Flurry(12945): FlurryControl::onStop(): End Flurry session
Die Events sollten auch im Flurry Analytics Dashboard aufscheinen. Das kann allerdings bis zu 24h dauern.
Proguard
Für einen Release-Build müssen wir noch Proguard über eine Textdatei konfigurieren.
source/android/proguard/proguard_flurry.txt
Die Textdatei wird ebenfalls im Makefile gelistet.
- Zu beachten
- Die Verwendung der "Google Play Service Library" und der Android "v4/v7 Support Library" ist für Analytics nicht notwendig. Wenn gewünscht, können auch diese beiden Bibliotheken dem Projekt hinzugefügt werden.
iOS Version
Um das Flurry Analytics SDK für die iOS Version zu implementieren, sind folgende Schritte notwendig.
SDK
Zunächst müssen wir die Flurry iOS SDK Dateien dem iOS Projekt hinzufügen. Dafür kopieren wir diese in das Verzeichnis ios
.
source/ios/Flurry.h
source/ios/libFlurry_6.3.0.a
Die Dateien können am einfachsten mit dem Dashboard und dem Befehl "Update Project" hinzugefügt werden. In Xcode muss dann noch die Bibliothek zu den Targets hinzugefügt werden:
- Target Membership für die Datei
libFlurry_6.3.0.a
hinzufügen
Bibliotheken
Das Flurry SDK benötigt folgende iOS System Bibliotheken:
- Security.framework
- SystemConfiguration.framework
Diese können mit
- Targets -> Build Phases -> Link Binary With Libraries -> Add
dem Projekt hinzugefügt werden können.
CPP Implementierung
Nun können wir die iOS Implementierung in C++ bzw. Obj-C für unser FlurryControl
erstellen.
source/flurry/flurry_control_ios.h
source/flurry/flurry_control_ios.mm
#include "flurry_control.h" namespace Murl { namespace App { class FlurryControliOS : public FlurryControl { public: static FlurryControl* Create(const String& name); FlurryControliOS(const String& name) : FlurryControl(name) {} virtual ~FlurryControliOS() {} virtual SInt32 LogEvent(const String& eventId, const Bool timed); virtual SInt32 LogEvent(const String& eventId, const String& param, const String& value, const Bool timed); virtual SInt32 LogEvent(const String& eventId, const Map<String, String>& parameters, const Bool timed); virtual void EndTimedEvent(const String& eventId); virtual void EndTimedEvent(const String& eventId, const String& param, const String& value); virtual void EndTimedEvent(const String& eventId, const Map<String, String>& parameters); virtual void OnError(const String& errorId, const String& message); virtual void OnPageView(); protected: virtual Bool AppFinishLaunching(void* launchOptions); void Configure(); }; } }
// Copyright 2015 Spraylight #include "flurry_control_ios.h" #include "../ios/Flurry.h" #import <Foundation/Foundation.h> using namespace Murl; App::FlurryControl* App::FlurryControl::Create(const String& name) { return new App::FlurryControliOS(name); } Murl::SInt32 App::FlurryControliOS::LogEvent(const String& eventId, const Bool timed) { NSString* nsString = [NSString stringWithUTF8String : eventId.Begin()]; SInt32 ret = [Flurry logEvent : nsString timed:timed]; return ret; } Murl::SInt32 App::FlurryControliOS::LogEvent(const String& eventId, const String& param, const String& value, const Bool timed) { NSString* nsString = [NSString stringWithUTF8String : eventId.Begin()]; NSDictionary *nsDictionary = [NSDictionary dictionaryWithObjectsAndKeys : [NSString stringWithUTF8String :value.Begin()], [NSString stringWithUTF8String : param.Begin()], nil]; SInt32 ret = [Flurry logEvent : nsString withParameters: nsDictionary timed: timed]; return ret; } Murl::SInt32 App::FlurryControliOS::LogEvent(const String& eventId, const Map<String, String>& parameters, const Bool timed) { SInt32 size = parameters.GetCount(); if (size == 0) { return LogEvent(eventId, timed); } NSString* nsString = [NSString stringWithUTF8String : eventId.Begin()]; NSMutableDictionary *nsDictionary = [NSMutableDictionary dictionary]; for (SInt32 i = 0; i < size; i++) { const String& key = parameters.GetKey(i); const String& value = parameters[i]; [nsDictionary setObject : [NSString stringWithUTF8String : value.Begin()] forKey : [NSString stringWithUTF8String : key.Begin()]]; } SInt32 ret = [Flurry logEvent : nsString withParameters: nsDictionary timed: timed]; return ret; } void App::FlurryControliOS::EndTimedEvent(const String& eventId) { NSString* nsString = [NSString stringWithUTF8String : eventId.Begin()]; [Flurry endTimedEvent: nsString withParameters: nil]; } void App::FlurryControliOS::EndTimedEvent(const String& eventId, const String& param, const String& value) { NSString* nsString = [NSString stringWithUTF8String : eventId.Begin()]; NSDictionary *nsDictionary = [NSDictionary dictionaryWithObjectsAndKeys : [NSString stringWithUTF8String :value.Begin()], [NSString stringWithUTF8String : param.Begin()], nil]; [Flurry endTimedEvent: nsString withParameters: nsDictionary]; } void App::FlurryControliOS::EndTimedEvent(const String& eventId, const Map<String, String>& parameters) { SInt32 size = parameters.GetCount(); if (size == 0) { return EndTimedEvent(eventId); } NSString* nsString = [NSString stringWithUTF8String : eventId.Begin()]; NSMutableDictionary *nsDictionary = [NSMutableDictionary dictionary]; for (SInt32 i = 0; i < size; i++) { const String& key = parameters.GetKey(i); const String& value = parameters[i]; [nsDictionary setObject : [NSString stringWithUTF8String : value.Begin()] forKey : [NSString stringWithUTF8String : key.Begin()]]; } [Flurry endTimedEvent: nsString withParameters: nsDictionary]; } void App::FlurryControliOS::OnError(const String& errorId, const String& message) { NSString* nsStringErr = [NSString stringWithUTF8String : errorId.Begin()]; NSString* nsStringMsg = [NSString stringWithUTF8String : message.Begin()]; NSDictionary *nsDictionary = @{ NSLocalizedDescriptionKey : nsStringMsg }; NSError* nsError = [NSError errorWithDomain:@"com.MyCompany.MyApplication.ErrorDomain" code:100 userInfo:nsDictionary]; [Flurry logError:nsStringErr message:nsStringMsg error:nsError]; } void App::FlurryControliOS::OnPageView() { [Flurry logPageView]; } // ************************** INIT / DEINIT ***************************************************** Bool App::FlurryControliOS::AppFinishLaunching(void* launchOptions) { Configure(); // enter your own key here [Flurry startSession:@"XXXXXXXXXXXXXXXXXXXX"]; return true; } void App::FlurryControliOS::Configure() { //Retrieves the Flurry Agent Build Version. //(NSString *) + getFlurryAgentVersion NSString* nsString = [Flurry getFlurryAgentVersion]; String s([nsString UTF8String]); Debug::Trace("Flurry Agent Version %s", s.Begin()); //Generates debug logs to console. //This is an optional method that displays debug information related to the Flurry SDK. display information to the console. //The default setting for this method is NO which sets the log level to FlurryLogLevelCriticalOnly. //When set to YES the debug log level is set to FlurryLogLevelDebug // (void) setDebugLogEnabled: (BOOL) value [Flurry setDebugLogEnabled: true]; //Generates debug logs to console. //This is an optional method that displays debug information related to the Flurry SDK to the console. //The default setting for this method is FlurryLogLevelCritycalOnly. // (void) setLogLevel: (FlurryLogLevel) value [Flurry setLogLevel: FlurryLogLevelAll]; //Enable automatic collection of crash reports. //This is an optional method that collects crash reports when enabled. The default value is NO. //(void) setCrashReportingEnabled: (BOOL) value [Flurry setCrashReportingEnabled: true]; //Use this method to allow the capture of custom events. The default value is YES. //(void) setEventLoggingEnabled: (BOOL) value //Explicitly specifies the App Version that Flurry will use to group Analytics data. //(void) setAppVersion: (NSString *) version //Assign a unique id for a user in your app. //(void) setUserID: (NSString *) userID //Use this method to capture the gender of your user. //Only use this method if you collect this information explictly from your user (i.e. - there is no need to set a default value). //Allowable values are "m" or @c @"f" // (void) setGender: (NSString *) gender //Use this method to capture the age of your user. //Only use this method if you collect this information explictly from your user (i.e. - there is no need to set a default value). // (void) setAge: (int) age //Set the location of the session. //(void) setLatitude: (double) latitude longitude: (double) longitude horizontalAccuracy: (float) horizontalAccuracy verticalAccuracy: (float) verticalAccuracy }
Wir fügen die neuen Dateien dem Projekt hinzu und passen die "Target Membership" an:
- Target Membership für die Datei
flurry_control_ios.mm
hinzufügen - Target Membership für die Dateien
flurry_control_android.cpp
entfernen - Target Membership für die Dateien
flurry_control_default.cpp
entfernen
Damit ist die iOS Implementierung abgeschlossen und die Events werden mit Flurry Analytics aufgezeichnet. Es kann bis zu 24h dauern, bis die Events im Dashboard aufscheinen.