Μετάβαση στο κύριο περιεχόμενο

Ενσωμάτωση Android SDK

Εγγενές Android SDK για παρακολούθηση συμβάντων, διαχείριση συνεδριών χρηστών, αποστολή push notifications μέσω FCM και εμφάνιση μηνυμάτων εντός εφαρμογής.

Δυνατότητες​

  • Ελαφρύ - Ελάχιστο αποτύπωμα
  • Γρήγορο - Βελτιστοποιημένο για απόδοση
  • Υποστήριξη εκτός σύνδεσης - Ουρά συμβάντων βασισμένη σε SQLite
  • Αυτόματη επανάληψη - Exponential backoff σε αποτυχίες
  • Ομαδοποίηση - Αποτελεσματική ομαδοποίηση συμβάντων (50 συμβάντα / 5 δευτ.)
  • Μηνύματα εντός εφαρμογής - native και HTML μηνύματα, 5 τύποι, με frequency capping
  • Push Notifications - Firebase Cloud Messaging (FCM)
  • Απόρρητο πρώτα - Συμβατό με GDPR, σέβεται τη συγκατάθεση χρήστη
  • Android 6.0+ - Υποστήριξη API 23+

Απαιτήσεις​

  • Android 6.0 (επίπεδο API 23) ή νεότερο - το κατώφλι για κρυπτογραφημένη αποθήκευση (EncryptedSharedPreferences) και το ίδιο ελάχιστο που απαιτεί το Firebase Cloud Messaging
  • Kotlin 1.9.20 ή νεότερο
  • Java 17 για build με το Android Gradle Plugin 8. Οι κλάσεις του SDK είναι bytecode Java 11, οπότε και ένα build με JDK 11 (Unity 2022.3) μεταγλωττίζεται με αυτές
  • Gradle 8.0 ή νεότερο

Τοπική ρύθμιση SDK​

Για τοπικά builds, ορίστε τη διαδρομή Android SDK στο local.properties:

sdk.dir=/Users/your-user/Library/Android/sdk

Εναλλακτικά, εξαγάγετε ANDROID_HOME/ANDROID_SDK_ROOT πριν εκτελέσετε Gradle.

Εγκατάσταση​

Gradle (συνιστάται)​

Προσθέστε στο build.gradle.kts:

Το SDK διατίθεται σε δύο artifacts. Προσθέστε ένα από αυτά - το artifact UI περιέχει το βασικό, οπότε ποτέ δεν δηλώνετε και τα δύο:

dependencies {
// Τα πάντα, μαζί με την εμφάνιση in-app μηνυμάτων. Ξεκινήστε εδώ.
implementation("io.joryio:joryio-android-ui:1.3.3")
}
dependencies {
// Μόνο tracking, ταυτότητα και push - χωρίς in-app απόδοση και χωρίς
// WebView. Επιλέξτε το αν η εφαρμογή δεν χρησιμοποιεί in-app μηνύματα ή αν
// τα αποδίδετε μόνοι σας.
implementation("io.joryio:joryio-android:1.3.3")
}

Ή με Groovy (build.gradle):

dependencies {
implementation 'io.joryio:joryio-android-ui:1.3.3'
}
Ποιο από τα δύο;

Το joryio-android-ui εξαρτάται από το joryio-android, οπότε μία γραμμή σας δίνει και τα δύο και δεν μπορούν να αποκλίνουν - δηλώνετε μόνο μία έκδοση.

Πάρτε μόνο το βασικό artifact όταν η εφαρμογή δεν χρειάζεται in-app μηνύματα ή όταν ένας έλεγχος ασφαλείας αντιτίθεται στη σύνδεση WebView. Τα in-app μηνύματα απλώς δεν θα εμφανίζονται· όλα τα υπόλοιπα λειτουργούν κανονικά.

Γρήγορη εκκίνηση​

1. Αρχικοποιήστε το SDK​

Στην κλάση Application:

import io.joryio.sdk.Joryio
import io.joryio.sdk.JoryioConfig

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

// Initialize with your SDK key
Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_SDK_KEY",
apiHost = "https://api-eu1.joryio.com/api"
)
}
}
Εύρεση του κλειδιού SDK

Βρείτε το κλειδί SDK στο dashboard του Joryio στις Ρυθμίσεις → Εφαρμογές → [Η εφαρμογή σας] → SDK Keys.

2. Παρακολουθήστε συμβάντα​

// Basic event
Joryio.track("Button Tapped")

// Event with properties
Joryio.track("Product Viewed", mapOf(
"product_id" to "abc123",
"product_name" to "Wireless Headphones",
"price" to 99.99,
"category" to "Electronics"
))

// Screen view
Joryio.trackScreen("ProductDetail", mapOf(
"product_id" to "abc123"
))

3. Αναγνωρίστε χρήστες​

Στείλτε τους αριθμούς τηλεφώνου με + και κωδικό χώρας

Στέλνετε πάντα τους αριθμούς τηλεφώνου σε πλήρη διεθνή μορφή (E.164): +, ο κωδικός χώρας και μετά ο αριθμός - για παράδειγμα +972501234567. Ένας αριθμός χωρίς + διαβάζεται με την προεπιλεγμένη χώρα για αριθμούς τηλεφώνου του χώρου εργασίας· αν δεν έχει οριστεί, ή αν τα ψηφία διαβάζονται με δύο τρόπους, αποθηκεύεται ως μη επαληθευμένος και δεν χρησιμοποιείται για μηνύματα. Πώς χειρίζονται οι αριθμοί τηλεφώνου

// Identify a user
Joryio.identify("user-123")
Joryio.getInstance().setAttributes(mapOf(
"email" to "[email protected]",
"name" to "John Doe",
"plan" to "premium"
))

// Set attributes later
Joryio.setAttribute("last_purchase", Date())
Joryio.incrementAttribute("lifetime_value", 99.99)

// Attach another identifier of yours (label: lowercase letters, digits, _).
// Works before identify() too - it follows the visitor through login.
Joryio.addAlias("loyalty_id", "L-12345")

// On logout: forgets the user AND starts a new anonymous ID
Joryio.reset()

Το identify είναι ο τρόπος με τον οποίο ένας ανώνυμος επισκέπτης γίνεται γνωστός χρήστης· δεν υπάρχει ξεχωριστή κλήση alias(userId). Το reset() επίσης διαγράφει τις εγγραφές γνωρισμάτων που δεν έχουν σταλεί ακόμη και όποιο μήνυμα in-app περιμένει να εμφανιστεί, καταχωρίζει ξανά το push token με το νέο ανώνυμο ID και συγχρονίζει ξανά τα μηνύματα in-app, ώστε το επόμενο άτομο στη συσκευή να μην καταγράφεται ποτέ ως το προηγούμενο.

Οι κλήσεις στο Joryio που γίνονται πριν ολοκληρωθεί η αρχικοποίηση μπαίνουν σε ουρά και εκτελούνται με τη σειρά μόλις ολοκληρωθεί, τόσο με το initialize όσο και με το initializeAsync. Καμία δημόσια κλήση δεν πετάει εξαίρεση στον κώδικά σας: ένα όρισμα που λείπει (ένα null από Java) καταγράφεται και αγνοείται. Η μόνη εξαίρεση είναι το Joryio.getInstance() πριν από το initialize() - δείτε παρακάτω.

Αυτόματα δεδομένα συνεδρίας​

Το Android SDK εμπλουτίζει τα συμβάντα Session Start με δεδομένα συσκευής και περιβάλλοντος:

  • $device_id
  • $platform (android)
  • $manufacturer / $model
  • $os_name / $os_version / $os_sdk_int
  • $app_version / $build_number
  • $package_name
  • $screen_width / $screen_height
  • $locale
  • $language / $languages
  • $timezone
  • country (ISO-3166-1 alpha-2, προέρχεται από IP κατά την έναρξη συνεδρίας)

Push Notifications​

Πότε να ρωτήσετε

Ζητήστε την άδεια όταν ο χρήστης έχει λόγο να πει ναι. Μια προτροπή στα πρώτα δευτερόλεπτα μιας συνεδρίας είναι αυτή που απορρίπτεται συχνότερα, και μια άρνηση είναι πολύ δυσκολότερο να αντιστραφεί από μια καθυστέρηση - η προτροπή του συστήματος είναι εφάπαξ, οπότε ένα "όχι" εκεί είναι ουσιαστικά μόνιμο, ενώ μια μεταγενέστερη ερώτηση δεν σας κοστίζει τίποτα.

Το SDK δεν εμφανίζει ποτέ την προτροπή για λογαριασμό σας. Αν έχετε αποφασίσει ότι τη θέλετε στην εκκίνηση, ορίστε το requestPushPermissionAtLaunch - είναι απενεργοποιημένο εξ ορισμού, όχι μη διαθέσιμο.

Ενεργοποιήστε push notifications με Firebase Cloud Messaging (FCM).

