كيفية ربط المعلمات المخصصة عند إنشاء رموز الاستجابة السريعة؟ يتطلب ربط المعلمات المخصصة لتوجيه التطبيقات إلحاق مفاتيح استعلام عنوان URL منظمة برابط صفحة هبوط مستضافة، والتقاط تلك المفاتيح عبر طبقة تكامل الويب، وإرسال حمولة البيانات التي تم التحقق منها إلى وحدات التحكم في التنقل الأصلية عند تنشيط التطبيق مباشرة أو عند التشغيل بعد التثبيت.
يعد التوجيه داخل التطبيق عبر رموز الاستجابة السريعة بنية تنقل بين العميل والخادم تدمج معلمات الوجهة المنظمة في عنوان URL للرمز الشريطي ثنائي الأبعاد. ومن خلال حل هذه المعلمات عبر الروابط العميقة المباشرة لنظام التشغيل عند تثبيت التطبيق، أو من خلال الاستعادة المؤجلة المدعومة من النظام الأساسي عند تثبيت التطبيق حديثاً من متجر التطبيقات، يمكن فرق الهندسة توجيه المستخدمين مباشرة إلى شاشات محددة داخل التطبيق بدلاً من عرض الصفحة الرئيسية الافتراضية.
| المصطلح | التعريف | الكيان ذو الصلة | دور نية البحث |
|---|---|---|---|
| إنشاء رمز الاستجابة السريعة للتطبيق | سير العمل الهندسي لترميز عناوين URL القابلة للتوجيه في رموز شريطية ثنائية الأبعاد للتنقل عبر الأجهزة المحمولة. | استعادة المشهد | تعليمي / تقني |
| رمز الاستجابة السريعة للتطبيق | أصل رمز شريطي تم تكوينه لتشغيل التطبيق الأصلي أو الانتقال عبر متجر التطبيقات. | الروابط العامة / روابط التطبيقات | تقني / تجاري |
| رمز الاستجابة السريعة الديناميكي | رمز شريطي يشير إلى نقطة نهاية توجيه وسيطة يمكن تحديث حمولة وجهتها من جانب الخادم. | توجيه معرف الموارد المنتظم (URI) | تجاري |
| تتبع رمز الاستجابة السريعة | قياس طلبات الدخول للتوجيه وتفعيل الشاشات اللاحقة داخل التطبيق. | إحصائيات وقنوات التتبع | تجاري |
نظرة عامة على البنية: كيفية إنشاء رمز الاستجابة السريعة لاستعادة مشهد التطبيق

مشكلة مغادرة الشاشة الرئيسية: لماذا تؤدي عمليات إعادة التوجيه العامة للمتجر إلى كسر رحلات المستخدم عالية النية
غالبًا ما يتم نشر رموز الاستجابة السريعة المادية في بيئات سياقية عالية النية: يقوم متسوق التجزئة بمسح علامة رف لعرض مواصفات المنتج، أو يقوم رواد المطعم بمسح خيمة طاولة للطلب من فئة قائمة طعام معينة، أو يقوم لاعب بمسح رمز شريطي للدعوة للانضمام إلى ردهة لعبة متعددة اللاعبين. عندما يقوم الرمز الشريطي بتضمين رابط متجر تطبيقات عام أو نطاق غير معلماتي، يتم فقدان نية المستخدم السياقية أثناء الانتقال من المسح المادي إلى وقت تشغيل التطبيق الأصلي.
عند تشغيل التطبيق، يتم إسقاط المستخدم على الشاشة الرئيسية الافتراضية ذات المستوى الأعلى. إن إجبار المستخدم على التنقل يدويًا عبر أشجار الفئات المعقدة، أو إعادة إدخال استعلامات البحث، أو كتابة معرفات الغرف يفرض احتكاكًا معرفيًا وميكانيكيًا كبيرًا. تعالج استعادة المشهد داخل التطبيق هذه المشكلة عن طريق ترميز حالة الوجهة في عنوان URL لرمز الاستجابة السريعة وإعادة بناء عرض الهدف برمجياً داخل العميل الأصلي.
آليات التوجيه ثنائية الحالة: التنبيه المباشر مقابل استعادة المشهد المؤجل

