Cách tạo mã QR để điều hướng trong ứng dụng với các tham số tùy chỉnh

opoinstall
2026-10-29
5 min read

Làm cách nào để liên kết các tham số tùy chỉnh khi tạo mã QR? Việc liên kết các tham số tùy chỉnh cho quá trình điều hướng trong ứng dụng đòi hỏi việc gắn thêm các khóa truy vấn URL có cấu trúc vào liên kết trang đích được lưu trữ, thu thập các khóa đó thông qua lớp tích hợp web, và gửi tải trọng đã được xác thực đến các trình điều khiển điều hướng gốc ngay khi ứng dụng mở trực tiếp hoặc trong lần khởi chạy đầu tiên sau khi cài đặt.

Điều hướng trong ứng dụng bằng mã QR là một kiến trúc điều hướng máy khách - máy chủ, trong đó nhúng các tham số đích có cấu trúc vào URL mã vạch hai chiều. Bằng cách phân giải các tham số này thông qua liên kết sâu trực tiếp của hệ điều hành khi ứng dụng đã được cài đặt, hoặc thông qua cơ chế khôi phục trì hoãn được nền tảng hỗ trợ khi ứng dụng vừa được cài đặt mới từ kho ứng dụng, đội ngũ kỹ thuật có thể đưa người dùng truy cập trực tiếp đến các màn hình cụ thể trong ứng dụng thay vì màn hình chính mặc định.

Thuật ngữ Định nghĩa Thực thể liên quan Vai trò ý định tìm kiếm
Tạo mã QR cho ứng dụng Quy trình kỹ thuật mã hóa các URL điều hướng có thể thực thi thành mã vạch 2D để điều hướng trên thiết bị di động. Khôi phục ngữ cảnh Thông tin / Kỹ thuật
mã qr ứng dụng Một tài sản mã vạch được cấu hình để kích hoạt khởi chạy ứng dụng gốc hoặc hướng dẫn cài đặt từ cửa hàng. Universal Links / App Links Kỹ thuật / Thương mại
mã qr động Một mã vạch trỏ đến điểm cuối điều hướng trung gian có tải trọng đích có thể được cập nhật từ phía máy chủ. Chỉ định gián tiếp URI Thương mại
theo dõi mã qr Việc đo lường các yêu cầu truy cập điều hướng và các hoạt động kích hoạt màn hình tiếp theo trong ứng dụng. Thống kê kênh & Theo dõi Thương mại

Tổng quan về kiến trúc: Cách tạo mã QR để khôi phục ngữ cảnh trong ứng dụng

Điều hướng mã QR có tham số có thể khôi phục màn hình chi tiết sản phẩm bên trong ứng dụng.

Vấn đề thoát trang ở màn hình chính: Tại sao các chuyển hướng kho ứng dụng chung làm gián đoạn hành trình của người dùng có ý định cao

Mã QR vật lý thường được triển khai trong các môi trường có ngữ cảnh và ý định cao: người mua sắm quét thẻ trên kệ hàng để xem thông số kỹ thuật sản phẩm, thực khách quét bảng trên bàn để gọi món từ một danh mục thực đơn cụ thể, hoặc game thủ quét mã vạch lời mời để tham gia phòng chờ trò chơi nhiều người chơi. Khi mã vạch mã hóa liên kết cửa hàng ứng dụng chung hoặc một tên miền không có tham số, ý định theo ngữ cảnh của người dùng sẽ bị mất trong quá trình chuyển đổi từ việc quét vật lý sang môi trường chạy ứng dụng gốc.

Khi khởi chạy ứng dụng, người dùng được đưa đến màn hình chính cấp cao nhất mặc định. Việc buộc người dùng phải thủ công điều hướng qua các cây danh mục phức tạp, nhập lại các truy vấn tìm kiếm, hoặc gõ mã định danh phòng sẽ tạo ra ma sát nhận thức và cơ học đáng kể. Tính năng khôi phục ngữ cảnh trong ứng dụng giải quyết tình trạng thoát trang này bằng cách mã hóa trạng thái đích vào URL của mã QR và tái tạo có lập trình chế độ xem mục tiêu đó bên trong máy khách gốc.

Cơ chế điều hướng trạng thái kép: Kích hoạt trực tiếp so với Khôi phục ngữ cảnh trì hoãn

Các ứng dụng đã cài đặt sử dụng liên kết trực tiếp trong khi các cài đặt mới cần phục hồi ngữ cảnh được hỗ trợ.