1. Προσθέστε Firebase στο έργο σας​

Ακολουθήστε τον οδηγό ρύθμισης Firebase για να προσθέσετε Firebase στο Android έργο σας.

2. Προσθέστε service στο AndroidManifest.xml​

<service
android:name="io.joryio.sdk.push.JoryioFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

3. Εγγραφή για Push Notifications​

import com.google.firebase.messaging.FirebaseMessaging

// Get FCM token and register
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
Joryio.getInstance().registerPushToken(token) { result ->
if (!result.success && result.isRetryable) {
// παροδικό (εκτός σύνδεσης, DNS, 5xx): καταχωρίστε ξανά όταν επανέλθει το δίκτυο
}
if (result.isUnauthorized) {
// λάθος κλειδί SDK - η επανάληψη δεν θα βοηθήσει
}
}
}
}

// Check if push is enabled
val isEnabled = Joryio.getInstance().isPushEnabled()

// Unregister when needed
Joryio.getInstance().unregisterPush()

Περάστε onResult για να μάθετε πώς πήγε: isRetryable σημαίνει παροδική αποτυχία (εκτός σύνδεσης, DNS, 5xx) που αξίζει νέα καταχώριση· isUnauthorized σημαίνει λάθος κλειδί SDK. Χωρίς αυτό η κλήση είναι fire-and-forget. Μια καταχώριση μετράει μόνο όταν την επιβεβαιώσει ο διακομιστής· μια απάντηση success: false αναφέρεται ως αποτυχία και επαναλαμβάνεται στην επόμενη μετάβαση στο προσκήνιο. Μετά το unregisterPush(), η κλήση του registerPushToken() καταχωρίζει ξανά τη συσκευή. Μην περνάτε στο registerPushToken() τις αυτόματες κλήσεις token του Firebase (onNewToken() στη δική σας υπηρεσία μηνυμάτων, το token που λαμβάνεται σε κάθε εκκίνηση) όσο ο χρήστης έχει απενεργοποιήσει το push: είναι η κλήση που το ενεργοποιεί ξανά. Η υπηρεσία μηνυμάτων του SDK χειρίζεται μόνη της τα νέα tokens και δεν αναιρεί ποτέ το unregisterPush(), και ένα Joryio.unregisterPush() πριν από το initialize() υπερισχύει του token που δίνει το Firebase στην εκκίνηση.

Το requestPushPermission(activity) λειτουργεί με οποιοδήποτε Activity. Όταν το δικό σας είναι ένα απλό android.app.Activity (όπως στο Unity), το SDK εμφανίζει το παράθυρο διαλόγου του συστήματος από μια δική του αόρατη activity και σας καλεί πίσω όταν απαντήσει ο χρήστης. Το PushPermissionStatus.publicName δίνει την κατάσταση με το όνομα που χρησιμοποιεί κάθε Joryio SDK: granted, denied, notDetermined ή unavailable.

Το κανάλι ειδοποιήσεων του SDK ονομάζεται "Notifications" στις ρυθμίσεις συστήματος. Ορίστε το notificationChannelName στο JoryioConfig για να εμφανίζεται το δικό σας, μεταφρασμένο όνομα.

Αντιμετώπιση προβλημάτων​

Τα συμβάντα δεν εμφανίζονται​

  1. Ελέγξτε ότι το κλειδί SDK είναι σωστό: jry_sdk_android_*
  2. Ενεργοποιήστε debug logging: enableDebug = true
  3. Ελέγξτε το logcat για σφάλματα
  4. Επαληθεύστε τα δικαιώματα δικτύου στο manifest
  5. Καλέστε flush() για άμεση αποστολή

Το push δεν λειτουργεί​

  1. Επαληθεύστε ότι το Firebase είναι ρυθμισμένο σωστά
  2. Ελέγξτε ότι το Firebase service account JSON έχει μεταφορτωθεί στο dashboard
  3. Βεβαιωθείτε ότι το device token έχει καταχωριστεί
  4. Δοκιμάστε πρώτα με το Firebase Console
  5. Αναζητήστε το σφάλμα της Αποστολή δοκιμαστικής push στο Λήψη διαπιστευτηρίων Firebase (FCM)

Σφάλματα build​

  1. Βεβαιωθείτε ότι η ελάχιστη έκδοση SDK είναι 23
  2. Συγχρονίστε τις εξαρτήσεις Gradle
  3. Εκκαθαρίστε και ξανακάντε build στο έργο

Release builds (R8) και backup​

  • R8 / ProGuard: και τα δύο artifacts περιέχουν τους δικούς τους κανόνες, που εφαρμόζονται αυτόματα όταν η εφαρμογή σας ορίζει isMinifyEnabled = true (μαζί και σε πλήρη λειτουργία R8). Δεν προσθέτετε κανόνες, και μια εφαρμογή χωρίς firebase-messaging γίνεται build κανονικά - απλώς το push είναι ανενεργό.
  • Backup: δεν απαιτείται τίποτα. Η ουρά συμβάντων βρίσκεται στον κατάλογο no-backup της εφαρμογής, και ένας κρυπτογραφημένος χώρος αποθήκευσης που επαναφέρεται από backup (το οποίο δεν περιλαμβάνει ποτέ το κλειδί του Keystore) εντοπίζεται και δημιουργείται ξανά, πάλι κρυπτογραφημένος. Αν η εφαρμογή σας δηλώνει δικούς της κανόνες backup και θέλετε να εξαιρέσετε ρητά τα αρχεία του SDK, παραπέμψτε στα @xml/joryio_backup_rules (android:fullBackupContent) και @xml/joryio_data_extraction_rules (android:dataExtractionRules), ή αντιγράψτε τις γραμμές τους στους δικούς σας κανόνες. Το SDK δεν ορίζει αυτά τα attributes το ίδιο, οπότε δεν συγκρούεται ποτέ με το manifest σας.

Παρακολούθηση E-Commerce​

Το Android SDK περιλαμβάνει ενσωματωμένο e-commerce tracker για συμβάντα προϊόντων, καλαθιού, checkout και παραγγελιών. Δείτε τον δια-SDK οδηγό E-Commerce Tracking για το πλήρες API με παραδείγματα Kotlin.

Μια αγορά, με τα κλειδιά που διαβάζουν οι αναφορές εσόδων:

import io.joryio.sdk.ecommerce.EcommerceCartItem
import io.joryio.sdk.ecommerce.EcommerceOrder

Joryio.getInstance().ecommerce().purchase(
EcommerceOrder(
orderId = "ORD-1001",
items = listOf(EcommerceCartItem(productId = "SKU-1", name = "Blue Shirt", price = 29.99, quantity = 2)),
value = 59.98,
currency = "USD",
)
)

Αυτό στέλνει Order Completed με orderId και total. Αν παρακολουθείτε μια αγορά χειροκίνητα, χρησιμοποιήστε αυτά τα δύο κλειδιά: μια παραγγελία που στέλνεται με order_id ή value καταγράφεται χωρίς έσοδα.

Μια αγορά χωρίς orderId, με value που δεν είναι πεπερασμένος αριθμός ή με είδος χωρίς productId αγνοείται με μια γραμμή στο log και δεν καταγράφεται ποτέ ως παραγγελία μηδενικής αξίας· καμία μέθοδος του tracker δεν πετάει εξαίρεση. Το EcommerceTracker.create() λειτουργεί πριν από το initialize(): τα συμβάντα του περιμένουν το SDK.

Επόμενα βήματα​

4. Ρύθμιση FCM στο Dashboard​

Για να στείλετε push notifications, εισαγάγετε τα διαπιστευτήρια Firebase στην Android εφαρμογή στο Joryio:

  1. Μεταβείτε στις Ρυθμίσεις → Εφαρμογές και επιλέξτε το εικονίδιο Ειδοποιήσεις push (καμπανάκι) στη γραμμή της Android εφαρμογής σας
  2. Επιλέξτε Upload service account JSON και διαλέξτε το κλειδί λογαριασμού υπηρεσίας Firebase (Firebase console: Project settings → Service accounts → Generate New Private Key)
  3. Επιλέξτε Αποθήκευση διαμόρφωσης

Για αναλυτικές οδηγίες δημιουργίας του κλειδιού, δείτε Λήψη διαπιστευτηρίων Firebase (FCM).

Μηνύματα εντός εφαρμογής​

Το SDK εμφανίζει τα μηνύματα in-app για εσάς. Μετά την αρχικοποίηση, οι επιλέξιμες καμπάνιες εμφανίζονται μόνες τους και οι εμφανίσεις, τα κλικ και οι απορρίψεις καταγράφονται αυτόματα - δεν χρειάζεται καμία σύνδεση.

Tokens παράδοσης (delivery tokens)​