عندما يقوم المستخدم بمسح رمز الاستجابة السريعة لتوجيه التطبيق، يكون جهاز الجوال في إحدى حالتي تثبيت. تتعامل بنية التوجيه الكاملة مع كلا دورات الحياة من خلال مسارات تقنية متميزة:
- توجيه التنبيه المباشر (التطبيق مثبت مسبقًا): عندما يكون التطبيق المستهدف موجودًا بالفعل على الجهاز، يؤدي مسح رمز الاستجابة السريعة إلى تشغيل رابط عميق لنظام التشغيل—تحديدًا روابط أبل العامة (Apple Universal Links) على نظام iOS أو روابط تطبيقات أندرويد (Android App Links) على نظام Android. تتطلب كلتا الآليتين استضافة ملفات ربط نطاق محققة تشفيرياً: يتطلب iOS ملف
apple-app-site-association(AASA) مقترنًا بامتيازAssociated Domainsفي Xcode، بينما يتطلب Android ملف Digital Asset Links/.well-known/assetlinks.jsonيطابق بصمة شهادة توقيع التطبيق SHA-256 ومرشحات نية البيان المتحقق منها. عند التحقق، يقوم نظام التشغيل بتنبيه التطبيق ويمرر عنوان URI مباشرة إلى مفوض دورة حياة العميل. - استعادة المشهد المؤجل (التطبيق غير مثبت): في حال عدم وجود التطبيق، يتم توجيه المستخدم عبر صفحة هبوط ويب وسيطة إلى متجر تطبيقات الأساس. ونظرًا لعدم تثبيت الملف الثنائي الأصلي بعد لتلقي نية نظام التشغيل، يجب تجهيز معلمات التوجيه قبل إعادة توجيه المتجر واستردادها أثناء الإطلاق الأول للتطبيق (البدء البارد في اليوم 0) بعد اكتمال التثبيت.
حدود الاستعادة الخاصة بالمنصة: مرجع تثبيت جوجل بلاي، روابط أبل العامة، ومحولات حزمة تطوير البرمجيات (SDK)
بعد قيام المستخدم بتثبيت التطبيق من متجر تطبيقات، يعتمد استرداد معلمات الإحالة والتوجيه على المنصة والآلية التي ينفذها التطبيق أو حزمة تطوير برمجيات الإحالة (SDK). إن تمرير معلمة route أو id عبر عنوان URL للويب لا يضمن، في حد ذاته، بقاء هذه المعلمة بعد تثبيت التطبيق من المتجر:
- أندرويد وجوجل بلاي: يمكن لـ واجهة برمجة تطبيقات مرجع تثبيت جوجل بلاي (Google Play Install Referrer API) توفير بيانات تعريف الإحالة المدعومة المرتبطة بتثبيت متجر بلاي. تنطبق هذه الآلية بشكل خاص على عمليات التثبيت المكتملة عبر مسار متجر جوجل بلاي الرسمي، وليس عمليات التحميل الجانبي لملفات APK التابعة لجهات خارجية أو المتاجر البديلة. يجب على التطبيق تكوين واختبار حمولة المرجع الفعلي والتحقق من أن المعلمات المتوقعة تنجو من تدفق إعادة التوجيه والتثبيت.
- نظام iOS ومتجر التطبيقات: لا يقوم متجر تطبيقات أبل تلقائياً برفع معلمات استعلام صفحة هبوط الويب العشوائية إلى ثنائي التطبيق المثبت حديثاً. تدعم الروابط العامة التوجيه المباشر عندما يكون التطبيق مثبتًا بالفعل وكان تكوين النطاق المرتبط صالحًا؛ ومع ذلك، فهي لا تضمن وحدها استرداد المعلمات المؤجلة بعد التثبيت الجديد.
- حزم تطوير برمجيات الإحالة (SDKs): استخدم فقط آليات الاسترداد المسجلة لإصدار حزمة SDK الدقيق والمنصة التي يتم نشرها. تحقق من التنفيذ، وجمع البيانات، وتداعيات الخصوصية بدلاً من افتراض أن حزمة SDK يمكنها استرداد كل معلمة توجيه عبر كل متصفح ومسار تثبيت.
إحالة iOS وحدود الخصوصية: الالتزام بقواعد أبل لمكافحة بصمات الأجهزة
يجب ألا تعتمد عمليات توجيه iOS واستعادة المشهد المؤجل على بصمات الأجهزة المحظورة لتحديد هوية المستخدم أو الجهاز عبر التطبيقات أو مواقع الويب. بموجب إرشادات خصوصية المستخدم واستخدام البيانات من أبل واتفاقية ترخيص برنامج مطوري أبل، يُحظر على المطورين منعًا باتًا اشتقاق معرف فريد من إشارات الجهاز أو المتصفح أو التكوين أو الموقع أو الشبكة لهذا الغرض. لا تجعل تفويض شفافية تتبع التطبيقات (ATT) البصمات المحظورة بخلاف ذلك جائزة.
استخدم الروابط العامة لتوجيه التطبيق المباشر المتحقق منه عند تثبيت التطبيق. لاستعادة المشهد بعد التثبيت على نظام iOS، استخدم آلية موثقة تم مراجعة سلوكها لإصدار حزمة SDK المنشورة ومتطلبات أبل المعمول بها. راجع إفصاحات الخصوصية وإعلانات واجهة برمجة التطبيقات للأسباب المطلوبة لحزمة SDK حيثما كان ذلك منطبقًا، ولكن لا تتعامل مع وجود بيان خصوصية (PrivacyInfo.xcprivacy) كدليل على أن تطبيق الإحالة متوافق.
إذا كانت حزمة SDK المحددة تعتمد على طريقة مطابقة لا يمكن التحقق من السماح بها، فلا توصي بهذه الطريقة لنشر iOS. وحيثما تعذر على أي آلية مسموح بها إنشاء سياق التوجيه المؤجل بشكل موثوق، حافظ على تدفق الإعداد العادي، ووجه المستخدم إلى عرض رئيسي افتراضي آمن، وقم بتعليق التثبيت على أنه غير مُحال بدلاً من تصنيع مطابقة غير متحقق منها.
تصور خط أنابيب استعادة مشهد رمز الاستجابة السريعة ثنائي الحالة
يوضح رسم تخطيطي للبنية أدناه المراحل التالية حيث يتفرع رمز الاستجابة السريعة المعلماتي إلى تنبيه نظام تشغيل مباشر أو استعادة متجر مؤجلة قبل التقاء في مدقق المسار من جانب العميل وبوابة المصادقة:
[User Scans QR Code] ──> URL (?route=product_detail&id=sku_8842&channelCode=pos_01)
│
┌────┴────────────────────────────────────────┐
▼ (App Already Installed) ▼ (App Not Installed)
[OS Universal Link / App Link] [Intermediary H5 Landing Page]
│ │
▼ ▼
[Hot/Cold Wakeup Callback] [Store Download & First Launch]
(onNewIntent / continueUserActivity) (Platform-Supported Deferred Recovery)
│ │
└────────────────────┬────────────────────────┘
▼
[Client Route Validator & Auth Gate]
│
┌────────────────────┴────────────────────────┐
▼ (Allowlisted & Authenticated) ▼ (Invalid Route / Unmatched)
[Navigate to Product Detail Screen] [Fallback to Default Home Screen]
الخطوة 1: تصميم مخطط معلمات التوجيه المخصصة
هيكلة حمولات التوجيه القابلة للتنفيذ مقابل علامات الإحالة السلبية
عند إنشاء رمز الاستجابة السريعة لتوجيه التطبيق، يجب على الفرق الهندسية إنشاء عقد بيانات نظيف من طبقتين يفصل بين مفاتيح استعلام عنوان URL الخارجي المدمجة وحمولة JSON الداخلية المعيارية:
| طبقة العقد | اتفاقية تسمية الحقول | مثال تمثيلي | الغرض المعماري |
|---|---|---|---|
| طبقة استعلام عنوان URL الخارجي | route، id، channelCode |
?route=product_detail&id=sku_8842&channelCode=pos_01 |
يقلل من طول أحرف معرف الموارد (URI) للحفاظ على مصفوفة رمز الاستجابة السريعة المطبوعة مدمجة |
| الطبقة الداخلية المعيارية | route_key، target_id، channel_code، fallback_route |
{"route_key": "product_detail", "target_id": "sku_8842", ...} |
كائن أساسي موحد يتم التحقق منه بواسطة موجهات H5 وعملاء النظام الأصليين |
| سجل أمان العميل | PROTECTED_ROUTES (ثابت داخلي) |
setOf("room_lobby", "promo_campaign") |
يحدد متطلبات المصادقة داخليًا؛ ولا يتم الوثوق به مطلقًا من عناوين URL الخارجية |