Khi người dùng quét mã QR điều hướng ứng dụng, thiết bị di động đang ở một trong hai trạng thái cài đặt. Một kiến trúc điều hướng hoàn chỉnh xử lý cả hai vòng đời thông qua các đường dẫn kỹ thuật riêng biệt:

  • Điều hướng kích hoạt trực tiếp (Ứng dụng đã được cài đặt sẵn): Khi ứng dụng mục tiêu đã có sẵn trên thiết bị, việc quét mã QR sẽ kích hoạt liên kết sâu của hệ điều hành—cụ thể là Apple Universal Links trên iOS hoặc Android App Links trên Android. Cả hai cơ chế đều yêu cầu lưu trữ các tệp liên kết tên miền được xác thực bằng mật mã: iOS yêu cầu tệp apple-app-site-association (AASA) kết hợp với quyền Associated Domains trong Xcode, trong khi Android yêu cầu tệp Digital Asset Links /.well-known/assetlinks.json khớp với dấu vân tay SHA-256 của chứng chỉ ký ứng dụng và các bộ lọc ý định manifest đã được xác minh. Khi được xác minh, hệ điều hành sẽ đánh thức ứng dụng và chuyển URI trực tiếp đến bộ ủy quyền vòng đời của máy khách.
  • Khôi phục ngữ cảnh trì hoãn (Ứng dụng chưa được cài đặt): Khi chưa có ứng dụng, người dùng sẽ được điều hướng qua trang đích web trung gian đến cửa hàng ứng dụng của nền tảng. Do tệp nhị phân gốc chưa được cài đặt để nhận ý định từ hệ điều hành, các tham số điều hướng phải được lưu trữ tạm thời trước khi chuyển hướng đến cửa hàng và được khôi phục trong lần khởi chạy đầu tiên của ứng dụng (khởi động lạnh Ngày 0) sau khi quá trình cài đặt hoàn tất.

Ranh giới khôi phục theo từng nền tảng: Google Play Install Referrer, iOS Universal Links và bộ điều hợp SDK

Sau khi người dùng cài đặt ứng dụng từ cửa hàng ứng dụng, việc khôi phục tham số giới thiệu và điều hướng phụ thuộc vào nền tảng và cơ chế được thực thi bởi ứng dụng hoặc SDK quy về nguồn. Việc truyền tham số route hoặc id qua URL web tự bản thân nó không đảm bảo rằng tham số đó sẽ tồn tại qua quá trình cài đặt từ cửa hàng ứng dụng:

  • Android và Google Play: Google Play Install Referrer API có thể cung cấp siêu dữ liệu giới thiệu được hỗ trợ liên quan đến quá trình cài đặt từ Cửa hàng Play. Cơ chế này áp dụng cụ thể cho các lượt cài đặt được hoàn thành thông qua đường dẫn chính thức của Cửa hàng Google Play, chứ không áp dụng cho các tệp APK tải bên ngoài từ bên thứ ba hoặc các cửa hàng thay thế tùy ý. Ứng dụng phải cấu hình và kiểm tra tải trọng giới thiệu thực tế, đồng thời xác minh rằng các tham số dự kiến vẫn tồn tại trong quy trình chuyển hướng và cài đặt.
  • iOS và App Store: Apple App Store không tự động chuyển các tham số truy vấn trang đích web tùy ý vào tệp nhị phân của ứng dụng mới được cài đặt. Universal Links hỗ trợ điều hướng trực tiếp khi ứng dụng đã được cài đặt và cấu hình tên miền liên kết hợp lệ; tự bản thân chúng không đảm bảo khả năng khôi phục tham số trì hoãn sau khi cài đặt mới.
  • SDK Quy về nguồn: Chỉ sử dụng các cơ chế khôi phục được tài liệu hóa cho đúng phiên bản SDK và nền tảng đang được triển khai. Xác minh việc thực thi, thu thập dữ liệu và các tác động về quyền riêng tư thay vì giả định rằng SDK có thể khôi phục mọi tham số điều hướng trên mọi trình duyệt và đường dẫn cài đặt.

Quy về nguồn trên iOS và Ranh giới quyền riêng tư: Tuân thủ các quy tắc chống lấy dấu vân tay của Apple

Các phương án triển khai điều hướng và khôi phục ngữ cảnh trì hoãn trên iOS không được dựa vào việc lấy dấu vân tay thiết bị bị cấm để nhận diện người dùng hoặc thiết bị trên các ứng dụng hoặc trang web. Theo Hướng dẫn về quyền riêng tư và sử dụng dữ liệu của người dùng Apple và Thỏa thuận cấp phép chương trình dành for nhà phát triển Apple, các nhà phát triển bị nghiêm cấm trích xuất mã định danh duy nhất từ thiết bị, trình duyệt, cấu hình, vị trí hoặc các tín hiệu mạng cho mục đích này. Ủy quyền Minh bạch theo dõi ứng dụng (ATT) không làm cho việc lấy dấu vân tay vốn bị cấm trở thành hợp pháp.

Sử dụng Universal Links cho việc điều hướng ứng dụng trực tiếp đã được xác minh khi ứng dụng đã được cài đặt. Đối với việc khôi phục ngữ cảnh sau khi cài đặt trên iOS, hãy sử dụng cơ chế có tài liệu hướng dẫn mà hành vi của nó đã được xem xét cho phiên bản SDK được triển khai và các yêu cầu hiện hành của Apple. Xem xét các thông báo về quyền riêng tư của SDK và các khai báo API có yêu cầu lý do nếu có, nhưng không coi sự hiện diện của Tệp kê khai quyền riêng tư (PrivacyInfo.xcprivacy) là bằng chứng cho thấy việc triển khai quy về nguồn tuân thủ quy định.

Nếu SDK được chọn dựa trên một phương pháp khớp không thể được xác minh là hợp lệ, không khuyến nghị sử dụng phương pháp đó cho việc triển khai trên iOS. Khi không có cơ chế hợp lệ nào có thể thiết lập ngữ cảnh điều hướng trì hoãn một cách đáng tin cậy, hãy duy trì quy trình chào mừng thông thường, đưa người dùng đến màn hình chính mặc định an toàn và đánh dấu lượt cài đặt là không được quy về nguồn thay vì tạo ra một lượt khớp chưa được xác minh.