Όταν ο διακομιστής σερβίρει μια επιλέξιμη καμπάνια, εκδίδει επίσης ένα υπογεγραμμένο token παράδοσης μικρής διάρκειας. Το SDK το επιστρέφει όταν αναφέρει εμφάνιση, κλικ ή απόρριψη, και ο διακομιστής επαληθεύει την υπογραφή πριν καταγράψει οτιδήποτε.

Δεν χρειάζεται να κάνετε τίποτα - το SDK το χειρίζεται για εσάς. Τεκμηριώνεται επειδή ο διακομιστής μπορεί να ρυθμιστεί ώστε να το απαιτεί. Από προεπιλογή, μια αναφορά χωρίς token γίνεται ακόμη δεκτή - ο διακομιστής την καταγράφει ως client παλαιότερης έκδοσης - και μόνο όταν το backend τρέχει με IN_APP_REQUIRE_DELIVERY_TOKEN=true απορρίπτεται η ίδια αναφορά:

POST /v1/in-app/track (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }

Ένα token που υπάρχει αλλά δεν επαληθεύεται - πλαστό, αλλοιωμένο ή ληγμένο - απορρίπτεται πάντα, ανεξάρτητα από τη ρύθμιση.

Το token είναι αυτό που κάνει μια εμφάνιση αξιόπιστη: χωρίς αυτό, οποιοσδήποτε κατέχει το SDK key - το οποίο περιλαμβάνεται σε κάθε εφαρμογή και σελίδα - θα μπορούσε να αναφέρει εμφανίσεις και κλικ για μια καμπάνια που δεν εμφανίστηκε ποτέ, και τα δεδομένα σας θα τα μετρούσαν.

Αν οι εμφανίσεις in-app σταματήσουν να καταγράφονται σε backend που επιβάλλει tokens, ελέγξτε ότι η εφαρμογή έχει χτιστεί με τρέχουσα έκδοση του SDK: μια έκδοση από πριν υπάρξουν τα tokens παράδοσης δεν στέλνει token.

Δύο είδη περιεχομένου​

Κάθε καμπάνια φτάνει σε μία από δύο μορφές περιεχομένου και το SDK αποδίδει την καθεμία διαφορετικά:

ΠεριεχόμενοΤι είναιΠώς αποδίδεται
NativeΔομημένα δεδομένα - τίτλος, κείμενο, εικόνα, κουμπιάΠραγματικά Android views, με το θέμα, τις γραμματοσειρές, τη σκοτεινή λειτουργία και το TalkBack της εφαρμογής σας. Χωρίς WebView.
HTMLΚώδικας HTML και CSS γραμμένος από τον συντάκτη. Διατηρείται όπως γράφτηκε (αφαιρούνται μόνο τα <base> και <meta http-equiv="refresh">)Ένα WebView μέσα στο μήνυμα, με Content-Security-Policy όπου εκτελείται μόνο το script γέφυρας του SDK.

Ενεργοποίηση μηνυμάτων HTML​

Τα μηνύματα HTML είναι απενεργοποιημένα από προεπιλογή. Τα scripts που γράφονται σε ένα μήνυμα δεν εκτελούνται: κάθε μήνυμα παίρνει νέο nonce στο Content-Security-Policy, οπότε εκτελείται μόνο το script γέφυρας του SDK - τα <script>, οι χειριστές on* και τα embeds του συντάκτη μένουν ανενεργά, και η γέφυρα χειρίζεται κουμπιά, χαρακτηριστικά data-joryio-action και φόρμες data-jry-form. Παραμένει όμως web view μέσα στην εφαρμογή σας, οπότε η ενεργοποίηση είναι απόφαση της ομάδας της εφαρμογής - όχι κάτι που ενεργοποιείται από μια πλατφόρμα μάρκετινγκ:

val config = JoryioConfig(
allowHtmlJsInAppMessages = true // προεπιλογή: false
)

Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "https://api-eu1.joryio.com/api",
config = config
)

Αφήνοντάς το απενεργοποιημένο δεν απενεργοποιείτε τα in-app μηνύματα. Τα native μηνύματα συνεχίζουν να εμφανίζονται, επειδή είναι δεδομένα που η εφαρμογή σας σχεδιάζει με τα δικά της views - χωρίς κανέναν διερμηνέα. Οι καμπάνιες HTML παραλείπονται και καταγράφονται, ώστε μια εφαρμογή που δεν το έχει ενεργοποιήσει να μην δείχνει τίποτα αντί για ένα χαλασμένο μήνυμα.

Αν η πολιτική ασφαλείας σας απαγορεύει την εκτέλεση συντεταγμένου HTML εντός της διεργασίας, αφήστε το απενεργοποιημένο και συντάξτε τις καμπάνιες σας ως native μηνύματα.

Αναλαμβάνοντας εσείς την απόδοση​

Ορίστε ένα callback για να σχεδιάσετε το δικό σας UI. Υπερισχύει της απόδοσης του SDK, οπότε δεν θα λάβετε δύο αντίγραφα του μηνύματος:

Joryio.getInstance().setInAppMessageCallback { campaign ->
showInAppMessage(campaign) // το δικό σας UI
}

// Αναφέρετε τι συνέβη - το SDK καταγράφει αυτόματα μόνο όσα εμφανίζει το ίδιο
Joryio.getInstance().trackInAppImpression(campaignId, "displayed")
Joryio.getInstance().trackInAppImpression(campaignId, "clicked", "cta") // ποιο κουμπί
Joryio.getInstance().trackInAppImpression(campaignId, "dismissed")

Η ενέργεια είναι μία από τις displayed, clicked, dismissed ή converted· οτιδήποτε άλλο καταγράφεται στο log και δεν αποστέλλεται. Μόνο το displayed μετράει στο όριο συχνότητας της καμπάνιας - ένα κλικ ή μια απόρριψη δεν καταναλώνει άλλη μία εμφάνιση. Το τρίτο όρισμα, elementId, ονομάζει το κουμπί ή τον σύνδεσμο και αποστέλλεται χωριστά από την ενέργεια.

Από την έκδοση 1.3.2. Αν ορίσετε το callback μετά το initialize(), ο πρώτος συγχρονισμός μπορεί να τελειώσει νωρίτερα και το SDK σχεδιάζει μόνο του εκείνο το μήνυμα. Για να το αποτρέψετε, περάστε JoryioConfig(holdInAppMessagesForHandler = true): ένα μήνυμα που είναι έτοιμο πριν οριστεί το callback κρατείται - δεν σχεδιάζεται, δεν αναφέρεται, δεν μετρά σε κανένα όριο συχνότητας - και δίνεται στο callback τη στιγμή που το ορίζετε. Αν αντί γι' αυτό καλέσετε clearInAppMessageCallback(), η αναμονή τελειώνει και το SDK σχεδιάζει το μήνυμα που κρατήθηκε. Η αναμονή ισχύει μόνο μέχρι τον πρώτο ορισμό ή καθαρισμό.

Απόδοση των μηνυμάτων μόνοι σας​

Το SDK σχεδιάζει τα native μηνύματα, αλλά μπορείτε να αναλάβετε πλήρως - το αντίστοιχο του custom view factory:

Joryio.getInstance().setInAppMessageCallback { campaign ->
// σχεδιάστε το όπως θέλετε
}

Ο ορισμός callback αντικαθιστά τον ενσωματωμένο renderer αντί να τρέχει παράλληλα, οπότε το μήνυμα εμφανίζεται μία φορά, όχι δύο.

Το campaign.content είναι ήδη έτοιμο - το Liquid αποδίδεται στον διακομιστή και τα native πεδία φτάνουν ως απλό κείμενο (μην τα δίνετε σε WebView: δεν έχουν HTML-escape ακριβώς επειδή προορίζονται για TextView). Καταγράψτε ό,τι εμφανίζετε με trackInAppImpression(campaignId, "displayed" | "clicked" | "dismissed" | "converted"), ώστε τα analytics να συμφωνούν.

Τύποι μηνυμάτων​

Το SDK υποστηρίζει 5 τύπους μηνυμάτων:

  1. Modal - Στο κέντρο της οθόνης με σκίαση
  2. Banner - Στο επάνω μέρος της οθόνης
  3. Slide-Up - Μικρή ειδοποίηση από κάτω
  4. Full-Screen - Μήνυμα που καταλαμβάνει την οθόνη
  5. Custom - Η εφαρμογή σας αποφασίζει τη θέση

Πότε ένα μήνυμα μετρά ως εμφανισμένο​