في هذا النموذج ذي الطبقتين:
- علامات الإحالة السلبية (
channelCodechannel_code) يتم استهلاكها بواسطة خطوط تحويل التحليلات لقياس حجم التحويل التسويقي دون تغيير حالة واجهة مستخدم العميل. - معلمات التوجيه القابلة للتنفيذ (
routeroute_keyوidtarget_id) توجيه وحدة التحكم في التنقل الخاصة بالعميل الأصلي بشأن الشاشة المراد تركيبها والكيان المراد جلبه. - عزل سياسة المصادقة: ما إذا كان المسار يتطلب مصادقة (
requires_auth) يجب استنتاجه بدقة من سجل المسار الموثوق الداخلي لتطبيق العميل بدلاً من قبوله من معلمة استعلام رمز الاستجابة السريعة الخارجي.
فرض ترميز النسبة المئوية IETF RFC 3986 القياسي القياسي وقيود حجم الحمولة
يجب أن تلتزم جميع معلمات التوجيه الملحقة بعنوان URL الخاص بوجهة رمز الاستجابة السريعة بناء الجملة العام لمعرفات الموارد (URI) المحدد في IETF RFC 3986:
- تجنب المحددات المتداخلة: لا تقم بتضمين سلاسل JSON الخام (
?data={"route":"product","id":123}) مباشرة في سلاسل استعلام عنوان URL. غالبًا ما تؤدي الأقواس وعلامات التنصيص والنقاط الرأسية غير المهربة إلى كسر محللات عناوين URL لمتصفحات داخل التطبيق وزيادة كثافة وحدة الرمز الشريطي. - تسوية أزواج المفتاح والقيمة: قم بتَمثِيل سياق التوجيه الخارجي كمعلمات استعلام مسطحة ومشفرة بالنسبة المئوية (
https://promo.example.com/open?route=product_detail&id=sku_8842&channelCode=pos_01). - فرض حدود الأحرف الصارمة: قصر قيم
routeوidعلى الأحرف الأبجدية الرقمية الآمنة لعناوين URL والشرطات والشرطات السفلية (^[A-Za-z0-9_-]{1,64}$).
تحديد عقد القائمة البيضاء للمسار من جانب العميل
بمجرد استيعاب معلمات عنوان URL الخارجي وتطبيعها في بنية الحمولة الداخلية، يجب أن تتوافق مع عقد مخطط صارم. إذا كان route_key غير معروف، أو إذا كان المسار الخاص بكيان معين (product_detail أو room_lobby أو promo_campaign) يفتقر إلى target_id المطلوب، فيجب على المعياري خفض الحمولة إلى home_default مع target_id: null.
يحدد مخطط JSON أدناه الهيكل الأساسي لحمولة استعادة المشهد المعيارية:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "QrSceneRestorationPayload",
"description": "Canonical, normalized routing payload. Authentication policy is derived separately from the application's trusted route registry.",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"route_key",
"target_id",
"fallback_route"
],
"properties": {
"schema_version": {
"const": "1.0.0"
},
"route_key": {
"type": "string",
"enum": [
"product_detail",
"room_lobby",
"promo_campaign",
"home_default"
]
},
"target_id": {
"type": ["string", "null"],
"pattern": "^[A-Za-z0-9_-]{1,64}$"
},
"channel_code": {
"type": "string",
"pattern": "^[A-Za-z0-9_.-]{1,64}$"
},
"fallback_route": {
"const": "home_default"
}
},
"allOf": [
{
"if": {
"properties": {
"route_key": {
"enum": [
"product_detail",
"room_lobby",
"promo_campaign"
]
}
},
"required": ["route_key"]
},
"then": {
"properties": {
"target_id": {
"type": "string"
}
}
}
},
{
"if": {
"properties": {
"route_key": {
"const": "home_default"
}
},
"required": ["route_key"]
},
"then": {
"properties": {
"target_id": {
"type": "null"
}
}
}
}
],
"examples": [
{
"schema_version": "1.0.0",
"route_key": "product_detail",
"target_id": "sku_8842",
"channel_code": "pos_01",
"fallback_route": "home_default"
}
]
}
الخطوة 2: التقاط وإعادة توجيه سياق التوجيه على صفحة الهبوط
تحليل مفاتيح التوجيه المدرجة في القائمة البيضاء على صفحة هبوط H5
عندما يقوم مستخدم ليس لديه التطبيق مثبتًا بمسح رمز الاستجابة السريعة، يفتح متصفح الجوال صفحة هبوط H5 الوسيطة المستضافة. يستخرج نص صفحة الهبوط مفاتيح الاستعلام الخارجية (route و id و channelCode)، ويرفض المعلمات المكررة الغامضة، ويقوم بتطبيعها في البنية المعيارية المحددة في الخطوة 1.
فرض أداة التطبيع بلغة JavaScript أدناه تحديد مفتاح فردي، وحدود التعابير النمطية، والخفض الشرطي لـ home_default مع اعتبار المعلمات المحجوزة المكررة غير صالحة:
const query = new URLSearchParams(window.location.search);
const routeValues = query.getAll("route");
const idValues = query.getAll("id");
const channelValues = query.getAll("channelCode");
const allowedRoutes = new Set([
"product_detail",
"room_lobby",
"promo_campaign",
"home_default"
]);
const idPattern = /^[A-Za-z0-9_-]{1,64}$/;
const channelPattern = /^[A-Za-z0-9_.-]{1,64}$/;
function normalizeQrRoutingPayload() {
// Reject duplicate keys to defend against parameter pollution
if (routeValues.length > 1 || idValues.length > 1 || channelValues.length > 1) {
return {
schema_version: "1.0.0",
route_key: "home_default",
target_id: null,
fallback_route: "home_default"
};
}
const rawRoute = routeValues.length === 1 ? routeValues[0] : null;
const rawId = idValues.length === 1 ? idValues[0] : null;
const rawChannel = channelValues.length === 1 ? channelValues[0] : null;
let routeKey = allowedRoutes.has(rawRoute)
? rawRoute
: "home_default";
let targetId =
rawId && idPattern.test(rawId)
? rawId
: null;
if (routeKey === "home_default" || targetId === null) {
routeKey = "home_default";
targetId = null;
}
const payload = {
schema_version: "1.0.0",
route_key: routeKey,
target_id: targetId,
fallback_route: "home_default"
};
if (rawChannel && channelPattern.test(rawChannel)) {
payload.channel_code = rawChannel;
}
return payload;
}
const routingPayload = normalizeQrRoutingPayload();
دمج حزمة تطوير برمجيات الويب (Web SDK) من خلال محول متحقق من إصداره
تنسق صفحة الهبوط محاولات تنبيه التطبيق الفورية وتجهيز المعلمات المؤجلة. قبل نشر حزمة تطوير برمجة الويب للإحالة (مثل opoinstall.js)، تأكد من عنوان URL لحزمة JavaScript بالضبط، وتوقيع التهيئة، ومعاودة الاتصال للجاهزية، وطريقة التنبيه، ومخطط المعلمات مقابل الوثائق الرسمية لإصدار حزمة SDK الذي يتم نشره.
احتفظ بالسلوك الخاص بحزمة SDK خلف محول تطبيق صغير يقبل كائن routingPayload المعياري. للحصول على مواصفات تفصيلية حول تكامل الويب وتكوين معلمات القناة، راجع وثائق مطوري OpoInstall.
الحفاظ على معلمات التوجيه عبر روابط الرجوع المباشر للمتجر
إذا كانت حزمة تطوير برمجة الويب (Web SDK) غير متاحة أو محجوبة بواسطة ضوابط أمان المتصفح، فسوف تعود صفحة الهبوط إلى رابط إعادة توجيه متجر مباشر. في عمليات نشر Android التي تستخدم متجر جوجل بلاي، يمكن لرابط الرجوع ترميز معلمات التوجيه المعيارية في معلمة referrer الخاصة بمتجر بلاي بحيث يمكن للعميل الأصلي استعادة سياق التوجيه عبر واجهة برمجة تطبيقات مرجع تثبيت جوجل بلاي عند التثبيت من خلال مسار متجر بلاي الرسمي:
// Example Android Play Store fallback URL preserving normalized routing context
const referrerString = `route_key=${routingPayload.route_key}&target_id=${routingPayload.target_id || ""}&channel_code=${routingPayload.channel_code || ""}`;
const playStoreFallbackUrl = `https://play.google.com/store/apps/details?id=com.example.app&referrer=${encodeURIComponent(referrerString)}`;
الخطوة 3: تنفيذ إرسال مسار العميل الأصلي واستعادة المشهد
التعامل مع أوضاع إطلاق نشاط Android: لماذا يتطلب singleTask كلا من onCreate و onNewIntent
في بنيات الروابط العميقة لنظام Android، تعتمد كيفية تلقي النشاط لنوايا التوجيه على وضع الإطلاق المكون وتصميم رسم التنقل البياني. قد يستخدم المطورون أوضاع الإطلاق القياسية مع Jetpack Navigation، أو singleTop، أو singleTask بناءً على متطلبات مكدس المهام لديهم.
عند تكوين نشاط باستخدام singleTop أو singleTask لمنع تكرار تكديس الحالات أثناء تنبيهات الروابط العميقة، يجب التعامل مع مسارين مميزين لدورة الحياة:
- البدء البارد (
onCreate): عندما لا تكون عملية التطبيق قيد التشغيل، يؤدي مسح رمز الاستجابة السريعة إلى إطلاق النشاط من الصفر. يتم تسليم نية الواردةIntentإلى الدالةonCreate(savedInstanceState: Bundle?). - البدء الدافئ (
onNewIntent): عندما يعيد نظام التشغيل استخدام مثيل نشاط موجود بموجب وضع الإطلاق المكون وشروط مكدس المهام، يتم تسليم نية الرابط العميق الواردة إلى الدالةonNewIntent(). تحت وضعsingleTop، يحدث إعادة الاستخدام فقط عندما يكون النشاط المستهدف موجودًا بالفعل في أعلى مكدس المهام ذي الصلة؛ وإلا، فقد يتم إنشاء مثيل نشاط جديد. قم بتنفيذ معالجة لكل منonCreate()وonNewIntent()، وتحقق من السلوك الفعلي لبنية التنقل الخاصة بالتطبيق.
للتعامل مع كلتا الحالتين بشكل موثوق دون حظر مستخدم يقوم بشكل مشروّع بمسح نفس رمز الاستجابة السريعة للمرة الثانية لاحقًا في جلسته بشكل دائم، يجب على النشاط وضع علامة استهلاك على كل مثيل نية معالج (EXTRA_ROUTE_CONSUMED) وتجاهل النوايا التي يتم إعادة تسليمها وحدها بواسطة عمليات إطلاق متبدل التطبيقات التاريخية (Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY).
التعامل مع نقاط دخول دورة حياة iOS: مفوض التطبيق (AppDelegate) مقابل أنشطة متابعة UISceneDelegate
على نظام iOS، يجب أن يتماشى توجيه الروابط العامة المباشر واسترداد المعلمات المؤجلة مع دورة حياة واجهة المستخدم المكونة للتطبيق:
- دورة الحياة القائمة على المشهد (
UISceneDelegate): في التطبيقات التي تتبنىUIScene(قياسية من iOS 13 فصاعدًا)، تصل الروابط العامة للتشغيل البارد فيscene(_:willConnectTo:options:)عبرconnectionOptions.userActivities، بينما تصل روابط الاستئناف الدافئ للروابط العامة فيscene(_:continue:). - دورة حياة مفوض التطبيق (
UIApplicationDelegate): في التطبيقات التي لا تستخدم مفوضي المشهد، يتم تسليم الروابط العامة إلىapplication(_:continue:restorationHandler:). - استعادة التشغيل الأول المؤجل: بشكل مستقل عن مفوضي متابعة الروابط العامة، يتم تنفيذ استعلام معلمات التثبيت المؤجل مرة واحدة أثناء بدء تشغيل التطبيق الأولي بعد أن تصبح التسلسل الهرمي للعرض الجذري جاهزًا.
يمكن للمطورين تنزيل حزم SDK الخاصة بالمنصة وفحص قوالب تكامل دورة الحياة عبر مركز تنزيل حزم SDK من OpoInstall.
توحيد معلمات التنبيه المباشر ومعلمات التأجيل للإطلاق الأول في موجه تنقل واحد
للحفاظ على سلوك أمان وتنقل متسق، تقوم بنيات العميل بتطبيع كل من معرفات الموارد (URI) للتنبيه المباشر وحمولات JSON للتثبيت المؤجل في نموذج NavigationTarget واحد قبل إرسالها إلى موجه واجهة المستخدم:
- التحقق من المنشأ (النوايا المباشرة): تحقق من أن معرفات الموارد الواردة لـ
Intent.ACTION_VIEWتستخدم بروتوكولhttps، وتطابق المضيف المرتبط المتحقق منه للتطبيق (promo.example.com) والمسار (/open)، ولا تحتوي على مفاتيح استعلام مكررة ومتعارضة. - تطبيع المخطط والتحقق من القائمة البيضاء: قم بتعيين مفاتيح
route/idالخارجية أو حقولroute_key/target_idالداخلية إلى النموذج الأساسي، مع رفض المسارات غير المدرجة في القائمة البيضاء أو المعرفات التالفة. لاحظ أن منطق التحليل بلغة Kotlin للعميل يعمل كهيكل تحليل توضيحي ودفاعي؛ ففي الأنظمة الإنتاجية، قم بتحليل الحمولات مقابل قواعد المخطط الأساسي الكاملة قبل الإرسال. - حراسة إعادة الدخول ذات الطيران الفردي: استخدم حراسة ذرية لكل تسليم حتى لا تؤدي عمليات رد الاتصال المتزامنة إلى تشغيل معاملات أجزاء متداخلة.
إدارة بوابات المصادقة: صف مسارات الروابط العميقة المعلقة حتى اكتمال تسجيل دخول المستخدم