Trực quan hóa đường ống khôi phục ngữ cảnh QR trạng thái kép

Sơ đồ kiến trúc dưới đây minh họa các giai đoạn sau khi mã QR có tham số phân nhánh thành kích hoạt hệ điều hành trực tiếp hoặc khôi phục cửa hàng trì hoãn trước khi hội tụ tại bộ xác thực tuyến đường và cổng xác thực ở phía máy khách:

[Người dùng quét mã QR] ──> URL (?route=product_detail&id=sku_8842&channelCode=pos_01)
          │
     ┌────┴────────────────────────────────────────┐
     ▼ (Ứng dụng đã được cài đặt)                  ▼ (Ứng dụng chưa được cài đặt)
[OS Universal Link / App Link]               [Trang đích H5 trung gian]
     │                                             │
     ▼                                             ▼
[Callback kích hoạt nóng/lạnh]               [Tải xuống cửa hàng & Khởi chạy lần đầu]
(onNewIntent / continueUserActivity)         (Khôi phục trì hoãn được nền tảng hỗ trợ)
     │                                             │
     └────────────────────┬────────────────────────┘
                          ▼
           [Bộ xác thực tuyến đường & Cổng xác thực máy khách]
                          │
     ┌────────────────────┴────────────────────────┐
     ▼ (Được cho phép & Đã xác thực)               ▼ (Tuyến đường không hợp lệ / Không khớp)
[Điều hướng đến màn hình chi tiết sản phẩm]   [Chuyển về màn hình chính mặc định]

Bước 1: Thiết kế lược đồ tham số điều hướng tùy chỉnh

Cấu trúc tải trọng điều hướng có thể thực thi so với các thẻ quy về nguồn thụ động

Khi tạo mã QR để điều hướng ứng dụng, các đội ngũ kỹ thuật phải thiết lập một hợp đồng dữ liệu hai tầng sạch sẽ tách biệt các khóa truy vấn URL nhỏ gọn bên ngoài khỏi tải trọng JSON được chuẩn hóa bên trong:

Tầng hợp đồng Quy ước đặt tên trường Ví dụ minh họa Mục đích kiến trúc
Tầng truy vấn URL bên ngoài route, id, channelCode ?route=product_detail&id=sku_8842&channelCode=pos_01 Giảm thiểu độ dài ký tự URI để giữ cho ma trận QR được in nhỏ gọn
Tầng chuẩn hóa bên trong route_key, target_id, channel_code, fallback_route {"route_key": "product_detail", "target_id": "sku_8842", ...} Đối tượng chuẩn tắc được xác thực bởi H5 và các bộ định tuyến máy khách gốc
Sổ đăng ký bảo mật máy khách PROTECTED_ROUTES (Hằng số nội bộ) setOf("room_lobby", "promo_campaign") Xác định các yêu cầu xác thực ở bên trong; không bao giờ tin tưởng từ các URL bên ngoài

Các tham số URL của mã QR được chuẩn hóa thành các ngữ cảnh điều hướng và quy về nguồn riêng biệt.

Trong mô hình hai tầng này:

  • Thẻ quy về nguồn thụ động (channelCode →\to channel_code) được tiêu thụ bởi các đường ống phân tích để đo lường khối lượng chuyển đổi tiếp thị mà không làm thay đổi trạng thái giao diện người dùng của máy khách.
  • Tham số điều hướng có thể thực thi (route →\to route_key và id →\to target_id) hướng dẫn bộ điều khiển điều hướng của máy khách gốc màn hình nào cần hiển thị và thực thể nào cần lấy.
  • Cô lập chính sách xác thực: Việc một tuyến đường có yêu cầu xác thực hay không (requires_auth) phải được dẫn xuất nghiêm ngặt từ sổ đăng ký tuyến đường đáng tin cậy nội bộ của ứng dụng khách thay vì được chấp nhận từ tham số truy vấn QR bên ngoài.

Tuân thủ mã hóa phần trăm IETF RFC 3986 và giới hạn kích thước tải trọng

Tất cả các tham số điều hướng được gắn vào URL đích của mã QR phải tuân theo cú pháp chung của URI được chỉ định trong IETF RFC 3986:

  • Tránh dấu phân cách lồng nhau: Không nhúng trực tiếp chuỗi JSON thô (?data={"route":"product","id":123}) vào chuỗi truy vấn URL. Các dấu ngoặc nhọn, dấu ngoặc kép và dấu hai chấm không được mã hóa thường làm hỏng trình phân tích cú pháp URL của trình duyệt trong ứng dụng và làm tăng mật độ mô-đun mã vạch.
  • Làm phẳng các cặp khóa-giá trị: Biểu diễn ngữ cảnh điều hướng bên ngoài dưới dạng các tham số truy vấn phẳng, được mã hóa phần trăm (https://promo.example.com/open?route=product_detail&id=sku_8842&channelCode=pos_01).
  • Áp dụng giới hạn ký tự nghiêm ngặt: Giới hạn các giá trị route và id ở các ký tự chữ số an toàn trên URL, dấu gạch ngang và dấu gạch dưới (^[A-Za-z0-9_-]{1,64}$).

Định nghĩa hợp đồng danh sách cho phép tuyến đường ở phía máy khách

Sau khi các tham số URL bên ngoài được tiếp nhận và chuẩn hóa thành cấu trúc tải trọng nội bộ, chúng phải tuân theo một hợp đồng lược đồ nghiêm ngặt. Nếu route_key không được nhận dạng, hoặc nếu một tuyến đường dành riêng cho thực thể (product_detail, room_lobby, promo_campaign) bị thiếu target_id bắt buộc, bộ chuẩn hóa phải hạ cấp tải trọng xuống home_default với target_id: null.

Lược đồ JSON dưới đây xác định cấu trúc chuẩn tắc cho tải trọng khôi phục ngữ cảnh được chuẩn hóa:

{
  "$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"
    }
  ]
}