Ένα μήνυμα μετρά ως εμφανισμένο - αναφέρεται το in_app.displayed και καταναλώνεται το όριο συχνότητας της καμπάνιας - μόνο όταν το άτομο μπορούσε πράγματι να το δει. Ο χρόνος που πρέπει να μείνει στην οθόνη είναι το IN_APP_DISPLAY_DWELL_MS (1000 ms), η ίδια σταθερά σε κάθε Joryio SDK.

  • Το SDK εμφανίζει ένα μήνυμα μόνο στο ενεργό (resumed) Activity, και μόνο αν δεν κλείνει, δεν έχει καταστραφεί και δεν αναδημιουργείται (περιστροφή). Διαφορετικά το μήνυμα κρατιέται για την επόμενη οθόνη.
  • Το μήνυμα μετρά ως εμφανισμένο αφού μείνει στην οθόνη 1 δευτερόλεπτο ενώ το Activity παραμένει ενεργό. Αν το Activity τεθεί σε παύση νωρίτερα, το δευτερόλεπτο ξεκινά από την αρχή όταν συνεχίσει.
  • Αν η οθόνη κλείσει νωρίτερα - ανακατεύθυνση μετά τη σύνδεση, αποσύνδεση - δεν αναφέρεται τίποτα και το όριο δεν καταναλώνεται· το μήνυμα προσφέρεται στην επόμενη οθόνη.
  • Ένα πάτημα ή κλείσιμο νωρίτερα μετρά ως προβολή: αναφέρεται πρώτα το displayed και μετά το κλικ ή η απόρριψη. Μια επαναφορά, αλλαγή χρήστη ή εξαίρεση (opt-out) νωρίτερα δεν αναφέρει τίποτα.
  • Αν αποδίδετε μόνοι σας τα μηνύματα: αναφέρετε το displayed με τον ίδιο κανόνα - αφού το μήνυμα είναι ορατό για IN_APP_DISPLAY_DWELL_MS, ή αμέσως πριν από ένα κλικ ή κλείσιμο που έρχεται νωρίτερα. Ένας δικός σας InAppMessagePresenter στέλνει InAppMessagePresenter.EVENT_HIDDEN όταν αφαιρεί ένα μήνυμα χωρίς να έχει ενεργήσει το άτομο.

Επιλογές ρυθμίσεων​

Προσαρμόστε τη συμπεριφορά του SDK με JoryioConfig:

val config = JoryioConfig(
// Initial user ID (optional)
userId = "user-123",
anonymousId = null, // Your own anonymous id; generated if null

// Event batching
batchSize = 50, // Events per batch (at most 500)
flushInterval = 5000, // Flush interval in ms (5s); never below 1000
sendImmediately = false, // Flush after every track, no batching
maxQueueSize = 1000, // Most events kept on the device; the oldest go first

// Session management
sessionTimeout = 1800000, // Session timeout in ms (30 min), background included
trackSessionStart = true, // Auto-track session start

// Network retry
maxRetries = 3, // Immediate retries of one request before backing off
retryBackoffMs = 1000, // Base backoff between retries (ms)
requestTimeout = 10000, // Per-request HTTP timeout (ms)

// SDK authentication (optional, JWT)
enableSdkAuthentication = false,
sdkAuthenticationToken = null,

// Privacy controls
optOut = false, // Start opted out (remembered, like optOut())
trackingConsent = null, // null = not set; see Tracking Consent below

// Push: ask for notification permission at the first foreground
requestPushPermissionAtLaunch = false,

// In-app messaging
// Allow HTML in-app messages, shown in a WebView. Scripts in them do not
// run; the SDK's bridge handles buttons and forms. Native messages always
// display regardless of this setting.
allowHtmlJsInAppMessages = false,
holdInAppMessagesForHandler = false, // since 1.3.2; true: your app draws messages and sets its callback after initialize()

// Push: the channel name users see in system settings (Android 8+)
notificationChannelName = "Notifications",

// Debugging
enableDebug = false, // Enable debug logging
logLevel = null // null (default): follows enableDebug. Set to pin a level.
)

Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "https://api-eu1.joryio.com/api",
config = config
)
enum class TrackingConsent {
GRANTED, // Full tracking allowed
PENDING, // Waiting for user decision
DENIED // User denied tracking
}

Η συγκατάθεση και η εξαίρεση (opt-out) είναι δύο ξεχωριστοί διακόπτες, ίδιοι σε κάθε SDK της Joryio:

ΔιακόπτηςΤον ορίζειΚλήσεις
Συγκατάθεση παρακολούθησηςτο banner συγκατάθεσής σαςupdateConsent(...), trackingConsent στις ρυθμίσεις
Εξαίρεση (opt-out)η επιλογή του ίδιου του ατόμουoptOut() / optIn(), optOut στις ρυθμίσεις

Το SDK συλλέγει δεδομένα μόνο όταν η συγκατάθεση είναι GRANTED και το άτομο δεν έχει εξαιρεθεί - οποιοδήποτε "όχι" υπερισχύει. Η συγκατάθεση δεν αίρει ποτέ μια εξαίρεση (μόνο το optIn() το κάνει), και το optOut() δεν αλλάζει ποτέ την αποθηκευμένη συγκατάθεση.

Joryio.updateConsent(TrackingConsent.GRANTED) // granted: everything held is sent, in order
Joryio.updateConsent(TrackingConsent.DENIED) // denied: everything held is discarded
Joryio.updateConsent(TrackingConsent.PENDING) // pending: hold everything on the device
  • Το PENDING κρατά τα συμβάντα και τα γνωρίσματα στη συσκευή (έως maxQueueSize) και δεν στέλνει τίποτα - ούτε μηνύματα in-app, ούτε καταχώριση push - μέχρι το GRANTED να τα αποδεσμεύσει με τη σειρά ή το DENIED να τα απορρίψει. Ισχύει μόνο για την τρέχουσα εκκίνηση και δεν υπερισχύει ποτέ ενός αποθηκευμένου DENIED. Ένα πάτημα ειδοποίησης κρατιέται επίσης και αναφέρεται με το GRANTED.
  • Το DENIED δεν συλλέγει τίποτα και απορρίπτει ό,τι κρατιέται. Αποθηκεύεται στη συσκευή: παραμένει σε ισχύ και μετά από επανεκκινήσεις, μέχρι ένα updateConsent(TrackingConsent.GRANTED) ή ένα ρητό trackingConsent = TrackingConsent.GRANTED στις ρυθμίσεις. Δεν εξαιρεί το άτομο - το isUserOptedOut() μένει false.
  • Χωρίς τιμή (trackingConsent = null, η προεπιλογή): ένα αποθηκευμένο DENIED παραμένει σε ισχύ· διαφορετικά το SDK συμπεριφέρεται σαν GRANTED, που είναι το σωστό για μια εφαρμογή χωρίς banner συγκατάθεσης.

Διαβάστε την τρέχουσα τιμή με το getTrackingConsent().

Επίπεδα log​

enum class LogLevel {
VERBOSE, // All logs
DEBUG, // Debug and above
INFO, // Info and above
WARN, // Warnings and errors
ERROR // Errors only
}

Το logLevel έχει προεπιλογή null, δηλαδή "ακολούθα το enableDebug": DEBUG όταν το debugging είναι ενεργό, ERROR όταν είναι ανενεργό. Ορίστε το ρητά μόνο για να κλειδώσετε ένα επίπεδο - ένα ρητό LogLevel.ERROR με enableDebug = true κρατά το SDK σιωπηλό.

Προηγμένες δυνατότητες​

Διαχείριση συνεδριών​

Οι συνεδρίες παρακολουθούν αυτόματα την αλληλεπίδραση χρήστη:

// Sessions are managed automatically with 30-minute timeout
// Get current session ID
val sessionId = Joryio.getInstance().getSessionId()

// Sessions refresh on user activity

Ο χρόνος στο παρασκήνιο μετράει στο timeout: μια εφαρμογή που επανέρχεται μετά από περισσότερο από sessionTimeout ξεκινά νέα συνεδρία και στέλνει νέο Session Start.

Γνωρίσματα χρήστη​

// Set multiple attributes
Joryio.getInstance().setAttributes(mapOf(
"age" to 28,
"city" to "San Francisco",
"premium" to true
))

// Set single attribute
Joryio.setAttribute("language", "en")

// Increment numeric attribute
Joryio.incrementAttribute("page_views", 1)
Joryio.incrementAttribute("total_spent", 29.99)

// Remove attribute
Joryio.getInstance().unsetAttribute("temporary_flag")

Έλεγχοι απορρήτου​

// Διακοπή συλλογής. ΑΠΟΘΗΚΕΥΕΤΑΙ - επιβιώνει μετά από επανεκκίνηση της εφαρμογής.
Joryio.optOut()

// Επαναφορά στην παρακολούθηση - η μόνη κλήση που αίρει μια εξαίρεση
Joryio.optIn()

// Μόνο ο διακόπτης εξαίρεσης· η συγκατάθεση είναι το getTrackingConsent()
if (Joryio.isUserOptedOut()) {
Log.i("MyApp", "User has opted out")
}

// Διαγραφή όλων όσων αποθήκευσε το SDK σε αυτή τη συσκευή.
// ΞΕΧΩΡΙΣΤΟ από το optOut(): το "σταματήστε να συλλέγετε" και το "διαγράψτε ό,τι
// έχετε" είναι διαφορετικά αιτήματα. Αυτό χρειάζεται ένα αίτημα διαγραφής. ΔΕΝ
// εξαιρεί τον χρήστη - καλέστε και το optOut() αν αυτό επιδιώκεται επίσης.
Joryio.wipeData()