عندما يقوم مستخدم جديد بتثبيت تطبيق من متجر تطبيقات عبر رمز الاستجابة السريعة ويقوم بتشغيله لأول مرة، يكون المستخدم عادةً غير مصادق عليه. إذا كان رمز الاستجابة السريعة يستهدف شاشة محمية (مثل room_lobby أو promo_campaign) وحاول الموجّه الانتقال فورًا، فسوف تعيد بوابة المصادقة توجيه المستخدم إلى شاشة تسجيل الدخول وتفقد سياق الرابط العميق.
للحفاظ على استعادة المشهد عبر حدود المصادقة:
- بحث سياسة المسار الداخلي: حدد ما إذا كان المسار يتطلب مصادقة عن طريق التحقق من سجل
PROTECTED_ROUTESالداخلي للعميل بدلاً من الوثوق بعلامات الحمولة الخارجية. - التجهيز الذري: إذا كان المستخدم غير مصادق عليه، فقم بتخزين
NavigationTargetالذي تم التحقق منه فيPendingRouteStoreذري ونطاق الجلسة (AtomicReference) وقم بتوجيه المستخدم إلى تدفق تسجيل الدخول. لاحظ أنPendingRouteStoreالموجود في الذاكرة ينطبق على عملية التطبيق النشطة؛ وإذا كان تدفق المصادقة يمكن أن يمتد عبر موت العملية، فاستخدم متجرًا محليًا مشفرًا ومحددًا بوقت يمسح الحالة عند تبديل الحسابات. - إعادة التحقق والإرسال بعد المصادقة: عند اكتمال تسجيل الدخول، استهلك الهدف المُجهَّز بشكل ذري (
getAndSet(null))، وتأكد من المصادقة على الجلسة الآن، وأعد التحقق من معلمات المسار، ونفذ انتقال الشاشة.
جاهزية مضيف التنقل واستعادة الحالة
يجب توصيل دالة رد الاتصال onNavigationGraphReady() بعملية تهيئة مضيف التنقل الفعلية للتطبيق. عندما يتم تعريف رسم التنقل البياني في NavHostFragment مستضاف بواسطة XML، يمكن للنشاط إنشاء هذا الاتصال بعد أن يصبح المضيف ووجهته جاهزين. وإذا تم تثبيت الرسم البياني بشكل غير متزامن، فقم باستدعاء دالة رد الاتصال فقط بعد اكتمال تهيئة الرسم البياني.
قد يتم فقدان طلب التنقل المعلق المخزن فقط في حقل نشاط أثناء تغييرات التكوين. بالنسبة لتنفيذ الإنتاج، احتفظ بحالة التوجيه غير المستهلكة في ViewModel واعية بدورة الحياة واستخدم SavedStateHandle عندما تكون الاستعادة عبر إعادة إنشاء النشاط المدفوعة بالنظام أو موت العملية مطلوبة. الكود المعروض هنا هو مثال معماري ويتطلب تكامل التنقل والجلسة الخاص بالتطبيق.
يوفر كود Kotlin أدناه هيكل توجيه توضيحي لنظام Android مع محولات SDK والتنقل المملوكة للطبق التي توضح هذه الضوابط:
<!-- Android Manifest Configuration for App Link Verification -->
<!-- File: app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity
android:name=".SceneRestorationActivity"
android:exported="true"
android:launchMode="singleTask">
<!-- Verified Android App Link Intent Filter -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Origin must match /.well-known/assetlinks.json SHA-256 fingerprint -->
<data
android:scheme="https"
android:host="promo.example.com"
android:pathPrefix="/open" />
</intent-filter>
</activity>
</application>
</manifest>
// Android Routing Activity Implementation
// File: app/src/main/java/com/example/app/SceneRestorationActivity.kt
//
// INTEGRATION NOTE:
// This example is an architectural routing skeleton, not a drop-in production implementation.
// Bind AttributionRoutingAdapter and NavController initialization to your verified SDK release,
// session manager, and NavHostFragment before deployment.
package com.example.app
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import org.json.JSONObject
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
/**
* Validated navigation target model representing an allowlisted in-app destination.
* Authentication requirement is derived strictly from the internal route registry.
*/
data class NavigationTarget(
val eventKey: String,
val routeKey: String,
val targetId: String?,
val requiresAuth: Boolean
)
/**
* Process-scoped atomic store holding a pending route when an unauthenticated user
* triggers a protected deep link during cold start or first-launch onboarding.
*/
object PendingRouteStore {
private val pending = AtomicReference<NavigationTarget?>(null)
fun stage(target: NavigationTarget) {
pending.set(target)
}
fun consume(): NavigationTarget? {
return pending.getAndSet(null)
}
}
class SceneRestorationActivity : AppCompatActivity() {
companion object {
private const val TAG = "SceneRestorationRouter"
private const val TRUSTED_HOST = "promo.example.com"
private const val TRUSTED_PATH = "/open"
private const val EXTRA_ROUTE_CONSUMED = "com.example.app.EXTRA_ROUTE_CONSUMED"
private const val ROUTE_HOME = "home_default"
private const val ROUTE_PRODUCT = "product_detail"
private const val ROUTE_LOBBY = "room_lobby"
private const val ROUTE_PROMO = "promo_campaign"
private val ALLOWED_ROUTES = setOf(ROUTE_HOME, ROUTE_PRODUCT, ROUTE_LOBBY, ROUTE_PROMO)
private val PROTECTED_ROUTES = setOf(ROUTE_LOBBY, ROUTE_PROMO)
private val SAFE_ID_REGEX = Regex("^[A-Za-z0-9_-]{1,64}$")
// Process-level guard ensuring deferred install recovery is queried only once per cold start
private val hasQueriedDeferredInstall = AtomicBoolean(false)
}
// Per-delivery re-entrancy guard; allows legitimate subsequent scans of the same QR code
private val processingIntent = AtomicBoolean(false)
// Staging container for routes that arrive before the UI/NavController is mounted
private val pendingUiTarget = AtomicReference<NavigationTarget?>(null)
private var navigationReady = false
// Application-owned adapter isolating vendor SDK and session calls
private val attributionAdapter = AttributionRoutingAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scene_restoration)
// Connect NavHost initialization when defined in layout
connectNavigationHost()
// Activity recreation (e.g., screen rotation) is not a new install or new QR scan
if (savedInstanceState != null) {
return
}
val launchIntent = intent
val isDirectLinkLaunch =
launchIntent?.action == Intent.ACTION_VIEW &&
launchIntent.data != null
if (isDirectLinkLaunch) {
processIncomingIntentOnce(launchIntent) { candidate ->
handleIncomingWakeupIntent(candidate)
}
// Do not start deferred recovery after a direct-link launch,
// even if the direct route was rejected.
return
}
// Only initial normal application launches may request deferred recovery
if (hasQueriedDeferredInstall.compareAndSet(false, true)) {
queryDeferredSceneParameters()
}
}
/**
* Connect this method to the actual NavHost initialization.
* Uses currentDestination check and destination listener to safely verify graph readiness
* without throwing IllegalStateException on unset controller.graph.
*/
private fun connectNavigationHost() {
val navHost = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment
if (navHost == null) {
Log.w(TAG, "NavHost not attached; route remains queued.")
// For an asynchronously attached NavHost, call this
// method again after the host has been attached.
return
}
val controller = navHost.navController
// currentDestination is nullable before a destination has been established.
// Do not access controller.graph merely to check whether setGraph() has been called.
if (controller.currentDestination != null) {
onNavigationGraphReady()
return
}
// If the graph/destination becomes available later,
// mark navigation ready when a destination is established.
val listener = object : androidx.navigation.NavController.OnDestinationChangedListener {
override fun onDestinationChanged(
navController: androidx.navigation.NavController,
destination: androidx.navigation.NavDestination,
arguments: Bundle?
) {
navController.removeOnDestinationChangedListener(this)
onNavigationGraphReady()
}
}
controller.addOnDestinationChangedListener(listener)
}
/**
* Captures warm-start QR scans when the Activity is reused under singleTop or singleTask.
*/
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
processIncomingIntentOnce(intent) { candidate ->
handleIncomingWakeupIntent(candidate)
}
}
private fun processIncomingIntentOnce(incomingIntent: Intent?, process: (Intent) -> Unit) {
val candidate = incomingIntent ?: return
// Ignore historical intents re-delivered from recent-apps switcher or configuration changes
if ((candidate.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) return
if (candidate.getBooleanExtra(EXTRA_ROUTE_CONSUMED, false)) return
if (!processingIntent.compareAndSet(false, true)) return
try {
process(candidate)
candidate.putExtra(EXTRA_ROUTE_CONSUMED, true)
} finally {
processingIntent.set(false)
}
}
private fun handleIncomingWakeupIntent(incomingIntent: Intent?): Boolean {
if (incomingIntent?.action != Intent.ACTION_VIEW) return false
val uri: Uri = incomingIntent.data ?: return false
// 1. Verify trusted HTTPS origin and path
val isTrusted =
uri.scheme.equals("https", ignoreCase = true) &&
uri.host.equals(TRUSTED_HOST, ignoreCase = true) &&
uri.path == TRUSTED_PATH
if (!isTrusted) {
Log.w(TAG, "Rejected untrusted deep-link origin: $uri")
return false
}
// 2. Reject missing or conflicting duplicate query parameters
if (uri.getQueryParameters("route").size != 1 ||
uri.getQueryParameters("id").size != 1) {
Log.w(TAG, "Rejected missing or duplicate routing keys in URI")
return false
}
val rawRoute = uri.getQueryParameter("route")
val rawId = uri.getQueryParameter("id")
val target = validateAndBuildTarget("wakeup", rawRoute, rawId)
if (target.routeKey != ROUTE_HOME) {
dispatchNavigationTarget(target)
return true
}
return false
}
private fun queryDeferredSceneParameters() {
// Application-defined timeout configuration; tune for deployed service behavior
attributionAdapter.fetchDeferredInstallParams(timeoutMs = 5000L) { jsonPayload ->
if (jsonPayload == null) {
Log.d(TAG, "No deferred routing parameters recovered; remaining on default view.")
return@fetchDeferredInstallParams
}
val target = parseJsonRoutingPayload("deferred_install", jsonPayload)
runOnUiThread {
dispatchNavigationTarget(target)
}
}
}
private fun parseJsonRoutingPayload(sourcePrefix: String, rawJson: String): NavigationTarget {
return try {
val obj = JSONObject(rawJson)
// Defensive normalization: map canonical 'route_key'/'target_id' with external 'route'/'id' fallback
val rawRoute = obj.optString("route_key", "").ifEmpty { obj.optString("route", "") }
val rawId = if (obj.has("target_id") && !obj.isNull("target_id")) {
obj.optString("target_id", "")
} else {
obj.optString("id", "")
}
validateAndBuildTarget(sourcePrefix, rawRoute, rawId)
} catch (e: Exception) {
Log.w(TAG, "Malformed routing JSON payload: ${e.message}")
NavigationTarget(sourcePrefix, ROUTE_HOME, null, false)
}
}
/**
* Enforces strict allowlist and regex bounds on untrusted routing parameters.
*/
private fun validateAndBuildTarget(
sourcePrefix: String,
rawRoute: String?,
rawId: String?
): NavigationTarget {
val cleanRoute = if (rawRoute != null && ALLOWED_ROUTES.contains(rawRoute)) {
rawRoute
} else {
ROUTE_HOME
}
if (cleanRoute == ROUTE_HOME) {
return NavigationTarget(sourcePrefix, ROUTE_HOME, null, false)
}
// Entity-specific routes strictly require a valid, sanitized targetId
val cleanId = if (rawId != null && SAFE_ID_REGEX.matches(rawId)) {
rawId
} else {
null
}
if (cleanId == null) {
Log.w(TAG, "Route $cleanRoute rejected due to missing or invalid target_id.")
return NavigationTarget(sourcePrefix, ROUTE_HOME, null, false)
}
val needsAuth = PROTECTED_ROUTES.contains(cleanRoute)
return NavigationTarget("${sourcePrefix}_${cleanRoute}_${cleanId}", cleanRoute, cleanId, needsAuth)
}
/**
* Invoked when the application's NavHost / NavController graph is attached and ready.
*/
fun onNavigationGraphReady() {
navigationReady = true
flushPendingNavigation()
}
private fun flushPendingNavigation() {
if (!navigationReady || isFinishing || isDestroyed) return
val target = pendingUiTarget.getAndSet(null) ?: return
dispatchReadyNavigationTarget(target)
}
private fun dispatchNavigationTarget(target: NavigationTarget) {
if (isFinishing || isDestroyed) return
// Stage route if the UI navigation graph has not finished inflating
if (!navigationReady) {
pendingUiTarget.set(target)
return
}
dispatchReadyNavigationTarget(target)
}
private fun dispatchReadyNavigationTarget(target: NavigationTarget) {
if (target.routeKey == ROUTE_HOME) {
navigateToHomeScreen()
return
}
// Check authentication gate for protected destinations
if (target.requiresAuth && !attributionAdapter.isUserAuthenticated()) {
Log.d(TAG, "Protected route ${target.routeKey} staged pending user authentication.")
PendingRouteStore.stage(target)
navigateToLoginScreen()
return
}
executeScreenTransition(target.routeKey, target.targetId)
}
/**
* Invoked by the authentication controller after login or registration completes.
*/
fun onUserAuthenticationCompleted() {
val pending = PendingRouteStore.consume()
if (pending == null) {
navigateToHomeScreen()
return
}
if (isFinishing || isDestroyed) return
if (!attributionAdapter.isUserAuthenticated()) {
PendingRouteStore.stage(pending)
navigateToLoginScreen()
return
}
val validated = validateAndBuildTarget(
pending.eventKey,
pending.routeKey,
pending.targetId
)
if (validated.routeKey == ROUTE_HOME) {
navigateToHomeScreen()
return
}
executeScreenTransition(validated.routeKey, validated.targetId)
}
private fun executeScreenTransition(routeKey: String, targetId: String?) {
// Implementation: perform backend-authorized resource fetch and invoke NavController.navigate()
Log.d(TAG, "Dispatching verified screen transition: route=$routeKey, id=$targetId")
}
private fun navigateToLoginScreen() {
// Implementation: transition to LoginFragment / LoginActivity
}
private fun navigateToHomeScreen() {
// Implementation: display default home screen
}
}
/**
* Application-owned adapter skeleton. Replace stubbed methods with verified OpoInstall SDK
* callbacks, Google Play Install Referrer queries, and your production session manager.
*/
class AttributionRoutingAdapter {
fun fetchDeferredInstallParams(timeoutMs: Long, callback: (String?) -> Unit) {
// Bind to verified SDK install parameter API or Play Install Referrer in production
callback(null)
}
fun isUserAuthenticated(): Boolean {
// Bind to production authentication session state
return false
}
}
الأمان والتشخيص: منع حقن المسار وإسقاط التنقل
الحماية ضد حقن مسارات الروابط العميقة واستغلال إعادة التوجيه المفتوحة وفقًا لإرشادات OWASP
تمثل المعلمات المستخرجة من رموز الاستجابة السريعة أو عمليات رد الاتصال للإحالة المؤجلة مدخلات خارجية غير موثوقة. تماشياً مع معيار التحقق من أمان تطبيقات الجوال من OWASP ومخاطر وإرشادات الأمان العشرة الأولى لتطبيقات الجوال من OWASP، يجب على موجهات العملاء فرض ثلاثة حدود دفاعية:
- حظر تحميل عناوين URL عشوائية لـ WebView: لا تسمح أبدًا لمعلمة توجيه (
?route=webview&url=https://...) بتحميل عنوان URL خارجي غير متحقق منه داخل WebView للتطبيق الداخلي. يمكن للمهاجمين ترميز عناوين URL الخاصة التصيد الاحتيالي في رمز الاستجابة السريعة، مستغلين إطار واجهة المستخدم الأصلية للتطبيق لجمع بيانات الاعتماد. - رفض تلوث المعلمات المكررة: إذا كان عنوان URL المُتلاعب به يحتوي على عدة مفاتيح متعارضة (
?route=home_default&route=room_lobby)، فارفض معرف الموارد (URI) بدلاً من السماح للمحلل باختيار عنصر أول أو أخير عشوائي. - فرض تخويل موارد الخادم الخلفي: إن تمرير
target_id=sku_8842يوجه العميل فقط بالعارض المطلوب طلبه. وعندما تستعلم الشاشة عن واجهة برمجة تطبيقات الخادم الخلفي، يجب على الخادم التحقق بشكل مستقل من أن جلسة المستخدم الحالية مخولة للوصول إلى هذا الكيان، مما يمنع ثغرات مرجع الكيان المباشر غير الآمن (IDOR).
تشخيص حالات السباق للتنقل بين البدء البارد والبدء الدافئ
أثناء البدء البارد، يمكن أن يتم حل نوايا الروابط العميقة أو عمليات رد الاتصال لـ SDK المخبأة قبل أنتهاء تضخيم NavHostFragment الرئيسي أو رسم التنقل البياني الجذري. تؤدي محاولة استدعاء navController.navigate() قبل أن يكون التسلسل الهرمي للعرض جاهزًا إلى حدوث استثناءات وقت التشغيل أو إسقاط الانتقالات. احرص دائماً على تأجيل تنفيذ التنقل حتى يتم ربط التسلسل الهرمي لعرض المضيف ويكون نشطًا (!isFinishing && !isDestroyed).
التعامل بلطف مع العروض الترويجية منتهية الصلاحية، ووحدات SKU المحذوفة، والمعلمات المفقودة
غالبًا ما تظل رموز الاستجابة السريعة المادية منشورة في متاجر التجزئة أو وسائل الإعلام المطبوعة بعد انتهاء العرض الترويجي الرقمي أو إيقاف وحدة SKU للمنتج. عندما تقوم الشاشة المستهدفة بجلب البيانات لـ target_id وتتلقى استجابة خطأ HTTP 404 Not Found أو 410 Gone من الخادم الخلفي، يجب على العميل التقاط الخطأ، وعرض إشعار إعلامي واضح للمستخدم، والانتقال بسلاسة إلى home_default.
قائمة التحقق للتحقق قبل النشر لتوجيه رمز الاستجابة السريعة ثنائي الحالة
قبل إصدار المواد المادية لرمز الاستجابة السريعة، يجب على الفرق الهندسية تنفيذ بروتوكول التحقق ذي النقاط السبع التالي عبر الأجهزة الفهدية:
- الخطوة 1: تنبيه البدء البارد المثبت: قم بفرض إغلاق التطبيق المثبت وقم بمسح رمز الاستجابة السريعة. تأكد من أن نظام التشغيل يفتح التطبيق مباشرة في الشاشة المستهدفة (
product_detail). - الخطوة 2: تنبيه البدء الدافئ المثبت وإعادة المسح: مع فتح التطبيق على علامة تبويب ثانوية، ضع التطبيق في الخلفية وامسح رمز الاستجابة السريعة. تأكد من أن
onNewIntentيوجه إلى الشاشة المستهدفة. انتقل بعيدًا، وامسح نفس رمز الاستجابة السريعة تمامًا للمرة الثانية، وتأكد من أنه ينتقل مرة أخرى بدلاً من حظره بسبب إلغاء التكرار الواسع جدًا. - الخطوة 3: الاستعادة المؤجلة غير المثبتة (مسار عام): قم إلغاء تثبيت التطبيق، وامسح رمز الاستجابة السريعة، وأكمل تثبيت المتجر، وابدأ التشغيل. تأكد من أن رد الاتصال للمعلمات المؤجلة يقوم بتطبيع الحمولة والتوجيه إلى
product_detail. - الخطوة 4: الاستعادة المؤجلة غير المثبتة مع بوابة المصادقة (مسار محمي): قم بإلغاء تثبيت التطبيق، وامسح رمز الاستجابة السريعة الذي يستهدف
room_lobby، وثبت، وابدأ التشغيل أثناء تسجيل الخروج. تأكد من أن التطبيق يوجه إلى تسجيل الدخول أولاً، وعند إتمام تسجيل الدخول، يستهلكPendingRouteStoreوينتقل إلىroom_lobby. - الخطوة 5: رفض المنشأ غير الموثوق والمفتاح المكرر: قم بتشغيل نية
ACTION_VIEWمن مضيف غير موثوق به أو بمعلمات استعلام مكررة (?route=product_detail&route=room_lobby). تأكد من أنhandleIncomingWakeupIntentيرفض النية ويسجل تحذيرًا. - الخطوة 6: تماثل تدوير الشاشة: بعد الوصول إلى الشاشة الموجهة بعمق، قم بتدوير الجهاز لتشغيل إعادة إنشاء النشاط. تأكد من أن علامة
EXTRA_ROUTE_CONSUMEDتمنع النية الأصلية من إعادة التنفيذ. - الخطوة 7: الرجوع السلس بلا اتصال / غير المتطابق: قم بتشغيل تثبيت نظيف في وضع الطيران. تأكد من أن مهلة الاسترداد تُحل دون حظر خيط واجهة المستخدم وتوجه المستخدم إلى
home_default.
تحليل مقارن لآليات التوجيه داخل التطبيق
تقييم مخططات عناوين URL، والروابط العامة وروابط التطبيقات، واستعادة المشهد المؤجل
توفر منصات الجوال آليات مميزة لتوجيه المستخدمين إلى عروض التطبيقات عبر حالات التثبيت المختلفة:
| آلية التوجيه | حالة التطبيق المستهدف | متطلبات التحقق لنظام التشغيل | بقاء المعلمة عبر تثبيت المتجر | القيد الهندسي الأساسي |
|---|---|---|---|---|
| مخطط عنوان URL المخصص | مثبت فقط | بلا (إعلان البيان فقط) | لا (يفشل إذا لم يكن التطبيق مثبتًا) | يؤدي إلى تشغيل النوافذ المنبثقة لأخطاء المتصفح إذا كانت غائبة؛ وعرضة لاختطاف المخطط |
| الروابط العامة / روابط التطبيقات | الأساسي المثبت | iOS: ملف AASA + امتياز النطاقات المرتبطة؛ Android: assetlinks.json + شهادة توقيع التطبيق المطابقة وتكوين مرشح النية المتحقق منه |
لا (يتطلب طبقة مؤجلة لعمليات التثبيت الجديدة) | يمكن قمعها بواسطة حاويات ويب داخل التطبيق مقيدة معينة |
| مرجع تثبيت جوجل بلاي | تثبيتات Android الجديدة | مسار توزيع متجر جوجل بلاي الرسمي | نعم (سلسلة مرجعية مدعومة) | خاص بمتجر جوجل بلاي؛ ولا يغطي iOS أو المتاجر غير التابعة لـ Play |
| استعادة المشهد بمساعدة SDK | مثبتة وتثبيتات جديدة | ارتباط النطاق + محول SDK متحقق منه | مشروط على آلية المنصة المدعومة | يتطلب التحقق من قائمة المسارات البيضاء ومراجعة خصوصية المنصة |
المقايضات المعمارية عبر حالات التثبيت وأنظمة التشغيل
تفتقر مخططات عناوين URL المخصصة (myapp://product/123) إلى التحقق من ملكية النطاق التشفيري، مما يعني أن أي تطبيق يمكنه تسجيل نفس المخطط واعتراض الروابط. علاوة على ذلك، فإن مسح رمز الاستجابة السريعة لمخطط مخصص خام عندما لا يكون التطبيق مثبتًا يؤدي إلى خطأ متصفح غير قابل للاسترداد.
تتحقق الروابط العامة وروابط تطبيقات Android من ملكية النطاق عبر HTTPS وتعود إلى صفحة هبوط ويب عندما يكون التطبيق غائبًا، مما يجعلها الأساس المطلوب لتوجيه رمز الاستجابة السريعة. يتيح جمع الروابط العامة وروابط التطبيقات لتنبيه التطبيق المثبت مع الاستعادة المؤجلة المدعومة من النظام الأساسي (مثل مرجع تثبيت جوجل بلاي على Android ومحولات SDK المُراجعة للخصوصية) استعادة المشهد ثنائي الحالة من أصل رمز الاستجابة السريعة واحد.
الأسئلة الشائعة (FAQ)
كيف تفتح معلمات رمز الاستجابة السريعة المخصصة صفحة معينة داخل التطبيق إذا لم يكن التطبيق مثبتًا بعد؟
لماذا يفتح مسح رمز الاستجابة السريعة الخاص بي التطبيق على الشاشة الرئيسية بدلاً من الصفحة المستهدفة؟
كيف يمكنني منع رموز الاستجابة السريعة الضارة من توجيه المستخدمين إلى شاشات داخلية غير مصرح بها؟
الملخص وإطار القرار
لإنشاء رموز الاستجابة السريعة لتوجيه التطبيقات باستخدام معلمات مخصصة، يجب على الفرق الهندسية ربط سلاسل استعلام عنوان URL الخارجي المدمجة بوحدات التحكم في التنقل الأصلية الواعية بالحالة. يتعامل التنفيذ الكامل مع كل من تنبيه نظام التشغيل المباشر للمستخدمين الحاليين واستعادة المعلمات المؤجلة المشروطة لعمليات تثبيت متجر التطبيقات الجديدة.
نظرًا لأن استمرارية المعلمات عبر متاجر التطبيقات تعتمد على آليات خاصة بالمنصة - مثل مرجع تثبيت جوجل بلاي على Android وسير العمل المتحقق منه والمتوافق مع الخصوصية على iOS - يجب على التطبيقات معاملة حمولات التوجيه المستعادة على أنها مرشحات غير موثوقة. من خلال دمج موجهات المسارات المدرجة في القائمة البيضاء، وإصطفاف حالة المصادقة، والرجوع السلس إلى الشاشة الرئيسية، يمكن للفرق تقديم استعادة موثوقة للمشهد دون المساس باستقرار التطبيق أو قواعد خصوصية المنصة.
توفر حزم SDK ووثائق المطورين من OpoInstall مكونات لتنفيذ توجيه الويب إلى التطبيق وتمرير المعلمات. تأكد من توقيعات واجهة برمجة التطبيقات المحددة، ومخططات رد الاتصال، وسلوكيات المنصة المدعومة بواسطة إصدار حزمة SDK المنشورة لديك قبل طرح الإنتاج.
للحصول على تفاصيل التكامل، راجع وثائق مطوري OpoInstall، أو قم بالوصول إلى مركز تنزيل حزم SDK من OpoInstall، أو قم بتكوين نطاقات توجيه التطبيق الخاصة بك في وحدة تحكم مطوري OpoInstall.
المواد ذات الصلة
-
المفاهيم: استعادة المشهد، الربط العميق ثنائي الحالة، إدراج المسارات في القائمة البيضاء، استعادة المعلمات المؤجلة
-
التقنيات: الروابط العامة، روابط تطبيقات Android، مرجع تثبيت جوجل بلاي، وحدات التحكم في التنقل الأصلية
-
المعايير: البناء العام لمعرف الموارد المنتظم (URI) لـ IETF RFC 3986، معيار التحقق من أمان تطبيقات الجوال من OWASP، مخاطر وإرشادات الأمان العشرة الأولى لتطبيقات الجوال من OWASP
-
واجهات التكامل: مدقق مسار الرابط العميق المملوك للتطبيق، وعمليات رد الاتصال الموثقة للتنبيه ومعلمات التثبيت لإصدار حزمة SDK المنشورة، ووحدات التحكم في التنقل الأصلية للمنصة.
-
التوثيق الرسمي والمراجع:
Share this article