Bước 2: Thu thập và chuyển tiếp ngữ cảnh điều hướng trên trang đích

Phân tích cú pháp các khóa điều hướng trong danh sách cho phép trên trang đích H5

Khi người dùng chưa cài đặt ứng dụng quét mã QR, trình duyệt di động sẽ mở trang đích H5 trung gian được lưu trữ. Tập lệnh trên trang đích sẽ trích xuất các khóa truy vấn bên ngoài (route, id, channelCode), từ chối các tham số trùng lặp mơ hồ và chuẩn hóa chúng thành cấu trúc chuẩn được xác định ở Bước 1.

Trình chuẩn hóa JavaScript dưới đây thực thi tính đơn trị của khóa, giới hạn biểu thức chính quy (regex) và việc hạ cấp xuống home_default có điều kiện, đồng thời coi các tham số được bảo lưu bị trùng lặp là không hợp lệ:

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();

Tích hợp Web SDK thông qua một bộ điều hợp được xác minh phiên bản

Trang đích điều phối cả các cố gắng đánh thức ứng dụng ngay lập tức và việc dàn dựng tham số trì hoãn. Trước khi triển khai Web SDK quy về nguồn (chẳng hạn như opoinstall.js), hãy xác nhận chính xác URL gói JavaScript, chữ ký khởi tạo, lệnh gọi lại sẵn sàng, phương thức đánh thức và lược đồ tham số dựa trên tài liệu chính thức cho phiên bản SDK đang được triển khai.

Giữ hành vi dành riêng cho SDK phía sau một bộ điều hợp ứng dụng nhỏ chấp nhận đối tượng routingPayload đã được chuẩn hóa. Để biết các thông số kỹ thuật chi tiết về tích hợp web và cấu hình tham số kênh, hãy tham khảo tài liệu nhà phát triển OpoInstall.

Bảo lưu các tham số điều hướng qua các liên kết dự phòng cửa hàng trực tiếp

Nếu Web JS SDK không khả dụng hoặc bị chặn bởi các điều khiển bảo mật của trình duyệt, trang đích sẽ chuyển về liên kết chuyển hướng cửa hàng trực tiếp. Trên các triển khai Android sử dụng Google Play, liên kết dự phòng có thể mã hóa các khóa điều hướng được chuẩn hóa vào tham số referrer của Cửa hàng Play để ứng dụng gốc có thể khôi phục ngữ cảnh điều hướng thông qua Google Play Install Referrer API khi được cài đặt thông qua luồng chính thức của Cửa hàng Play:

// 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)}`;

Bước 3: Thực thi điều phối tuyến đường của máy khách gốc và khôi phục ngữ cảnh

Xử lý các chế độ khởi chạy Activity trên Android: Tại sao singleTask yêu cầu cả onCreate và onNewIntent

Trong các kiến trúc liên kết sâu của Android, cách một Activity nhận các ý định điều hướng phụ thuộc vào chế độ khởi chạy được cấu hình và thiết kế biểu đồ điều hướng của nó. Các nhà phát triển có thể sử dụng các chế độ khởi chạy tiêu chuẩn với Jetpack Navigation, singleTop hoặc singleTask tùy thuộc vào yêu cầu ngăn xếp tác vụ của họ.

Khi một Activity được cấu hình với singleTop hoặc singleTask để ngăn các phiên bản trùng lặp xếp chồng lên nhau trong quá trình đánh thức bằng liên kết sâu, hai đường vòng đời riêng biệt phải được xử lý:

  • Khởi động lạnh (onCreate): Khi quá trình ứng dụng không chạy, việc quét mã QR sẽ khởi chạy Activity từ đầu. Intent đến được gửi tới onCreate(savedInstanceState: Bundle?).
  • Khởi động ấm (onNewIntent): Khi hệ điều hành tái sử dụng một phiên bản Activity hiện có theo chế độ khởi chạy được cấu hình và các điều kiện ngăn xếp tác vụ, Intent liên kết sâu đến sẽ được gửi tới onNewIntent(). Dưới singleTop, việc tái sử dụng chỉ xảy ra khi Activity mục tiêu đã ở đầu ngăn xếp tác vụ có liên quan; nếu không, một phiên bản Activity mới có thể được tạo. Triển khai xử lý cả onCreate() và onNewIntent(), đồng thời xác minh hành vi thực tế cho kiến trúc điều hướng của ứng dụng.