// Στοιχεία ταυτότητας
val (userId, anonymousId) = Joryio.getInstance().getIdentity()
println("User: ${userId ?: "anonymous"}, Anonymous ID: $anonymousId")

Τι ακριβώς κάνει το optOut():

σταματά τη συλλογήναι - κάθε εγγραφή γίνεται no-op: συμβάντα, identify, γνωρίσματα (μαζί με αυξήσεις και αφαιρέσεις), aliases, εγγραφές συνδρομών, καταχώριση push, αποδείξεις παράδοσης και παρακολούθηση κλικ, αναφορές in-app. Οι απεγγραφές και η κατάργηση καταχώρισης push στέλνονται κανονικά
επιβιώνει μετά από επανεκκίνησηναι - η σημαία αποθηκεύεται στη συσκευή και διαβάζεται πριν συλλεχθεί οτιδήποτε
απορρίπτει ό,τι είναι ήδη σε ουράναι - τα συμβάντα σε ουρά και οι ανεπιβεβαίωτες εγγραφές γνωρισμάτων απορρίπτονται, δεν παραδίδονται αργότερα. Μια απεγγραφή που δεν έχει ακόμη παραδοθεί κρατιέται και στέλνεται
ενημερώνει τον serverναι - το γνώρισμα προφίλ tracking_opted_out, που στέλνεται όταν αλλάζει ο διακόπτης (και για εξαίρεση που αποφασίστηκε κατά ή πριν από την αρχικοποίηση) και ξαναδοκιμάζεται μέχρι να το λάβει ο server, ανεξάρτητα από τη συγκατάθεση για το true (το false του optIn() περιμένει συγκατάθεση GRANTED)
διαγράφει αποθηκευμένα δεδομέναόχι - χρησιμοποιήστε το wipeData()

Το γνώρισμα tracking_opted_out είναι καταγραφή, όχι επιβολή: καταλήγει στο προφίλ ώστε οι καμπάνιες να μπορούν να εξαιρούν βάσει αυτού. Η καταστολή στην πλευρά του server είναι ξεχωριστή ρύθμιση.

Μη αυτόματη αποστολή ουράς​

// Flush events immediately
Joryio.flush()

// Useful before app termination
override fun onDestroy() {
super.onDestroy()
Joryio.flush()
}

Βέλτιστες πρακτικές​

1. Αρχικοποιήστε νωρίς​

Αρχικοποιήστε στην κλάση Application:

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "https://api-eu1.joryio.com/api"
)
}
}

2. Παρακολουθήστε προβολές οθόνης​

Χρησιμοποιήστε παρακολούθηση οθόνης για πλοήγηση:

override fun onResume() {
super.onResume()
Joryio.trackScreen(this::class.simpleName ?: "Unknown")
}

3. Χειριστείτε την αποσύνδεση χρήστη​

Κάνετε πάντα reset κατά την αποσύνδεση:

fun logout() {
// Clear user session
clearUserSession()

// Reset SDK
Joryio.reset()
}

Αποστολή, συγκατάθεση και κύκλος ζωής δεδομένων​

Οι εγγραφές κρατιούνται μέχρι να τις επιβεβαιώσει ο server. Τα identify, addAlias, incrementAttribute, unsetAttribute, setSubscription και οι κλήσεις ομάδων συνδρομής κρατιούνται σε κρυπτογραφημένο outbox στη συσκευή και αποστέλλονται με τη σειρά που έγιναν. Μια αποτυχία - εκτός σύνδεσης, timeout, 5xx, 429 ή ένα 200 που απαντά success: false - κρατά την εγγραφή και την ξαναδοκιμάζει με αυξανόμενη αναμονή, και μετά από επανεκκίνηση. Το Retry-After τηρείται, σε δευτερόλεπτα ή ως ημερομηνία, και ακόμη και το flush() περιμένει να περάσει. Μια εγγραφή που ο server δεν θα δεχτεί ποτέ - ένα 4xx όπως 400, 409, 413 ή 422, ή ένα 403 από την πολιτική ταυτότητάς σας - απορρίπτεται με μια γραμμή στο log, ώστε να μην μπλοκάρει όσες ακολουθούν. Η απουσία σύνδεσης, ένα timeout, ένα 429 ή ένα 5xx δεν μετράνε ποτέ εις βάρος μιας εγγραφής. Ένα 401 ή ένα 200 με success: false ξαναδοκιμάζεται το πολύ δέκα φορές για τη συγκεκριμένη εγγραφή, ένα 404 (δεν υπάρχει ακόμη προφίλ) το πολύ πέντε: μια αλλαγή συνδρομής που λαμβάνει απάντηση 404 ξαναδοκιμάζεται αφού σταλούν τα συμβάντα σε ουρά, αφού αυτά δημιουργούν το προφίλ. Οι τιμές που στέλνει το setAttributes έχουν τις ίδιες δέκα απαντήσεις. Οι κλήσεις γνωρισμάτων στο ίδιο κλειδί εφαρμόζονται με τη σειρά που τις κάνατε, όποιο από τα setAttribute, unsetAttribute και incrementAttribute κι αν χρησιμοποιήσατε. Το setAttributes έχει τη δική του ανθεκτική ουρά, που περιγράφεται παραπάνω· γνωρίσματα που ο server απορρίπτει ως μη έγκυρα (400) απορρίπτονται κι εκεί.

Όρια συμβάντων. Ο server απορρίπτει ένα συμβάν του οποίου οι ιδιότητες ξεπερνούν τα 50 KB, έχουν περισσότερα από 200 κλειδιά ή φωλιάζουν βαθύτερα από 5 επίπεδα, ή του οποίου το όνομα είναι κενό ή μεγαλύτερο από 255 χαρακτήρες. Το track() τα ελέγχει στη συσκευή, μετρώντας το μέγεθος όπως ο server (ένας χαρακτήρας όπως < ή & μετράει μία φορά), και απορρίπτει ένα τέτοιο συμβάν με μια γραμμή στο log, αντί να βάζει σε ουρά κάτι που δεν μπορεί ποτέ να παραδοθεί. Ένα όνομα που αποτελείται μόνο από κενά είναι έγκυρο όνομα. Ένα συμβάν που ο server εξακολουθεί να απορρίπτει οριστικά απορρίπτεται, δεν ξαναδοκιμάζεται.

Μια τιμή null αφαιρεί ένα γνώρισμα. Το setAttribute("plan", null) είναι το ίδιο με το unsetAttribute("plan"), σε κάθε SDK της Joryio.

Συνεδρίες. Μια συνεδρία λήγει μετά από sessionTimeout χωρίς δραστηριότητα, είτε η εφαρμογή ήταν στο παρασκήνιο είτε έμεινε ανοιχτή σε μία οθόνη. Το πρώτο συμβάν μετά από αυτό ξεκινά νέα συνεδρία και στέλνει πρώτα το Session Start της. Και το reset() ξεκινά νέα συνεδρία. Μια εκκίνηση εντός sessionTimeout από την τελευταία δραστηριότητα συνεχίζει εκείνη τη συνεδρία και δεν στέλνει νέο Session Start. Μια συνεδρία που ξεκίνησε ενώ η παρακολούθηση ήταν αποκλεισμένη (opt-out ή απορριφθείσα συγκατάθεση) δεν συνεχίζεται έτσι: η πρώτη εκκίνηση που επιτρέπεται να παρακολουθεί ξεκινά νέα συνεδρία με το Session Start της.

Συγκατάθεση στην εκκίνηση. Το optOut = true λειτουργεί όπως το optOut(): αποθηκεύεται στη συσκευή, ώστε μια επόμενη εκκίνηση να μένει εκτός παρακολούθησης μέχρι το optIn(). Το trackingConsent = TrackingConsent.DENIED αποθηκεύεται επίσης, ως συγκατάθεση (δείτε Συγκατάθεση παρακολούθησης). Και στις δύο περιπτώσεις, ό,τι άφησε σε ουρά μια προηγούμενη εκκίνηση απορρίπτεται. Τα Joryio.optOut(), Joryio.optIn(), Joryio.updateConsent() και Joryio.wipeData() που καλούνται πριν από το initialize() εφαρμόζονται από το ίδιο το initialize(), πριν συλλεχθεί ή σταλεί οτιδήποτε - και μέχρι τότε κρατιούνται στη συσκευή, ώστε μια απόφαση που πάρθηκε πριν ξεκινήσει το SDK να επιβιώνει ακόμη κι αν κλείσει η εφαρμογή. Πριν από το initialize(), τα Joryio.isUserOptedOut() και Joryio.getTrackingConsent() απαντούν ό,τι είναι αποθηκευμένο στη συσκευή - μια εξαίρεση ή μια άρνηση από προηγούμενη εκκίνηση - όχι μια προεπιλογή. Ένα Joryio.updateConsent(TrackingConsent.PENDING) πριν από το initialize() ισχύει μόνο για εκείνη την εκκίνηση και δεν ανοίγει ποτέ ξανά μια άρνηση, και ένα Joryio.optIn() ή Joryio.optOut() πριν από το initialize() υπερισχύει του optOut στις ρυθμίσεις. Όταν δεν υπάρχει τίποτα αποθηκευμένο, το Joryio.getTrackingConsent() απαντά GRANTED, όπως στο iOS.

