دمج Android SDK
SDK أصلي لـ Android لتتبع الأحداث وإدارة جلسات المستخدمين وإرسال إشعارات Push عبر FCM وعرض رسائل In-app.
الخصائص
- خفيف: أثر محدود.
- سريع: محسّن للأداء.
- دعم عدم الاتصال: طابور أحداث مبني على SQLite.
- إعادة محاولة تلقائية: تراجع أسي عند الإخفاق.
- دفعات: تجميع فعال للأحداث، 50 حدثًا/5 ثوانٍ.
- رسائل In-app: رسائل أصلية و‑HTML، 5 أنواع، مع حدود تكرار.
- إشعارات Push: Firebase Cloud Messaging، FCM.
- الخصوصية أولًا: متوافق مع GDPR ويحترم موافقة المستخدم.
- Android 6.0+: دعم API 23+.
المتطلبات
- Android 6.0، API level 23، أو أحدث - الحد الأدنى للتخزين المشفّر (
EncryptedSharedPreferences)، وهو نفس الحد الذي يتطلبه Firebase Cloud Messaging. - Kotlin 1.9.20 أو أحدث.
- Gradle 8.0 أو أحدث.
إعداد SDK المحلي
للبُنى المحلية، اضبط مسار Android SDK في local.properties:
sdk.dir=/Users/your-user/Library/Android/sdk
أو صدّر ANDROID_HOME/ANDROID_SDK_ROOT قبل تشغيل Gradle.
التثبيت
Gradle، موصى به
أضف إلى build.gradle.kts:
تأتي الحزمة في artifactين. أضف واحدًا منهما - إذ يحتوي artifact الواجهة على الأساسي، فلا تحتاج مطلقًا إلى الإعلان عنهما معًا:
dependencies {
// كل شيء، بما في ذلك عرض الرسائل داخل التطبيق. ابدأ من هنا.
implementation("io.joryio:joryio-android-ui:1.0.0")
}
dependencies {
// التتبع والهوية والإشعارات فقط - دون عرض داخل التطبيق ودون ربط WebView.
// اختر هذا إذا كان تطبيقك لا يستخدم الرسائل داخل التطبيق، أو إذا كنت تعرضها بنفسك.
implementation("io.joryio:joryio-android:1.0.0")
}
أو باستخدام Groovy (build.gradle):
dependencies {
implementation 'io.joryio:joryio-android-ui:1.0.0'
}
يعتمد joryio-android-ui على joryio-android، لذا يمنحك سطر واحد كليهما ولا
يمكن أن يفترقا - فأنت تذكر إصدارًا واحدًا فقط.
خذ الـ artifact الأساسي وحده إذا كان تطبيقك لا يحتاج الرسائل داخل التطبيق، أو إذا اعترضت مراجعة أمنية على إمكانية ربط WebView أصلًا. عندها لن تُعرض الرسائل داخل التطبيق فحسب، ويعمل كل شيء آخر دون تغيير.
بدء سريع
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 = "api-eu1.joryio.com"
)
}
}
اعثر على مفتاح SDK في لوحة Joryio ضمن Settings ← Apps ← [Your App] ← 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. تعريف المستخدمين
// Identify a user
Joryio.identify("user-123")
Joryio.getInstance().setAttributes(mapOf(
"email" to "user@example.com",
"name" to "John Doe",
"plan" to "premium"
))
// Set attributes later
Joryio.setAttribute("last_purchase", Date())
Joryio.incrementAttribute("lifetime_value", 99.99)
// On logout
Joryio.reset()
بيانات الجلسة التلقائية
يثري 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$timezonecountry، ISO-3166-1 alpha-2، مشتقة من IP عند بدء الجلسة.
إشعارات Push
فعّل إشعارات Push باستخدام 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
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)
}
}
// Check if push is enabled
val isEnabled = Joryio.getInstance().isPushEnabled()
// Unregister when needed
Joryio.getInstance().unregisterPush()
4. اضبط FCM في لوحة التحكم
لإرسال إشعارات Push، اضبط بيانات Firebase:
- انتقل إلى Settings ← Apps ← [Your App].
- افتح إعداد إشعارات Push.
- ارفع service account JSON الخاص بـ Firebase، من Firebase console: Project settings ← Service accounts ← Generate new private key.
- احفظ الإعداد.
رسائل In-app
تعرض حزمة SDK رسائل داخل التطبيق نيابةً عنك. بعد التهيئة تظهر الحملات المؤهلة من تلقاء نفسها، وتُقاس مرات الظهور والنقرات والإغلاق تلقائيًا - لا شيء لتوصيله.
رموز التسليم (delivery tokens)
عندما يقدّم الخادم حملة مؤهّلة، فإنه يصدر لها أيضًا رمز تسليم موقّعًا وقصير الأجل. يعيد الـ SDK إرسال هذا الرمز عند الإبلاغ عن ظهور أو نقرة أو إغلاق، ويتحقق الخادم من التوقيع قبل تسجيل أي شيء.
لا يتعيّن عليك فعل أي شيء - الـ SDK يتولى ذلك نيابة عنك. وهو موثّق هنا لأنه يغيّر ما يحدث للعميل الذي لا يرسل رمزًا:
POST /v1/in-app/track (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }
الرمز هو ما يجعل الظهور جديرًا بالثقة: بدونه، يستطيع أي شخص يملك مفتاح الـ SDK - وهو مضمّن في كل تطبيق وكل صفحة - الإبلاغ عن ظهورات ونقرات لحملة لم تُعرض قط، وستحتسبها تقاريرك.
إذا توقّف تسجيل ظهورات in-app، فتأكد من أن التطبيق مبني على إصدار حديث من الـ SDK: الإصدار الذي بُني قبل وجود رموز التسليم لا يرسل رمزًا، وسيرفض الخادم ظهوراته.
نوعان من المحتوى
تصل كل حملة بأحد شكلين من المحتوى، وتعرض حزمة SDK كلًا منهما بطريقة مختلفة:
| المحتوى | ما هو | كيف يُعرض |
|---|---|---|
| الأصلي (Native) | بيانات منظمة - عنوان ونص وصورة وأزرار | عناصر Android حقيقية، بسمة التطبيق وخطوطه ووضعه الداكن و‑TalkBack. بدون WebView. |
| HTML | HTML و‑CSS و‑JavaScript كتبها المؤلف | WebView داخل الرسالة. |
السماح برسائل HTML
رسائل HTML معطلة افتراضيًا. تُنفذ رسالة HTML شيفرة JavaScript كتبها المؤلف داخل تطبيقك، لذا فإن تفعيلها قرار يتخذه فريق التطبيق - وليس شيئًا يُفعَّل من منصة تسويق:
val config = JoryioConfig(
allowHtmlJsInAppMessages = true // الافتراضي: false
)
Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "api-eu1.joryio.com",
config = config
)
تركه معطلًا لا يعطل الرسائل داخل التطبيق. تستمر الرسائل الأصلية في الظهور، لأنها بيانات يرسمها تطبيقك بعناصره الخاصة - دون أي مفسّر. تُتجاهل حملات HTML وتُسجَّل، فيعرض التطبيق الذي لم يفعّل الإعداد لا شيء بدلًا من رسالة معطوبة.
إذا كانت سياسة الأمان لديك تمنع تنفيذ HTML مكتوب خارجيًا داخل العملية، فاترك الإعداد معطلًا وأنشئ حملاتك كرسائل أصلية.
تولي العرض بنفسك
عيّن callback لرسم واجهتك الخاصة. هذا يتجاوز عرض حزمة SDK، فلن تحصل على نسختين من الرسالة:
Joryio.getInstance().setInAppMessageCallback { campaign ->
showInAppMessage(campaign) // واجهتك
}
// أبلغ عما حدث - تقيس الحزمة تلقائيًا الرسائل التي تعرضها هي فقط
Joryio.getInstance().trackInAppImpression(campaignId, "viewed")
Joryio.getInstance().trackInAppImpression(campaignId, "clicked")
Joryio.getInstance().trackInAppImpression(campaignId, "dismissed")
عرض الرسائل بنفسك
تتولى الحزمة رسم الرسائل الأصلية، لكن يمكنك تولّي الأمر بالكامل، وهو ما يقابل custom view factory:
Joryio.getInstance().setInAppMessageCallback { campaign ->
// ارسمها كما تشاء
}
ضبط callback يتجاوز المُصيِّر المدمج بدل أن يعمل بجانبه، فتظهر الرسالة مرة واحدة لا مرتين.
الحقل campaign.content جاهز بالفعل: يُصيَّر Liquid في الخادم وتصل الحقول الأصلية
نصًا عاديًا (لا تمررها إلى WebView؛ فهي بلا HTML-escape تحديدًا لأنها معدّة
لعناصر النص). سجِّل ما تعرضه عبر
trackInAppImpression(campaignId, "impression" | "clicked" | "dismissed").
أنواع الرسائل
تدعم الحزمة 5 أنواع من الرسائل:
- Modal - وسط الشاشة مع خلفية معتمة
- Banner - أعلى الشاشة
- Slide-Up - إشعار صغير من الأسفل
- Full-Screen - رسالة تملأ الشاشة
- Custom - التطبيق يحدد الموضع
خيارات الإعداد
خصص سلوك SDK باستخدام JoryioConfig:
val config = JoryioConfig(
// Initial user ID (optional)
userId = "user-123",
// Event batching
batchSize = 50, // Events per batch
flushInterval = 5000, // Flush interval in ms (5s)
// Session management
sessionTimeout = 1800000, // Session timeout in ms (30 min)
trackSessionStart = true, // Auto-track session start
// Network retry
maxRetries = 3, // Max retry attempts
// Privacy controls
optOut = false, // Opt out of tracking
trackingConsent = TrackingConsent.GRANTED,
// Debugging
enableDebug = false, // Enable debug logging
logLevel = LogLevel.ERROR // Log level
)
Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "api-eu1.joryio.com",
config = config
)
موافقة التتبع
enum class TrackingConsent {
GRANTED, // Full tracking allowed
PENDING, // Waiting for user decision
DENIED // User denied tracking
}
مستويات السجل
enum class LogLevel {
VERBOSE, // All logs
DEBUG, // Debug and above
INFO, // Info and above
WARN, // Warnings and errors
ERROR // Errors only
}
خصائص متقدمة
إدارة الجلسة
تتبع الجلسات تفاعل المستخدم تلقائيًا:
// Sessions are managed automatically with 30-minute timeout
// Get current session ID
val sessionId = Joryio.getInstance().getSessionId()
// Sessions refresh on user activity
سمات المستخدم
// 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")
عناصر تحكم الخصوصية
// Stop collecting. PERSISTED - survives an app restart.
Joryio.getInstance().optOut()
// Opt back in
Joryio.getInstance().optIn()
// Check opt-out status
if Joryio.getInstance().isUserOptedOut() {
print("User has opted out")
}
// Delete everything the SDK stored on this device.
// SEPARATE from optOut(): "stop collecting" and "delete what you have" are
// different requests. This is the one an erasure request needs. It does NOT
// opt the user out - call optOut() as well if that is also intended.
Joryio.getInstance().wipeData()
// Get identity info
val (userId, anonymousId) = Joryio.getInstance().getIdentity()
println("User: ${userId ?: "anonymous"}, Anonymous ID: $anonymousId")
What optOut() does, precisely:
| stops collection | yes - track, identify and setAttributes all become no-ops |
| survives a restart | yes - the flag is stored on the device and read before anything is collected |
| drops what is already queued | yes - queued events and un-acked attribute writes are discarded, not delivered later |
| tells the server | yes - one final $tracking_opted_out profile attribute, best-effort, sent while sending is still permitted |
| deletes stored data | no - use wipeData() |
The $tracking_opted_out attribute is a record, not enforcement: it lands on
the profile so campaigns can exclude on it. Server-side suppression is a
separate setting.
تم التحديث بالإنجليزية - الترجمة قيد الإعداد.
تفريغ الطابور يدويًا
// Flush events immediately
Joryio.flush()
// Useful before app termination
override fun onDestroy() {
super.onDestroy()
Joryio.flush()
}
أفضل الممارسات
1. هيئ مبكرًا
هيئ SDK في فئة Application:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "api-eu1.joryio.com"
)
}
}
2. تتبع مشاهدات الشاشة
استخدم تتبع الشاشات للتنقل:
override fun onResume() {
super.onResume()
Joryio.trackScreen(this::class.simpleName ?: "Unknown")
}
3. تعامل مع تسجيل خروج المستخدم
أعد الضبط دائمًا عند تسجيل الخروج:
fun logout() {
// Clear user session
clearUserSession()
// Reset SDK
Joryio.reset()
}
مرجع API
تتبع الأحداث
// Track event
Joryio.track(
eventName: String,
properties: Map<String, Any?> = emptyMap()
)
// Track screen view
Joryio.trackScreen(
screenName: String,
properties: Map<String, Any?> = emptyMap()
)
// Flush events immediately
Joryio.flush()
هوية المستخدم
// Identify user
Joryio.identify(
userId: String
)
// Set attributes
Joryio.getInstance().setAttributes(
attributes: UserAttributes
)
// Alias user
Joryio.alias(userId: String)
// Reset user (logout)
Joryio.reset()
// Get IDs
Joryio.getInstance().getAnonymousId(): String
Joryio.getInstance().getUserId(): String?
Joryio.getInstance().getSessionId(): String
رسائل In-app
// Set message callback
Joryio.getInstance().setInAppMessageCallback { campaign ->
// Handle message display
}
// Track impressions
Joryio.getInstance().trackInAppImpression(
campaignId: String,
action: String
)
إشعارات Push
// Register token
Joryio.getInstance().registerPushToken(token: String)
// Check status
Joryio.getInstance().isPushEnabled(): Boolean
// Unregister
Joryio.getInstance().unregisterPush()
استكشاف الأخطاء وإصلاحها
الأحداث لا تظهر
- تحقق من صحة مفتاح SDK:
jry_sdk_android_*. - فعّل سجل التصحيح:
enableDebug = true. - افحص logcat بحثًا عن أخطاء.
- تحقق من أذونات الشبكة في manifest.
- استدعِ
flush()للإرسال فورًا.
Push لا يعمل
- تحقق من ضبط Firebase بشكل صحيح.
- تحقق من رفع Firebase service account JSON في لوحة التحكم.
- تأكد من تسجيل رمز الجهاز.
- اختبر باستخدام Firebase Console أولًا.
أخطاء البناء
- تأكد من أن الحد الأدنى لإصدار SDK هو 23.
- زامن تبعيات Gradle.
- نظف المشروع وأعد بناءه.
تتبع E-Commerce
يتضمن Android SDK متتبعًا مدمجًا للتجارة الإلكترونية لأحداث المنتجات والسلة وإتمام الشراء والطلبات. راجع دليل تتبع E-Commerce العابر لـ SDKs لمعرفة API الكاملة مع أمثلة Kotlin.
الخطوات التالية
- iOS SDK: دمج iOS SDK.
- Web SDK: دمج Web SDK.
- دليل إشعارات Push: تعرّف إلى حملات Push.
- دليل حملات In-app: إنشاء رسائل داخل التطبيق.
- Custom Events: تتبع الأحداث المخصصة.
- E-Commerce API: دمج التجارة الإلكترونية من الخادم.
واجهات الاختبار والتشخيص
مجموعتان، والفرق بينهما مهم.
واجهات الاختبار - تُتجاهَل ما لم يكن enableDebug مفعّلًا. فهي تغيّر حالة
حيّة، ومن ثم فإن استدعاءً عارضًا في نسخة الإنتاج سيفسد حدود التكرار والتقارير
الحقيقية.
| الدالة | ما الذي تفعله |
|---|---|
resetDisplayedCampaigns() | تنسى أي حملات in-app عُرضت بالفعل. حالة التكرار محفوظة على الجهاز، لذا فإن تغيير الحملة على الخادم لن يعيد عرضها. |
evaluateInAppCampaigns() | تعيد تنفيذ قرار العرض على الحملات المُزامنة دون طلب شبكة. |
واجهات التشخيص - متاحة دائمًا، بما في ذلك الإنتاج. فهي تقرأ الحالة فقط ولا يمكنها إحداث ضرر.
| الدالة | تجيب عن |
|---|---|
getQueueSize() | كم حدثًا ينتظر الإرسال؟ |
currentApiEndpoint / lastTransportError | مع أي خادم نتحدث، وهل فشل آخر طلب؟ |
الدوال getIdentity() وgetSessionInfo() وgetDeviceToken() ليست ضمن أي من المجموعتين - فهي واجهة عادية. قراءة هوية الجهاز أمر طبيعي للتطبيق.
Attribute delivery
setAttributes is durable. A write is queued until the server acknowledges it,
so an attribute set while the device is offline is delivered when connectivity
returns rather than dropped.
- Retried on the next
setAttributes, on foreground, and before an in-app sync. - Batched - a burst of calls becomes one request (800ms window). A single write still goes out promptly.
- Persisted in the platform's encrypted store (iOS Keychain, Android
EncryptedSharedPreferences), so a write survives the process being killed.
Cleared the moment the server acks, and purged by
optOut()andwipeData().
Attributes are also used for in-app targeting. The server profile is authoritative: only writes the server has not yet acknowledged can override it, which is what keeps two devices belonging to the same contact from disagreeing about who that contact is.
تم التحديث بالإنجليزية - الترجمة قيد الإعداد.