Để xử lý cả hai trạng thái một cách đáng tin cậy mà không chặn vĩnh viễn người dùng quét hợp pháp cùng một mã QR lần thứ hai sau đó trong phiên của họ, Activity nên đánh dấu mỗi phiên bản Intent đã xử lý bằng một cờ tiêu thụ (EXTRA_ROUTE_CONSUMED) và bỏ qua các intent được gửi lại thuần túy bởi các lần khởi chạy từ bộ chuyển đổi ứng dụng gần đây (Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY).

Xử lý các điểm vào vòng đời trên iOS: AppDelegate so với các hoạt động tiếp tục của UISceneDelegate

Trên iOS, việc điều hướng Universal Link trực tiếp và truy xuất tham số trì hoãn phải đồng bộ với vòng đời giao diện người dùng được cấu hình của ứng dụng:

  • Vòng đời dựa trên Scene (UISceneDelegate): Trong các ứng dụng áp dụng UIScene (tiêu chuẩn từ iOS 13 trở đi), các Universal Link khởi động lạnh đến trong scene(_:willConnectTo:options:) thông qua connectionOptions.userActivities, trong khi các Universal Link tiếp tục khi đang mở ấm đến trong scene(_:continue:).
  • Vòng đời Application-Delegate (UIApplicationDelegate): Trong các ứng dụng không sử dụng trình ủy quyền scene, Universal Link được gửi tới application(_:continue:restorationHandler:).
  • Khôi phục lần khởi chạy đầu tiên trì hoãn: Độc lập với các trình ủy quyền tiếp tục Universal Link, truy vấn tham số cài đặt trì hoãn chạy một lần trong quá trình khởi động ứng dụng ban đầu sau khi hệ thống phân cấp chế độ xem gốc đã sẵn sàng.

Các nhà phát triển có thể tải xuống các gói SDK nền tảng và kiểm tra các mẫu tích hợp vòng đời thông qua trung tâm tải xuống SDK OpoInstall.

Hợp nhất các tham số đánh thức trực tiếp và tham số trì hoãn lần khởi chạy đầu tiên thành một bộ định tuyến điều hướng duy nhất

Để duy trì hành vi bảo mật và điều hướng nhất quán, các kiến trúc máy khách chuẩn hóa cả URI đánh thức trực tiếp và tải trọng JSON cài đặt trì hoãn thành một mô hình NavigationTarget duy nhất trước khi điều phối chúng đến bộ định tuyến giao diện người dùng:

  1. Xác minh nguồn gốc (Intent trực tiếp): Xác minh rằng các URI Intent.ACTION_VIEW đến sử dụng lược đồ https, khớp với máy chủ liên kết đã được xác minh của ứng dụng (promo.example.com) và đường dẫn (/open), đồng thời không chứa các khóa truy vấn trùng lặp xung đột.
  2. Chuẩn hóa lược đồ và kiểm tra danh sách cho phép: Ánh xạ các khóa route / id bên ngoài hoặc các trường route_key / target_id bên trong vào mô hình chuẩn tắc, từ chối các tuyến đường không nằm trong danh sách cho phép hoặc các ID bị lỗi. Lưu ý rằng logic phân tích cú pháp Kotlin của máy khách hoạt động như một bộ khung phân tích cú pháp phòng thủ mang tính minh họa; trong các hệ thống sản xuất, hãy phân tích cú pháp tải trọng dựa trên các quy tắc lược đồ chuẩn tắc đầy đủ trước khi điều phối.
  3. Bảo vệ chống tái nhập đơn luồng: Sử dụng một trình bảo vệ nguyên tử trên mỗi lần phân phối để các lệnh gọi lại đồng thời không thể kích hoạt các giao dịch phân đoạn chồng chéo.

Quản lý các cổng xác thực: Xếp hàng đợi các tuyến liên kết sâu đang chờ xử lý cho đến khi quá trình đăng nhập của người dùng hoàn tất

Các tuyến QR được bảo vệ có thể tiếp tục sau khi xác thực trong khi các mục tiêu không hợp lệ sẽ quay lại một cách an toàn.

Khi một người dùng mới cài đặt ứng dụng từ cửa hàng ứng dụng thông qua mã QR và khởi chạy lần đầu tiên, người dùng đó thường chưa được xác thực. Nếu mã QR nhắm mục tiêu vào một màn hình được bảo vệ (chẳng hạn như room_lobby hoặc promo_campaign) và bộ định tuyến cố gắng điều hướng ngay lập tức, cổng xác thực sẽ chuyển hướng người dùng đến màn hình đăng nhập và làm mất ngữ cảnh liên kết sâu.