Πιστοποίηση SDK. Με ενεργό το enableSdkAuthentication, ο handler που ορίζεται με το setSdkAuthenticationErrorHandler καλείται για κάθε 401 πιστοποίησης, ακόμη και όταν δεν έχει οριστεί ακόμη token (αιτία MISSING): δώστε ένα με το setSdkAuthenticationToken. Τα συμβάντα στέλνονται σε δέσμες ενός χρήστη τη φορά· συμβάντα ή εγγραφές ενός χρήστη που έχει στο μεταξύ αποσυνδεθεί, τα οποία ο server απορρίπτει για οποιονδήποτε λόγο πιστοποίησης, απορρίπτονται, ώστε να μην καθυστερούν ποτέ τα δεδομένα του επόμενου χρήστη. Ο handler καλείται μόνο για αιτήματα του χρήστη που είναι συνδεδεμένος τώρα, ποτέ για υπολείμματα ενός χρήστη που αποσυνδέθηκε. Το reset() βγάζει το token από τη συνεδρία· μένει με ό,τι άφησε στην ουρά ο χρήστης που αποσυνδέεται. Κάθε αίτημα φέρει το token του οποίου το sub είναι ο χρήστης του αιτήματος - ποτέ ένα που ονομάζει κάποιον άλλο - και ένα αίτημα ανώνυμου επισκέπτη δεν φέρει κανένα. Ένα token που ορίστηκε για τον επόμενο χρήστη πριν από το reset(), ακόμη και όταν δεν είναι κανείς συνδεδεμένος, κρατιέται για εκείνον και χρησιμοποιείται ξανά στο identify() του· το wipeData() το διαγράφει. Διαφορετικά, ορίστε το token του επόμενου χρήστη με το setSdkAuthenticationToken μόλις συνδεθεί. Συμβάντα και εγγραφές που ο χρήστης που αποσυνδέεται είχε ήδη βάλει στην ουρά εξακολουθούν να στέλνονται με το δικό του token, οπότε μια κανονική αποσύνδεση δεν χάνει τίποτα.

Πριν από την αρχικοποίηση. Οι κλήσεις στο Joryio πριν ολοκληρωθεί το initialize() κρατιούνται - έως 1000, οι παλαιότερες απορρίπτονται πρώτες - και εκτελούνται με τη σειρά. Ένα συμβάν, μια εγγραφή γνωρίσματος, ένα alias ή μια εγγραφή συνδρομής που γίνεται μέσω αυτών ενώ ο χρήστης έχει κάνει opt-out ή η συγκατάθεση έχει απορριφθεί - με απόφαση πριν από το initialize() ή αποθηκευμένη από προηγούμενη εκκίνηση - απορρίπτεται αμέσως, ώστε ένα μεταγενέστερο optIn() ή μια παροχή συγκατάθεσης να μην το στείλει ποτέ· οι απεγγραφές, το identify() και το reset() διατηρούνται. Ένα optOut() ή updateConsent(DENIED) πριν από το initialize() απορρίπτει επίσης τα συμβάντα, τις εγγραφές γνωρισμάτων, τα aliases και τις εγγραφές συνδρομής που μπήκαν στην ουρά πριν από αυτό, όπως και μετά την αρχικοποίηση, ώστε ένα μεταγενέστερο optIn() ή μια παροχή συγκατάθεσης πριν από το initialize() να μην τα στείλει ποτέ. Ένα token που ορίζεται με Joryio.setSdkAuthenticationToken() πριν από οποιοδήποτε identify(), reset() ή wipeData() εφαρμόζεται από το ίδιο το initialize(), πριν από το πρώτο του αίτημα. Ορίστε το token και τον handler με τις στατικές Joryio.setSdkAuthenticationToken() και Joryio.setSdkAuthenticationErrorHandler(), όχι μέσα σε whenReady: μόνο αυτές λαμβάνονται πριν από το πρώτο αίτημα και διατηρούνται μετά από ένα wipeData() πριν από το initialize(), ώστε οι απεγγραφές του χρήστη που διαγράφεται να στέλνονται ακόμη με το token του. Το Joryio.getInstance() είναι η μόνη κλήση που πετάει εξαίρεση (IllegalStateException) πριν από το initialize()· όπου η αρχικοποίηση μπορεί να μην έχει ολοκληρωθεί, χρησιμοποιήστε τις στατικές μεθόδους ή το Joryio.whenReady { sdk -> ... }.

Λογαριασμός σε παύση (402). Ένα 402 σημαίνει ότι η λήψη δεδομένων του λογαριασμού σας είναι σε παύση. Δεν μετρά ποτέ στο όριο επαναλήψεων μιας εγγραφής και δεν καθυστερεί τις ανακλήσεις: μια διαγραφή από συνδρομή, μια αποχώρηση από ομάδα και η καταγραφή tracking_opted_out = true που περιμένουν πίσω από μια εγγραφή που απορρίφθηκε στέλνονται κανονικά, ενώ οι άλλες εγγραφές περιμένουν και ξαναδοκιμάζονται με αυξανόμενη αναμονή.

Αποσύνδεση και αλλαγή λογαριασμού. Το token πιστοποίησης SDK του χρήστη που αποσυνδέεται κρατιέται στον κρυπτογραφημένο χώρο αποθήκευσης μαζί με τα συμβάντα και τις εγγραφές του που περιμένουν, ώστε να παραδοθούν με αυτό ακόμη κι αν η εφαρμογή κλείσει πριν σταλούν· διαγράφεται μόλις παραδοθούν. Ένα identify() άλλου χρήστη χωρίς reset() αντιμετωπίζεται με τον ίδιο τρόπο για τον προηγούμενο χρήστη: τα γνωρίσματά του που δεν έχουν σταλεί στέλνονται με τα δικά του αναγνωριστικά, και το token πηγαίνει με ό,τι του έχει απομείνει - εκτός αν έχετε ήδη ορίσει το token του νέου χρήστη, το οποίο το SDK αναγνωρίζει από το sub του. Μια διαγραφή από συνδρομή σε αναμονή αντικαθίσταται μόνο από νεότερη αλλαγή του ίδιου χρήστη.

identify() με εξαίρεση ή άρνηση συγκατάθεσης. Το αναγνωριστικό χρήστη κρατιέται μόνο στη συσκευή - δεν στέλνεται τίποτα και δεν γράφεται κανένα προφίλ - ώστε μια διαγραφή από συνδρομή στο μεταξύ να το φέρει. Όταν η παρακολούθηση επιτρέπεται ξανά (optIn(), updateConsent(TrackingConsent.GRANTED)), το identify() εκτελείται κανονικά. Τα reset() και wipeData() το ξεχνούν. Αυτό συμβαίνει και σε μια επόμενη εκκίνηση της οποίας η αποθηκευμένη κατάσταση ή οι ρυθμίσεις επιτρέπουν την παρακολούθηση. Ένα νέο identify() αντικαθιστά το κρατημένο αναγνωριστικό χρήστη, και ένα userId στις ρυθμίσεις υπερισχύει. Η εγγραφή εξαίρεσης και μια κατάργηση καταχώρισης push που έγιναν στο μεταξύ το φέρουν επίσης, και με SDK Authentication μια απόρριψή τους ζητά από τον handler σας το token εκείνου του χρήστη.

Αριθμοί που δεν χωρούν σε JSON. Ένα NaN ή Infinity σε τιμή γνωρίσματος ή σε ιδιότητα συμβάντος ή ηλεκτρονικού εμπορίου παραλείπεται - μόνο αυτό το κλειδί - με μια γραμμή καταγραφής, και η υπόλοιπη κλήση συνεχίζει. Μια γραμμή null σε λίστα items ηλεκτρονικού εμπορίου παραλείπεται.

Η ανάγνωση της συνεδρίας δεν αλλάζει τίποτα. Τα getSessionId() και getSessionInfo() δεν ξεκινούν ούτε παρατείνουν ποτέ μια συνεδρία. Μόνο τα συμβάντα και η επιστροφή της εφαρμογής στο προσκήνιο το κάνουν, και μια νέα συνεδρία στέλνει πάντα το Session Start της.

Γεμάτη μνήμη αναμονής πριν από την αρχικοποίηση. Όταν η μνήμη των 1000 κλήσεων είναι γεμάτη, απορρίπτεται πρώτη η παλαιότερη κλήση που δεν είναι διαγραφή από συνδρομή, αποχώρηση από ομάδα ή Joryio.unregisterPush().

Κλειδιά, αποθήκευση και threads. Ένα κλειδί SDK που εκδόθηκε για άλλη πλατφόρμα (jry_sdk_ios_..., jry_sdk_web_..., κατάστημα) απορρίπτεται με σφάλμα και το SDK δεν στέλνει τίποτα. Ένα σφάλμα του Keystore ξαναδοκιμάζεται και δεν διαγράφει ποτέ τον κρυπτογραφημένο χώρο αποθήκευσης· μόνο ένας χώρος που δεν μπορεί να αποκρυπτογραφηθεί (επαναφορά από αντίγραφο ασφαλείας) δημιουργείται ξανά, και μια εκκίνηση που έτρεξε όσο το Keystore δεν ήταν διαθέσιμο ξεκινά από ένα απλό αρχείο χωρίς την αποθηκευμένη ταυτότητα· στην επόμενη εκκίνηση που ανοίγει τον κρυπτογραφημένο χώρο, η τελευταία της αλλαγή σε κάθε τιμή υπερισχύει - μαζί και μια αποσύνδεση, ένα wipeData(), ένα optIn() ή μια παροχή συγκατάθεσης που έγιναν τότε - ενώ το ανώνυμο αναγνωριστικό και το αναγνωριστικό συσκευής που χρειάστηκε να δημιουργήσει δεν αντικαθιστούν τα αποθηκευμένα, και οι απεγγραφές και οι εγγραφές σε αναμονή και από τις δύο εκκινήσεις διατηρούνται. Αν εκείνη η εκκίνηση έκανε αποσύνδεση ή συνέδεσε άλλον χρήστη, το ανώνυμο αναγνωριστικό του αποθηκευμένου χρήστη δεν μεταφέρεται και τα γνωρίσματά του που δεν στάλθηκαν δεν στέλνονται ποτέ ως ο νέος χρήστης - απορρίπτονται μετά από αποσύνδεση, αλλιώς στέλνονται με τα δικά του αναγνωριστικά. Ένα κύριο κλειδί που παραμένει άχρηστο - σε τρεις διαδοχικές εκκινήσεις χωρίς να ανοίξει κανένας χώρος αποθήκευσης με αυτό, με αιτία που δείχνει ότι δεν μπορεί να αποκρυπτογραφήσει ή με αποτυχίες που διαρκούν τουλάχιστον μία ημέρα - αντικαθίσταται από νέο κλειδί του ίδιου του SDK (το κλειδί androidx της εφαρμογής σας δεν αγγίζεται ποτέ). Ο κρυπτογραφημένος χώρος του παλιού κλειδιού τότε απορρίπτεται, και ό,τι περιείχε ακόμη και δεν ξαναγράφτηκε από τις εκκινήσεις με το απλό αρχείο - μαζί και απεγγραφές σε αναμονή - χάνεται· ένας χώρος που το παλιό κλειδί ανοίγει ακόμη μεταφέρεται πρώτα στο νέο κλειδί. Το main thread δεν περιμένει ποτέ μια επανάληψη. Με το initializeAsync(), ένα Activity που άνοιξε όσο ξεκινούσε το SDK εντοπίζεται - για τα μηνύματα in-app, το αίτημα άδειας κατά την εκκίνηση και το πάτημα της ειδοποίησης που το άνοιξε. Το callback σας για μηνύματα in-app εκτελείται στο main thread, και ένας τύπος μηνύματος που αυτή η έκδοση του SDK δεν γνωρίζει φτάνει ως MessageType.CUSTOM.

Τα μηνύματα HTML μένουν στο έγγραφό τους. Φορτώνεται μόνο το ίδιο το έγγραφο του μηνύματος. Κάθε πλοήγηση που ξεκινά - σύνδεσμος, ανανέωση, frame - ακυρώνεται, και ένας σύνδεσμος που πατά ο χρήστης ανοίγει στον browser. Το CSS του δεν μπορεί να κλείσει το δικό του στοιχείο <style>.

Το unregisterPush() διαρκεί. Το token ξεχνιέται αμέσως στη συσκευή και το αίτημα κρατιέται μέχρι να το επιβεβαιώσει ο server. Το SDK δεν καταχωρεί ξανά token από μόνο του - ούτε στην επόμενη εκκίνηση ούτε όταν το Firebase αλλάζει το token - μέχρι να καλέσετε το registerPushToken() ή ο χρήστης να δώσει άδεια μέσω του requestPushPermission().

Το wipeData() διαγράφει το ανώνυμο αναγνωριστικό και το αναγνωριστικό χρήστη, την ουρά συμβάντων, τις εκκρεμείς εγγραφές, την κατάσταση in-app και το ιστορικό εμφανίσεων, τη συνεδρία, το αναγνωριστικό συσκευής και το push token - και καταργεί την καταχώριση αυτού του token στον server, με την ταυτότητα που διαγράφεται. Διατηρεί μια υπάρχουσα εξαίρεση από την παρακολούθηση: η διαγραφή δεδομένων δεν είναι συγκατάθεση. Στη συνέχεια το push μένει ανενεργό μέχρι η εφαρμογή να καλέσει ξανά το registerPushToken ή να ζητήσει ξανά άδεια - ένα token που έρχεται σε επόμενη εκκίνηση δεν καταχωρίζεται αυτόματα.

Μία εμφάνιση ανά έναυσμα. Μια καμπάνια δεσμεύεται τη στιγμή που επιλέγεται, πριν από οποιαδήποτε καθυστέρηση εμφάνισης, ώστε δύο εναύσματα σε γρήγορη διαδοχή (το ίδιο συμβάν που καταγράφεται δύο φορές, ένας συγχρονισμός που φτάνει ενώ ένα καθυστερημένο μήνυμα περιμένει) να την εμφανίζουν μία φορά. Η δέσμευση λήγει όταν το μήνυμα απορριφθεί από τον χρήστη (και όταν δεν επιτρέπεται να σταλεί τίποτα) ή όταν δεν ήταν δυνατό να εμφανιστεί. Κανένα μήνυμα δεν εμφανίζεται όσο η συγκατάθεση δεν είναι GRANTED ή το άτομο έχει εξαιρεθεί. Ένας συγχρονισμός που ήταν ήδη σε εξέλιξη όταν συνέβη ένα identify(), reset(), wipeData() ή μια αλλαγή συγκατάθεσης απορρίπτεται, και ακολουθεί νέος συγχρονισμός.

Συχνότητα in-app. Μόνο μια αναφορά displayed καταναλώνει το όριο συχνότητας της καμπάνιας και το διάστημα πριν από την επόμενη καμπάνια. Ο renderer του SDK την αναφέρει όταν εμφανίζεται ένα μήνυμα· με το setInAppMessageCallback την αναφέρετε εσείς με trackInAppImpression(campaignId, "displayed"). Η παράδοση ενός μηνύματος στο callback σας δεν καταναλώνει τίποτα, οπότε ένα μήνυμα που αποφασίζετε να μην εμφανίσετε δεν καθυστερεί το επόμενο. Το clearInAppMessageCallback() επιστρέφει την απόδοση στο SDK. Ένας presenter που εγκαθίσταται με setInAppPresenter αναφέρει μέσω του callback onEvent που λαμβάνει ("displayed", "clicked", "dismissed") και δεν πρέπει να καλεί επιπλέον το trackInAppImpression, αλλιώς το μήνυμα μετράει δύο φορές.

Καταγραφή εξαίρεσης. Το optOut() γράφει το γνώρισμα προφίλ tracking_opted_out = true (χωρίς πρόθεμα $) και το optIn() το επαναφέρει σε false - το καθένα μόνο όταν ο διακόπτης πράγματι αλλάζει. Μια καταγραφή που βρίσκεται ακόμη σε αναμονή όταν ο χρήστης της αποσυνδέεται παραμένει δική του και παραδίδεται στο προφίλ του, χωρίς να απευθύνεται ποτέ στο επόμενο άτομο.

Αναφορά API​

Κάθε δημόσια μέθοδος, με τα ονόματα των παραμέτρων της. Οι κλήσεις της μορφής Joryio.x() είναι στατικές και ασφαλείς πριν από την αρχικοποίηση· οι κλήσεις μέσω Joryio.getInstance() χρειάζονται αρχικοποιημένο SDK.

// Setup
Joryio.initialize(context: Context, sdkKey: String?, apiHost: String?, config: JoryioConfig? = JoryioConfig()): Joryio
Joryio.initializeAsync(context: Context, sdkKey: String?, apiHost: String?, config: JoryioConfig? = JoryioConfig(), onReady: ((Joryio) -> Unit)? = null)
Joryio.isInitialized(): Boolean
Joryio.getInstance(): Joryio // throws IllegalStateException before initialize()
Joryio.whenReady(action: ((Joryio) -> Unit)?) // runs now, or as soon as the SDK is initialized
Joryio.SDK_VERSION: String