Để bảo lưu việc khôi phục ngữ cảnh qua các ranh giới xác thực:

  • Tra cứu chính sách tuyến đường nội bộ: Xác định xem tuyến đường có yêu cầu xác thực hay không bằng cách kiểm tra sổ đăng ký PROTECTED_ROUTES nội bộ của máy khách thay vì tin tưởng các cờ tải trọng bên ngoài.
  • Dựng hình nguyên tử: Nếu người dùng chưa được xác thực, hãy lưu trữ NavigationTarget đã được xác thực trong một PendingRouteStore có phạm vi phiên, nguyên tử (AtomicReference) và điều hướng người dùng đến luồng đăng nhập. Lưu ý rằng PendingRouteStore nằm trong bộ nhớ áp dụng cho quá trình ứng dụng hoạt động; nếu luồng xác thực có thể kéo dài qua việc kết thúc quá trình, hãy sử dụng kho lưu trữ cục bộ được mã hóa, có giới hạn thời gian để xóa trạng thái khi chuyển đổi tài khoản.
  • Xác thực lại và điều phối sau xác thực: Khi quá trình đăng nhập hoàn tất, hãy tiêu thụ mục tiêu đang chờ xử lý một cách nguyên tử (getAndSet(null)), xác minh rằng phiên hiện đã được xác thực, xác thực lại các tham số tuyến đường và thực hiện chuyển đổi màn hình.

Sự sẵn sàng của máy chủ điều hướng và khôi phục trạng thái

Lệnh gọi lại onNavigationGraphReady() phải được kết nối với quá trình khởi tạo máy chủ điều hướng thực tế của ứng dụng. Khi biểu đồ điều hướng được xác định trong một NavHostFragment được lưu trữ bằng XML, hoạt động có thể thiết lập kết nối này sau khi máy chủ và đích của nó sẵn sàng. Nếu biểu đồ được cài đặt không đồng bộ, chỉ gọi lệnh gọi lại sau khi quá trình khởi tạo biểu đồ hoàn tất.

Yêu cầu điều hướng đang chờ xử lý được lưu trữ thuần túy trong một trường Activity có thể bị mất trong quá trình thay đổi cấu hình. Đối với các trường hợp triển khai trong môi trường sản xuất, hãy giữ lại trạng thái điều hướng chưa được tiêu thụ trong ViewModel nhận biết vòng đời và sử dụng SavedStateHandle khi cần khôi phục qua việc tạo lại Activity do hệ thống thúc đẩy hoặc kết thúc quá trình. Mã được hiển thị ở đây là một ví dụ về kiến trúc và yêu cầu tích hợp điều hướng và phiên dành riêng cho ứng dụng.

Mã Kotlin bên dưới cung cấp một bộ khung điều hướng Android minh họa với các bộ điều hợp SDK và điều hướng do ứng dụng sở hữu minh họa các điều khiển này:

<!-- 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
    }
}

Bảo mật và Chẩn đoán: Ngăn chặn tiêm nhiễm tuyến đường và lỗi điều hướng

Phòng thủ chống lại việc tiêm nhiễm tuyến đường liên kết sâu và các lỗ hổng chuyển hướng mở theo nguyên tắc của OWASP