// Events
Joryio.track(eventName: String?, properties: Map<String, Any?>? = emptyMap())
Joryio.trackScreen(screenName: String?, properties: Map<String, Any?>? = emptyMap())
Joryio.flush()
Joryio.getInstance().ecommerce(config: EcommerceConfig? = null): EcommerceTracker

// Identity and attributes
Joryio.identify(userId: String?)
Joryio.addAlias(label: String?, name: String?) // works before identify() too
Joryio.reset() // logout: forgets the user, new anonymous id
Joryio.setAttribute(key: String?, value: Any?) // a null value removes the attribute
Joryio.setAttributes(attributes: Map<String, Any?>?)
Joryio.incrementAttribute(key: String?, by: Number? = 1)
Joryio.unsetAttribute(key: String?)
Joryio.getInstance().getAnonymousId(): String
Joryio.getInstance().getUserId(): String?
Joryio.getInstance().getSessionId(): String
Joryio.getInstance().getSessionInfo(): SessionInfo
Joryio.getInstance().getIdentity(): Pair<String?, String> // (userId, anonymousId)

// Tracking consent, opt-out and stored data
Joryio.optOut()
Joryio.optIn()
Joryio.isUserOptedOut(): Boolean // the opt-out switch only
Joryio.updateConsent(consent: TrackingConsent?) // GRANTED, DENIED or PENDING
Joryio.getTrackingConsent(): TrackingConsent? // before initialize(): the stored decision, or GRANTED when there is none
Joryio.getInstance().getTrackingConsent(): TrackingConsent
Joryio.wipeData() // safe before initialize()

// Marketing subscription
Joryio.setSubscription(channel: String?, status: String?)
Joryio.addToSubscriptionGroup(groupId: String?, channel: String?)
Joryio.removeFromSubscriptionGroup(groupId: String?, channel: String?)
Joryio.unregisterPush()

// In-app messages
Joryio.getInstance().setInAppMessageCallback(callback: ((InAppCampaign) -> Unit)?, capabilities: List<String>? = null)
Joryio.clearInAppMessageCallback() // hands rendering back to the SDK
Joryio.getInstance().setInAppPresenter(presenter: InAppMessagePresenter?)
Joryio.syncInAppCampaigns()
Joryio.getInstance().trackInAppImpression(campaignId: String?, action: String?, elementId: String? = null)
Joryio.getInstance().submitInAppForm(campaignId: String?, values: Map<String, Any?>?, onResult: ((String) -> Unit)?)
Joryio.resetDisplayedCampaigns() // testing only: needs enableDebug
Joryio.getInstance().evaluateInAppCampaigns() // testing only: needs enableDebug

// Push
Joryio.getInstance().registerPushToken(token: String?, onResult: ((PushRegistrationResult) -> Unit)? = null)
Joryio.getInstance().unregisterPush()
Joryio.getInstance().isPushEnabled(): Boolean
Joryio.getInstance().getDeviceToken(): String?
Joryio.getInstance().getPushPermissionStatus(): PushPermissionStatus
Joryio.getInstance().requestPushPermission(activity: Activity?, callback: ((PushPermissionStatus) -> Unit)? = null)
Joryio.getInstance().reportPushDelivered(trackingId: String?) // only with your own messaging service
Joryio.getInstance().trackPushClick(trackingId: String?) // only when you handle the tap yourself

// SDK authentication
Joryio.setSdkAuthenticationToken(token: String?)
Joryio.setSdkAuthenticationErrorHandler(handler: ((SdkAuthError) -> Unit)?)

// Diagnostics
Joryio.getInstance().getQueueSize(): Int
Joryio.getInstance().apiHost: String
Joryio.getInstance().currentApiEndpoint: String
Joryio.getInstance().lastTransportError: JoryioTransportError?

API δοκιμών και διαγνωστικών​

Δύο ομάδες, και η διαφορά έχει σημασία.

API δοκιμών - αγνοούνται εκτός αν το enableDebug είναι ενεργό. Αλλάζουν ζωντανή κατάσταση, οπότε μια αδέσποτη κλήση σε build παραγωγής θα αλλοίωνε πραγματικά όρια συχνότητας και αναφορές.

ΜέθοδοςΤι κάνει
resetDisplayedCampaigns()Ξεχνά ποιες καμπάνιες in-app έχουν ήδη εμφανιστεί. Η κατάσταση συχνότητας κρατιέται στη συσκευή, οπότε μια αλλαγή στον διακομιστή ΔΕΝ θα την εμφανίσει ξανά.
evaluateInAppCampaigns()Επαναλαμβάνει την απόφαση εμφάνισης χωρίς κλήση δικτύου.

Διαγνωστικά API - πάντα διαθέσιμα, και στην παραγωγή. Μόνο διαβάζουν κατάσταση, οπότε δεν μπορούν να βλάψουν τίποτα.

ΜέθοδοςΑπαντά
getQueueSize()Πόσα συμβάντα περιμένουν αποστολή;
currentApiEndpoint / lastTransportErrorΣε ποιον διακομιστή μιλάμε και απέτυχε η τελευταία κλήση;

Τα getIdentity(), getSessionInfo() και getDeviceToken() ΔΕΝ ανήκουν σε καμία ομάδα - είναι κανονικό API. Η ανάγνωση της ταυτότητας της συσκευής είναι φυσιολογική ενέργεια για μια εφαρμογή.

Παράδοση γνωρισμάτων​

Το setAttributes είναι ανθεκτικό. Μια εγγραφή μένει σε ουρά μέχρι να την επιβεβαιώσει ο server, οπότε ένα γνώρισμα που ορίζεται ενώ η συσκευή είναι εκτός σύνδεσης παραδίδεται όταν επανέλθει η σύνδεση αντί να χαθεί.

  • Ξαναδοκιμάζεται στο επόμενο setAttributes, στη μετάβαση στο προσκήνιο και πριν από έναν συγχρονισμό in-app.
  • Ομαδοποιείται - μια ριπή κλήσεων γίνεται ένα αίτημα (παράθυρο 800ms). Μια μεμονωμένη εγγραφή εξακολουθεί να στέλνεται άμεσα.
  • Αποθηκεύεται στον κρυπτογραφημένο χώρο της πλατφόρμας (iOS Keychain, Android EncryptedSharedPreferences), ώστε μια εγγραφή να επιβιώνει αν τερματιστεί η διεργασία. Διαγράφεται μόλις την επιβεβαιώσει ο server και εκκαθαρίζεται από τα optOut() και wipeData().

Τα γνωρίσματα χρησιμοποιούνται επίσης για τη στόχευση in-app. Το προφίλ στον server είναι η αυθεντική πηγή: μόνο εγγραφές που ο server δεν έχει ακόμη επιβεβαιώσει μπορούν να το παρακάμψουν, και αυτό εμποδίζει δύο συσκευές της ίδιας επαφής να διαφωνούν για το ποια είναι αυτή η επαφή.

Συνδρομή μάρκετινγκ​

Αν επιτρέπεται να στέλνουμε μηνύματα σε αυτό το άτομο σε ένα κανάλι. Διαφορετική συναίνεση από το optOut(), που αφορά αν επιτρέπεται να το παρακολουθούμε - καμία δεν συνεπάγεται την άλλη.

Αυτό γράφει ένα κέντρο προτιμήσεων μέσα στην εφαρμογή.

Joryio.setSubscription("email", "unsubscribed")
Joryio.addToSubscriptionGroup("list_abc123", "email")

Κανάλια: email, sms, whatsapp, push, viber. Καταστάσεις: optedIn, subscribed, unsubscribed.

Απαιτείται αναγνωριστικό - η κλήση παραλείπεται, με καταγραφή σφάλματος, αν δεν υπάρχει ούτε user id ούτε anonymous id.

Η απεγγραφή λειτουργεί πάντα. Τα setSubscription(channel, "unsubscribed") και removeFromSubscriptionGroup γίνονται δεκτά και παραδίδονται ακόμη κι όταν το άτομο έχει εξαιρεθεί (opt-out), η συγκατάθεση είναι DENIED ή PENDING, και μετά από wipeData() - ένα αίτημα διακοπής μηνυμάτων δεν καθυστερεί ούτε απορρίπτεται ποτέ. Η εγγραφή (subscribed, optedIn, addToSubscriptionGroup) απορρίπτεται όσο ισχύει εξαίρεση ή άρνηση και κρατιέται όσο η συγκατάθεση εκκρεμεί. Μια νεότερη αλλαγή του ίδιου καναλιού ή της ίδιας ομάδας αντικαθιστά μια παλαιότερη που περιμένει ακόμη να σταλεί, και μια αλλαγή που έγινε πριν από το identify() και στέλνεται μετά από αυτό φέρει και το user id.