Các tham số được trích xuất từ mã QR hoặc các lệnh gọi lại quy về nguồn trì hoãn đại diện cho đầu vào bên ngoài không đáng tin cậy. Theo Tiêu chuẩn xác minh bảo mật ứng dụng di động OWASP và 10 rủi ro và hướng dẫn bảo mật hàng đầu cho thiết bị di động của OWASP, các bộ định tuyến máy khách phải thực thi ba ranh giới phòng thủ:

  • Cấm tải URL WebView tùy ý: Không bao giờ cho phép tham số điều hướng (?route=webview&url=https://...) tải một URL bên ngoài chưa được xác thực bên trong WebView của ứng dụng nội bộ. Kẻ tấn công có thể mã hóa các URL lừa đảo vào mã QR, khai thác khung giao diện người dùng gốc của ứng dụng để thu thập thông tin đăng nhập.
  • Từ chối ô nhiễm tham số trùng lặp: Nếu một URL bị thao túng chứa nhiều khóa xung đột (?route=home_default&route=room_lobby), hãy từ chối URI thay vì cho phép trình phân tích cú pháp chọn một phần tử đầu hoặc cuối tùy ý.
  • Thực thi ủy quyền tài nguyên phụ trợ: Việc truyền target_id=sku_8842 chỉ hướng dẫn máy khách chế độ xem nào cần yêu cầu. Khi màn hình truy vấn API phụ trợ, máy chủ phải xác minh độc lập rằng phiên người dùng hiện tại được ủy quyền truy cập thực thể đó, ngăn chặn các lỗ hổng Tham chiếu đối tượng trực tiếp không an toàn (IDOR).

Chẩn đoán các điều kiện tranh chấp điều hướng khởi động lạnh so với khởi động ấm

Trong quá trình khởi động lạnh, các ý định liên kết sâu hoặc các lệnh gọi lại SDK được lưu vào bộ nhớ đệm có thể phân giải trước khi NavHostFragment chính hoặc biểu đồ điều hướng gốc hoàn tất việc nạp. Việc cố gắng gọi navController.navigate() trước khi hệ thống phân cấp chế độ xem sẵn sàng sẽ gây ra ngoại lệ thời gian chạy hoặc làm rơi các chuyển đổi. Luôn hoãn việc thực thi điều hướng cho đến khi hệ thống phân cấp chế độ xem máy chủ được đính kèm và hoạt động (!isFinishing && !isDestroyed).

Xử lý các chương trình khuyến mãi đã hết hạn, SKU bị xóa và các tham số bị thiếu một cách mượt mà

Mã QR vật lý thường vẫn được triển khai tại các cửa hàng bán lẻ hoặc phương tiện truyền thông in ấn sau khi chương trình khuyến mãi kỹ thuật số đã kết thúc hoặc một SKU sản phẩm đã ngừng cung cấp. Khi màn hình mục tiêu tìm nạp dữ liệu cho target_id và nhận được phản hồi HTTP 404 Not Found hoặc 410 Gone từ phụ trợ, máy khách phải bắt lỗi, hiển thị thông báo thông tin rõ ràng cho người dùng và điều hướng sạch sẽ về home_default.

Bảng kiểm tra xác minh trước khi triển khai cho điều hướng mã QR trạng thái kép

Trước khi phát hành tài liệu mã QR vật lý, các đội ngũ kỹ thuật nên thực hiện giao thức xác minh bảy điểm sau trên các thiết bị vật lý:

  • Bước 1: Đánh thức khởi động lạnh đã cài đặt: Buộc đóng ứng dụng đã cài đặt và quét mã QR. Xác nhận hệ điều hành mở ứng dụng trực tiếp vào màn hình mục tiêu (product_detail).
  • Bước 2: Đánh thức khởi động ấm đã cài đặt và quét lặp lại: Với ứng dụng mở trên một tab phụ, đưa ứng dụng về nền và quét mã QR. Xác nhận onNewIntent điều hướng đến màn hình mục tiêu. Điều hướng đi nơi khác, quét chính xác cùng một mã QR lần thứ hai và xác nhận nó điều hướng lại thay vì bị chặn bởi việc khử trùng lặp quá mức.
  • Bước 3: Khôi phục trì hoãn chưa cài đặt (Tuyến đường công khai): Gỡ cài đặt ứng dụng, quét mã QR, hoàn tất cài đặt từ cửa hàng và khởi chạy. Xác nhận lệnh gọi lại tham số trì hoãn chuẩn hóa tải trọng và điều hướng đến product_detail.
  • Bước 4: Khôi phục trì hoãn chưa cài đặt có cổng xác thực (Tuyến đường được bảo vệ): Gỡ cài đặt ứng dụng, quét mã QR nhắm mục tiêu room_lobby, cài đặt và khởi chạy khi đang đăng xuất. Xác nhận ứng dụng điều hướng đến Đăng nhập trước, và sau khi hoàn tất đăng nhập, tiêu thụ PendingRouteStore và điều hướng đến room_lobby.
  • Bước 5: Từ chối nguồn gốc không đáng tin cậy và khóa trùng lặp: Kích hoạt ý định ACTION_VIEW từ một máy chủ không đáng tin cậy hoặc có các tham số truy vấn trùng lặp (?route=product_detail&route=room_lobby). Xác nhận handleIncomingWakeupIntent từ chối ý định và ghi nhật ký cảnh báo.
  • Bước 6: Tính bất biến khi xoay màn hình: Sau khi đến màn hình liên kết sâu, hãy xoay thiết bị để kích hoạt việc tạo lại Activity. Xác nhận cờ EXTRA_ROUTE_CONSUMED ngăn ý định ban đầu thực thi lại.
  • Bước 7: Dự phòng ngoại tuyến / không khớp mượt mà: Khởi chạy một bản cài đặt sạch ở chế độ trên máy bay. Xác nhận thời gian chờ truy xuất giải quyết mà không chặn luồng giao diện người dùng và đưa người dùng đến home_default.

Phân tích so sánh các cơ chế điều hướng trong ứng dụng

Đánh giá Lược đồ URL, Universal Links và App Links, cùng Khôi phục ngữ cảnh trì hoãn

Các nền tảng di động cung cấp các cơ chế riêng biệt để điều hướng người dùng vào các chế độ xem của ứng dụng trên các trạng thái cài đặt khác nhau:

Cơ chế điều hướng Trạng thái ứng dụng mục tiêu Yêu cầu xác minh của hệ điều hành Sự tồn tại của tham số qua quá trình cài đặt từ cửa hàng Ràng buộc kỹ thuật chính
Lược đồ URL tùy chỉnh Chỉ đã cài đặt Không có (Chỉ khai báo trong Manifest) Không (Thất bại nếu ứng dụng chưa được cài đặt) Kích hoạt cửa sổ bật lên lỗi trình duyệt nếu vắng mặt; dễ bị chiếm đoạt lược đồ
Universal Links / App Links Đã cài đặt là chính iOS: Tệp AASA + quyền Associated Domains; Android: assetlinks.json + chứng chỉ ký ứng dụng khớp và cấu hình bộ lọc ý định đã được xác minh Không (Yêu cầu lớp trì hoãn cho các cài đặt mới) Có thể bị chặn bởi một số vùng chứa webview trong ứng dụng bị hạn chế
Google Play Install Referrer Cài đặt Android mới Đường dẫn phân phối Cửa hàng Google Play chính thức Có (Chuỗi giới thiệu được hỗ trợ) Đặc thù cho Cửa hàng Google Play Android; không bao gồm iOS hoặc các cửa hàng ngoài Play
Khôi phục ngữ cảnh hỗ trợ bởi SDK Đã cài đặt & Cài đặt mới Liên kết tên miền + bộ điều hợp SDK được xác minh Phụ thuộc vào cơ chế nền tảng được hỗ trợ Yêu cầu xác thực danh sách cho phép tuyến đường & đánh giá quyền riêng tư của nền tảng

Đánh đổi kiến trúc qua các trạng thái cài đặt và hệ điều hành

Lược đồ URL tùy chỉnh (myapp://product/123) thiếu xác minh quyền sở hữu tên miền bằng mật mã, có nghĩa là bất kỳ ứng dụng nào cũng có thể đăng ký cùng một lược đồ và chặn các liên kết. Hơn nữa, việc quét mã QR lược đồ tùy chỉnh thô khi ứng dụng chưa được cài đặt dẫn đến lỗi trình duyệt không thể phục hồi.

Universal Links và Android App Links xác minh quyền sở hữu tên miền qua HTTPS và chuyển về trang đích web khi vắng mặt ứng dụng, làm cho chúng trở thành nền tảng bắt buộc cho việc điều hướng mã QR. Việc kết hợp Universal Links và App Links để đánh thức khi đã cài đặt với khôi phục trì hoãn được nền tảng hỗ trợ (chẳng hạn như Google Play Install Referrer trên Android và các bộ điều hợp SDK đã được đánh giá về quyền riêng tư) cho phép khôi phục ngữ cảnh trạng thái kép từ một tài sản mã QR duy nhất.

Các câu hỏi thường gặp (FAQ)

Các tham số QR tùy chỉnh mở một trang cụ thể trong ứng dụng như thế nào nếu ứng dụng chưa được cài đặt?
Khi ứng dụng chưa được cài đặt, mã QR sẽ mở một trang đích web ghi lại các tham số điều hướng trước khi hướng người dùng đến kho ứng dụng. Ngay trong lần khởi chạy đầu tiên sau khi cài đặt, ứng dụng cố gắng khôi phục các tham số thông qua cơ chế khôi phục trì hoãn được hỗ trợ bởi nền tảng, xác thực tuyến đường mục tiêu và điều hướng đến màn hình được chỉ định.
Tại sao việc quét mã QR của tôi lại mở ứng dụng ở màn hình chính thay vì trang mục tiêu?
Việc chuyển đến màn hình chính thường xảy ra do ba nguyên nhân: Activity trên Android được cấu hình là `singleTop` hoặc `singleTask` mà không ghi đè `onNewIntent` để nắm bắt các intent khởi động ấm, bộ định tuyến điều hướng thực thi trước khi biểu đồ giao diện người dùng chính hoàn tất việc nạp, hoặc màn hình mục tiêu yêu cầu người dùng xác thực và đã hủy bỏ tuyến đường đang chờ xử lý trong luồng đăng nhập.
Làm thế nào để tôi ngăn chặn các mã QR độc hại điều hướng người dùng đến các màn hình nội bộ trái phép?
Không bao giờ truyền trực tiếp các đường dẫn URL thô vào bộ điều khiển điều hướng của bạn. Xác minh lược đồ URI, máy chủ và đường dẫn đến, thực thi danh sách cho phép các khóa tuyến đường được phép ở phía máy khách một cách nghiêm ngặt, xác thực định dạng ID thực thể bằng các biểu thức chính quy, đồng thời xác minh cả trạng thái xác thực của máy khách và quyền tài nguyên ở phía máy chủ trước khi hiển thị các màn hình được bảo vệ.

Tóm tắt và Khung quyết định

Để tạo mã QR điều hướng ứng dụng với các tham số tùy chỉnh, các đội ngũ kỹ thuật phải kết nối các chuỗi truy vấn URL bên ngoài nhỏ gọn với các bộ điều khiển điều hướng gốc nhận biết trạng thái. Quá trình triển khai hoàn chỉnh xử lý cả việc đánh thức hệ điều hành trực tiếp cho người dùng hiện tại và khôi phục tham số trì hoãn có điều kiện cho các cài đặt cửa hàng ứng dụng mới.

Vì tính liên tục của tham số qua các kho ứng dụng phụ thuộc vào các cơ chế đặc thù của nền tảng—chẳng hạn như Google Play Install Referrer trên Android và các quy trình tuân thủ quyền riêng tư đã được xác minh trên iOS—các ứng dụng phải coi các tải trọng điều hướng được khôi phục là các ứng viên chưa đáng tin cậy. Bằng cách kết hợp các trình điều phối tuyến đường nằm trong danh sách cho phép, xếp hàng đợi trạng thái xác thực và các đường dẫn dự phòng về màn hình chính mượt mà, các đội ngũ có thể cung cấp tính năng khôi phục ngữ cảnh đáng tin cậy mà không làm tổn hại đến sự ổn định của ứng dụng hoặc các quy tắc quyền riêng tư của nền tảng.

Các SDK và tài liệu dành cho nhà phát triển của OpoInstall cung cấp các thành phần để triển khai điều hướng web-to-app và chuyển đổi tham số. Xác nhận các chữ ký API cụ thể, lược đồ lệnh gọi lại và hành vi nền tảng được hỗ trợ bởi phiên bản SDK được triển khai của bạn trước khi đưa vào sản xuất.

Để biết chi tiết về tích hợp, hãy tham khảo tài liệu nhà phát triển OpoInstall, truy cập trung tâm tải xuống SDK OpoInstall, hoặc cấu hình các tên miền điều hướng ứng dụng của bạn trong bảng điều khiển dành cho nhà phát triển OpoInstall.

Tài liệu liên quan

Share this article