Cara Menghantar Parameter Penjejakan dari Web ke Pemasangan Aplikasi

opoinstall
2026-10-10
5 min read

Bagaimanakah cara menghantar parameter penjejakan dari laman web mudah alih ke pemasangan stor? Menghantar parameter penjejakan dari laman web mudah alih ke aplikasi yang baru dipasang melibatkan pemuatan SDK JS Web OpoInstall, menangkap parameter URL yang dibenarkan (allowlisted), dan memulihkan data atribusi yang disokong melalui SDK natif apabila aplikasi dilancarkan buat kali pertama.

Penyambungan parameter merentas muat turun stor aplikasi adalah proses mengekalkan parameter URL web yang layak apabila pengguna beralih dari halaman pendaratan mudah alih ke stor aplikasi dan seterusnya melancarkan aplikasi yang baru dipasang. SDK web menangkap konteks kempen atau rujukan, manakala mekanisme yang disokong platform atau perkhidmatan pemulihan tertunda mungkin menjadikan parameter yang berkaitan tersedia untuk aplikasi natif. Pemulihan bergantung pada platform, konfigurasi SDK, isyarat padanan yang tersedia, dan keperluan privasi yang terpakai.

Istilah Definisi Entiti Berkaitan Peranan Niat Carian
Parameter Penjejakan Pasangan kunci-nilai yang ditambah pada URL untuk mengenal pasti konteks trafik, sumber kempen, atau maklumat rujukan. Rentetan Pertanyaan URL Maklumat / Teknikal
Pemasangan Parameter Aplikasi Aliran kerja pemulihan parameter tertunda yang menjadikan konteks web yang layak tersedia selepas pemasangan. SDK Mudah Alih Natif Teknikal / Maklumat
Google Play Install Referrer API Android rasmi untuk mendapatkan metadata rujukan yang dikaitkan dengan pemasangan Google Play. SDK Android Teknikal

Jarak Atribusi Web-ke-Stor: Halangan Platform Merentas Stor Aplikasi

Parameter penjejakan web tidak secara automatik melintasi sempadan pemasangan stor aplikasi.

Halangan Pemutusan: Bagaimana Kotak Pasir Sistem Pengendalian Mengasingkan Konteks Pelayar

Analitis web biasanya menggunakan rentetan pertanyaan URL, termasuk tag UTM (utm_source, utm_medium, utm_campaign), pengecam klik pengiklanan seperti gclid dan fbclid, serta parameter rujukan tersuai.

Dalam laman web yang mengambil bahagian, API pelayar dan sistem analitis pihak pertama boleh menangkap nilai ini untuk aliran kerja analitis dan kempen yang dibenarkan.

Cabaran timbul apabila pelawat meninggalkan pelayar dan memasuki proses pemasangan stor aplikasi. Pelayar dan aplikasi natif yang baru dipasang beroperasi dalam persekitaran yang berasingan. Kuki pelayar, storan sesi, dan parameter URL halaman tidak secara automatik tersedia di dalam aplikasi selepas pemasangan.

Oleh itu, pembangun memerlukan mekanisme rujukan yang disokong stor atau aliran kerja pemulihan parameter tertunda untuk menyambungkan konteks pra-pemasangan yang layak dengan sesi aplikasi pasca-pemasangan.

Spesifikasi Platform: Apple App Store vs. Ingestion Google Play Store

Android dan iOS menggunakan mekanisme berbeza sebelum parameter dinormalkan ke dalam satu kontrak aplikasi.

Stor aplikasi mudah alih menyokong pendekatan berbeza untuk menghantar konteks rujukan.

  • Apple App Store (iOS): Pemasangan App Store standard tidak secara automatik memajukan parameter pertanyaan URL luaran sewenang-wenangnya ke dalam persekitaran pelancaran pertama aplikasi yang baru dipasang. Perkhidmatan pemulihan tertunda atau aliran kerja pengantaraan pengguna eksplisit mungkin membantu memulihkan konteks yang layak, tertakluk kepada keperluan privasi Apple.
  • Google Play Store (Android): Google menyediakan API Google Play Install Referrer rasmi. Jika aliran rujukan Google Play yang dibina dengan betul digunakan, aplikasi yang dipasang boleh mendapatkan semula metadata rujukan yang disokong, termasuk rentetan perujuk dan cap masa yang relevan.

Mekanisme ini tidak boleh dianggap sebagai boleh ditukar ganti. Google Play menyediakan antara muka rujukan peringkat stor rasmi, manakala pemulihan parameter tertunda sewenang-wenangnya pada iOS memerlukan mekanisme yang direka secara berasingan dan mematuhi polisi.

Kesan Perniagaan Trafik Pemasangan Tanpa Atribusi terhadap Pengiraan ROI Saluran

Jika parameter kempen hilang antara lawatan web dan pemasangan aplikasi, sistem analitis mungkin tidak mengaitkan pemasangan tersebut dengan kempen asalnya dengan betul.

Ini boleh menyebabkan pemasangan tidak diatribusikan, pelaporan pemerolehan yang tidak lengkap, dan pengiraan Pulangan Pelaburan (ROI) atau Kos Pemerolehan Pelanggan (CAC) yang tidak tepat.

Kehilangan parameter juga boleh menjejaskan pengalaman onboarding. Sebagai contoh, pautan rujukan web mungkin menyertakan pengecam penjemput atau kod promosi:

https://example.com/download?inviter_id=USR_1024
https://example.com/download?promo_code=WELCOME50

Jika aplikasi tidak dapat memulihkan nilai ini, pengguna mungkin perlu memasukkannya secara manual.

Walau bagaimanapun, memulihkan nilai rujukan atau promosi tidak secara automatik membenarkan ganjaran. Bahagian belakang aplikasi mesti mengesahkan kelayakan, had penebusan, dan peraturan perniagaan yang terpakai secara bebas.

Mekanisme Parameter Disokong Stor vs. Tertunda: Apple App Store vs. Google Play

Mekanisme Google Play Store: Memanfaatkan API Install Referrer Natif

Pada Android, API Google Play Install Referrer menawarkan mekanisme rasmi untuk mendapatkan semula metadata rujukan pemasangan yang disokong.

URL muat turun Google Play boleh menyertakan parameter referrer yang dikodkan:

https://play.google.com/store/apps/details?id=com.example.app&referrer=utm_source%3Dfacebook%26inviter_id%3DUSR_1024

Dalam URL ilustrasi ini, nilai perujuk yang dikodkan mewakili:

utm_source=facebook&inviter_id=USR_1024

Selepas pemasangan, aplikasi Android natif boleh menggunakan Pelanggan Install Referrer untuk mendapatkan maklumat rujukan yang tersedia daripada Google Play.

Sama ada keluaran tertentu SDK Web OpoInstall menjana, mengubah suai, atau mengurus URL sedemikian secara automatik mesti disahkan terhadap dokumentasi dan tingkah lakunya yang diguna pakai. Kewujudan API rasmi Google tidak menetapkan pelaksanaan dalaman SDK pihak ketiga.

Sempadan Apple App Store: Mengendalikan Ubah Hala Stor Standard Tanpa Parameter Pertanyaan Natif

Apple App Store tidak menghantar parameter pertanyaan web sewenang-wenangnya secara terus ke panggilan balik pelancaran pertama aplikasi yang baru dipasang.

Oleh itu, aliran kerja pemulihan tertunda mungkin melibatkan penangkapan maklumat kempen yang dibenarkan sebelum ubah hala stor dan meminta konteks yang berkaitan apabila aplikasi natif dilancarkan.

Untuk aliran kerja berasaskan SDK, pembangun mesti membezakan antara fungsi yang didokumenkan oleh vendor dan kaedah pemadanan yang digunakan oleh versi SDK yang digunakan.

Sebarang kaedah pemadanan atau pengumpulan data mesti mematuhi polisi privasi Apple. Khususnya, pelaksanaan atribusi tidak boleh bergantung pada cap jari peranti (fingerprinting) yang dilarang atau menganggap bahawa kebenaran ATT menjadikan kaedah pemadanan yang dilarang sebaliknya menjadi boleh diterima.

Pautan Universal berguna untuk menghalakan pautan yang disokong ke dalam aplikasi yang sudah dipasang, tetapi ia sendiri tidak menjamin bahawa parameter pertanyaan web sewenang-wenangnya akan bertahan selepas pemasangan App Store yang baharu.

Perbezaan Senibina Merentas Platform: Membina Kontrak Parameter Bersatu

Walaupun Android dan iOS menggunakan mekanisme platform yang berbeza, aplikasi boleh menormalkan nilai yang dipulihkan ke dalam skema peringkat aplikasi yang dikongsi.

Sebagai contoh:

{
  "utm_source": "facebook",
  "utm_medium": "paid_social",
  "utm_campaign": "summer_sale",
  "inviter_id": "USR_1024",
  "promo_code": "WELCOME50"
}

Objek JSON ini mewakili kontrak parameter ternormal yang ditakrifkan oleh aplikasi, bukan format respons literal yang dijamin daripada setiap keluaran SDK OpoInstall.

Aplikasi harus memisahkan medan ini kepada dua kategori:

  • Atribusi pemasaran: utm_source, utm_medium, dan utm_campaign.
  • Tindakan rujukan dan promosi: inviter_id dan promo_code.

Atribusi pemasaran boleh diserahkan kepada saluran analitis yang dibenarkan. Tindakan rujukan dan promosi memerlukan pengesahan bahagian belakang bebas sebelum aplikasi memberikan sebarang faedah.

Peranan Caching Beban Data Kontekstual dalam Merapatkan Ubah Hala Stor

Perkhidmatan pemulihan tertunda biasanya memerlukan tempoh pengekalan terhad untuk konteks pemasangan.

Tetingkap pengekalan yang terpakai, tingkah laku pemulihan, dan ketersediaan padanan bergantung pada perkhidmatan yang diguna pakai, konfigurasi SDK, keadaan rangkaian, had platform, dan keperluan privasi.

Pasukan kejuruteraan harus mengukur hasil pemulihan menggunakan kohort pemasangan mereka sendiri dan bukannya menerima dakwaan ketepatan yang tidak disokong.

Seni Bina Konteks Tertunda yang Sedar Privasi dan Sempadan Platform

Sempadan Papan Klip Sistem pada iOS: Sepanduk Ketelusan dan Mekanik Niat UIPasteControl

Beberapa pelaksanaan pemautan tertunda sejarah menggunakan papan klip sistem untuk membawa konteks sementara antara aplikasi.

Walau bagaimanapun, iOS menyekat dan mendedahkan akses papan klip merentas aplikasi kepada pengguna.

  • iOS 14: Membaca kandungan yang disalin daripada aplikasi lain boleh mencetuskan pemberitahuan akses papan klip.
  • iOS 16 dan seterusnya: Operasi tampal programatik tertentu boleh mencetuskan gesaan kebenaran. Kawalan seperti UIPasteControl menyokong interaksi tampal yang dimulakan oleh pengguna.

Oleh itu, akses papan klip umum tidak boleh dianggap sebagai saluran pengangkutan parameter pemasangan yang tidak kelihatan atau tidak terhad.

Aplikasi yang menggunakan fungsi berkaitan papan klip harus menjadikan interaksi tersebut sesuai dengan jangkaan pengguna dan mengendalikan kebenaran yang ditolak atau kandungan yang hilang dengan lancar.

Sekatan Papan Klip Latar Depan Android: Kekangan Navigasi dan Pembersihan Automatik

Android juga mengenakan sekatan ke atas akses papan klip.

  • Android 10 dan seterusnya: Akses papan klip disekat untuk aplikasi yang tidak berada dalam konteks latar depan yang layak, tertakluk kepada pengecualian yang ditakrifkan oleh platform.
  • Android 13 dan seterusnya: Android menyediakan maklum balas antara muka pengguna berkaitan papan klip dan tingkah laku pembersihan automatik untuk kandungan papan klip.

Aplikasi tidak boleh menganggap bahawa data papan klip boleh diambil secara senyap atau kekal tersedia selama-lamanya.

Akses papan klip haruslah bersifat pilihan dan terhad kepada aliran kerja berorientasikan pengguna yang sesuai dan bukannya berfungsi sebagai saluran atribusi utama yang dianggap.

Pematuhan Ketat terhadap Polisi Privasi Pengguna dan Penggunaan Data Apple: Melarang Cap Jari Peranti

Apple melarang cap jari peranti (fingerprinting) yang digunakan untuk mengenal pasti pengguna atau peranti melalui kombinasi ciri peranti dan persekitaran.

Pelaksanaan tidak boleh membina pengecam berterusan atau kebarangkalian daripada gabungan alamat IP, maklumat ejen pengguna, ciri paparan, atau isyarat peranti lain yang melanggar polisi Apple.

Memberikan kebenaran App Tracking Transparency tidak menjadikan kaedah cap jari yang dilarang menjadi boleh diterima.

Bagi mana-mana perkhidmatan pemulihan parameter tertunda pihak ketiga, pembangun harus menyemak pengumpulan data sebenar SDK yang digunakan, tingkah laku pemadanan, pendedahan privasi, dan keperluan polisi platform.

Integrasi yang sedar privasi harus mengutamakan peminimuman data, interaksi pengguna yang telus, dan mekanisme platform yang disokong.

Sempadan Keselamatan dan Kepercayaan: Mengurus Kitar Hayat Parameter dan Pengesahan Pelayan

Parameter pemasangan yang dipulihkan memerlukan analitis berasingan dan pemprosesan pengesahan bahagian belakang.

Semua parameter yang dipulihkan mesti dianggap sebagai input yang tidak dipercayai.

Tag pemasaran dan token rujukan boleh berasal daripada URL yang boleh diedit atau permintaan pelanggan yang tidak dipercayai. Kehadirannya sahaja tidak membuktikan bahawa kempen adalah sah atau pengguna layak mendapat ganjaran.

Aplikasi harus mengikuti prinsip ini:

  • Nilai yang dibenarkan (Allowlisted): Terima hanya kunci yang disokong secara eksplisit dan nilai yang terhad.
  • Asingkan atribusi daripada pengesahan: Nilai UTM mungkin menerangkan asal trafik; ia tidak boleh mengesahkan faedah promosi secara bebas.
  • Rekod tertunda terikat masa: Simpan hanya maklumat yang diperlukan untuk pemprosesan, tertakluk kepada tetingkap tamat tempoh yang ditakrifkan oleh aplikasi.
  • Storan terlindung: Gunakan storan yang sesuai dengan sensitiviti token tertunda, seperti reka bentuk yang disokong Android Keystore atau iOS Keychain Services jika perlu.
  • Hasil penghantaran bebas: Untuk rekod yang mengandungi kedua-dua parameter pemasaran dan ganjaran, aplikasi mesti menjejaki hasil yang diperlukan secara bebas.
  • Pengesahan ganjaran bahagian belakang: Kredit rujukan, diskaun promosi, dan hak yang serupa mesti diluluskan oleh bahagian belakang aplikasi yang disahkan.
  • Aplikasi ganjaran idempoten: Bahagian belakang harus menguatkuasakan kekangan idempotensi peringkat transaksi dan keunikan peringkat perniagaan supaya permintaan pelanggan yang berulang tidak boleh menduplikasi ganjaran sekali sahaja.

Kod di bawah menggambarkan sempadan parameter dan tanggungjawab integrasi aplikasi hos. Ia tidak cuba menyediakan sistem storan, analitis, atau transaksi ganjaran gred pengeluaran yang lengkap.

Pelaksanaan Hujung-ke-Hujung: Menangkap Parameter Web ke Pemulihan Natif

Menyucikan Parameter Pertanyaan Web: Bootstrap HTML, Penghuraian Allowlist, dan Penyediaan SDK JS Web

Halaman pendaratan boleh menangkap parameter pertanyaan yang disokong secara eksplisit dan menghantarnya ke SDK Web sebagai beban data terhad.

Contoh berikut menunjukkan:

  • Memuatkan SDK daripada sumber yang dikonfigurasikan.
  • Mengekalkan pautan sandaran muat turun HTTPS biasa.
  • Menerima hanya kunci parameter yang disokong.
  • Menolak nilai di luar polisi aksara dan panjang yang sempit.
  • Menyambungkan tindakan muat turun SDK ke butang muat turun halaman.

Pembina SDK, kunci konfigurasi, dan kaedah adalah wakil. Sahkan tandatangannya terhadap keluaran SDK Web OpoInstall yang tepat sebelum penerbitan atau penggunaan.

<!-- CODE_BLOCK_01: Bootstrap HTML dan Integrasi SDK Web Wakil -->
<!-- Fail: landing_page.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Muat Turun Aplikasi</title>
</head>
<body>
    <!-- Gantikan dengan muat turun HTTPS sebenar atau URL penghalaan stor. -->
    <a
        id="download-app-btn"
        href="https://app.example.com/store-download"
    >
        Muat Turun Aplikasi
    </a>

    <!-- Sahkan URL CDN rasmi terhadap versi SDK yang digunakan. -->
    <script src="https://web.cdn.opoinstallcloud.com/openinstall.js"></script>

    <script>
    (function () {
        "use strict";

        function getSanitizedQueryParams() {
            var allowedKeys = new Set([
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "inviter_id",
                "promo_code"
            ]);

            var validPattern = /^[A-Za-z0-9_.-]{1,64}$/;
            var result = Object.create(null);

            try {
                var query = new URLSearchParams(window.location.search);

                query.forEach(function (value, key) {
                    if (
                        allowedKeys.has(key) &&
                        validPattern.test(value) &&
                        !Object.prototype.hasOwnProperty.call(result, key)
                    ) {
                        result[key] = value;
                    }
                });
            } catch (error) {
                console.warn("Tidak dapat menghuraikan parameter URL.");
                return Object.create(null);
            }

            return result;
        }

        var downloadButton = document.getElementById(
            "download-app-btn"
        );

        // Kekalkan href sauh sebagai sandaran pelayar biasa.
        if (!downloadButton) {
            return;
        }

        if (typeof OpenInstall === "undefined") {
            console.warn(
                "SDK Web tidak tersedia; menggunakan pautan muat turun."
            );
            return;
        }

        var customParams = getSanitizedQueryParams();

        try {
            new OpenInstall({
                appKey: "OPOINSTALL_APPKEY_PLACEHOLDER",
                data: customParams,

                onready: function () {
                    var sdkClient = this;

                    downloadButton.addEventListener(
                        "click",
                        function (event) {
                            try {
                                // Kaedah SDK wakil.
                                // Sahkan tingkah lakunya dan pengendalian kegagalan async
                                // untuk keluaran yang digunakan.
                                sdkClient.wakeupOrInstall();
                                event.preventDefault();
                            } catch (error) {
                                console.warn(
                                    "Tindakan SDK gagal; pelayar akan menggunakan sandaran."
                                );

                                // Jangan halang tindakan sauh lalai.
                            }
                        }
                    );
                }
            });
        } catch (error) {
            console.warn(
                "Permulaan SDK Web gagal; menggunakan pautan sandaran."
            );
        }
    })();
    </script>
</body>
</html>

Polisi nilai secara sengaja menerima hanya huruf, digit, garis bawah, tanda sempang, dan titik, dengan maksimum 64 aksara.

Nilai yang mengandungi ruang atau aksara lain dikecualikan dalam pelaksanaan rujukan ini. Pasukan dengan keperluan penamaan UTM yang lebih luas harus mentakrifkan dan menguji skema khusus medan yang sesuai dan bukannya secara senyap mengandaikan semua nilai URL disokong.

Begitu juga, pengecam klik pengiklanan seperti gclid dan fbclid dibincangkan sebagai konsep atribusi web biasa tetapi tidak disertakan dalam senarai benarkan sampel ini. Menyokongnya memerlukan pengumpulan data, pengekalan, dan semakan privasi yang berasingan.

Sauh sandaran kekal tersedia apabila SDK tidak dimuatkan atau tidak dapat dimulakan. Oleh kerana operasi SDK boleh gagal secara tidak segerak, integrasi pengeluaran mesti tambahan pula mengikuti pengendalian ralat dan tingkah laku ubah hala yang didokumenkan oleh SDK; try/catch segerak sahaja bukanlah strategi sandaran tidak segerak yang lengkap.

Pelaksanaan Natif Android: Penyediaan SDK, Pengesahan Parameter, dan Kontrak Aplikasi Hos

Selepas pemasangan, aplikasi Android boleh meminta parameter yang disokong daripada SDK natif dan menormalkannya ke dalam rekod yang ditakrifkan oleh aplikasi.

Sampel di bawah secara sengaja memisahkan tanggungjawab:

  1. SDK OpoInstall dimulakan dalam proses utama aplikasi.
  2. Panggilan balik natif membekalkan beban data calon.
  3. Aplikasi mengesahkan dan menormalkan beban data.
  4. Aplikasi menyimpan rekod melalui antara muka storan yang disediakan oleh hos.
  5. Komponen pemprosesan yang disediakan oleh hos menyelaraskan atribusi pemasaran dan pengesahan ganjaran.
  6. Aplikasi menandakan rekod selesai hanya selepas komponen itu melaporkan hasil pemprosesan yang disahkan.

Penting: Antara muka hos ialah kontrak yang ditakrifkan oleh aplikasi, bukan API OpoInstall. Contoh ini memerlukan aplikasi hos untuk membekalkan pelaksanaan. Ia tidak termasuk storan sandaran tidak selamat, penyampaian analitis simulasi, atau respons pengesahan ganjaran olok-olok.

// CODE_BLOCK_02: Integrasi Android Wakil (Kotlin)
// Fail: CustomApplication.kt dan MainActivity.kt
//
// Sahkan import dan panggilan balik OpoInstall terhadap AAR yang digunakan.
// Pelaksanaan hos mesti disediakan sebelum pemprosesan parameter.

package com.example.app

import android.app.ActivityManager
import android.app.Application
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Process
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.opoinstall.api.OpoInstall
import com.opoinstall.api.OpoData
import com.opoinstall.api.OpoError
import com.opoinstall.api.ResultCallBack
import org.json.JSONObject
import java.util.UUID

enum class ParamFetchState {
    NOT_STARTED,
    FETCHING,
    RECEIVED,
    APPLIED,
    REJECTED,
    NO_MATCH,
    RETRYABLE_FAILURE
}

data class PendingAttributionRecord(
    val utmSource: String?,
    val utmMedium: String?,
    val utmCampaign: String?,
    val inviterId: String?,
    val promoCode: String?,
    val idempotencyKey: String,
    val createdAt: Long,
    val expiresAt: Long
) {
    val hasAttributionData: Boolean
        get() = listOf(
            utmSource, utmMedium, utmCampaign
        ).any { !it.isNullOrEmpty() }

    val hasRewardData: Boolean
        get() = !inviterId.isNullOrEmpty() ||
                !promoCode.isNullOrEmpty()

    val hasAnyData: Boolean
        get() = hasAttributionData || hasRewardData

    fun isValid(): Boolean {
        val now = System.currentTimeMillis()
        val pattern = Regex("^[A-Za-z0-9_.-]{1,64}$")

        val values = listOf(
            utmSource, utmMedium, utmCampaign,
            inviterId, promoCode
        )

        val validFields = values.all {
            it == null || pattern.matches(it)
        }

        val validWindow =
            expiresAt > createdAt &&
            expiresAt - createdAt <= 48L * 60 * 60 * 1000 &&
            now >= createdAt &&
            now <= expiresAt

        val validId = try {
            UUID.fromString(idempotencyKey)
            true
        } catch (_: IllegalArgumentException) {
            false
        }

        return hasAnyData &&
               validFields &&
               validWindow &&
               validId
    }
}

// Kontrak kegigihan selamat yang ditakrifkan oleh hos.
// Pelaksanaan mesti membezakan data yang hilang daripada
// kegagalan storan dan mengendalikan pengikatan akaun jika perlu.
interface PendingTransactionStore {
    fun save(record: PendingAttributionRecord): Boolean
    fun load(): PendingAttributionRecord?
    fun clear(): Boolean
}

// Penyelaras yang ditakrifkan oleh hos. Pelaksanaannya bertanggungjawab
// untuk pengakuan telemetri/ganjaran bebas,
// idempotensi, pengendalian cuba semula, dan pemeriksaan identiti transaksi.
interface InstallParameterProcessor {
    fun process(
        record: PendingAttributionRecord,
        onComplete: (ProcessingOutcome) -> Unit
    )
}

enum class ProcessingOutcome {
    COMPLETED,
    REJECTED,
    RETRYABLE_FAILURE
}

// Ini adalah kebergantungan aplikasi, BUKAN API SDK OpoInstall.
object HostAppDependencies {
    var transactionStore: PendingTransactionStore? = null
    var parameterProcessor: InstallParameterProcessor? = null

    val isConfigured: Boolean
        get() = transactionStore != null &&
                parameterProcessor != null
}

class CustomApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        if (isMainProcess()) {
            OpoInstall.initialize(this)

            // Aplikasi hos mesti mengkonfigurasi kebergantungan di sini
            // menggunakan mekanisme DI/bootstrap sebenar.
            //
            // HostAppDependencies.transactionStore =
            //     hostProvidedProtectedStore
            //
            // HostAppDependencies.parameterProcessor =
            //     hostProvidedInstallParameterProcessor
        }
    }

    private fun isMainProcess(): Boolean {
        val manager = getSystemService(
            Context.ACTIVITY_SERVICE
        ) as ActivityManager

        val currentPid = Process.myPid()

        return manager.runningAppProcesses?.any {
            it.pid == currentPid &&
            it.processName == packageName
        } == true
    }
}

class MainActivity : AppCompatActivity() {

    private val mainHandler = Handler(Looper.getMainLooper())

    private var state = ParamFetchState.NOT_STARTED
    private var activeRecord: PendingAttributionRecord? = null
    private var processingInFlight = false

    private val transactionTtlMs =
        24L * 60 * 60 * 1000

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startParameterFlow()
    }

    private fun startParameterFlow() {
        val store = HostAppDependencies.transactionStore
        val processor = HostAppDependencies.parameterProcessor

        if (store == null || processor == null) {
            Log.w(
                "OpoInstall",
                "Kebergantungan hos tidak tersedia; pemprosesan digantung"
            )
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        // Pemulihan rekod wakil.
        // Storan pengeluaran mesti melaporkan kegagalan baca secara berasingan
        // daripada ketiadaan rekod tertunda yang disahkan.
        val existingRecord = try {
            store.load()
        } catch (error: Exception) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        if (existingRecord != null) {
            if (existingRecord.isValid()) {
                activeRecord = existingRecord
                state = ParamFetchState.RECEIVED
                processRecord(existingRecord)
            } else {
                // Rekod tidak sah atau tamat tempoh tidak diproses.
                if (!store.clear()) {
                    state = ParamFetchState.RETRYABLE_FAILURE
                    return
                }

                requestInstallParameters()
            }
        } else {
            requestInstallParameters()
        }
    }

    private fun requestInstallParameters() {
        if (!HostAppDependencies.isConfigured) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        if (state == ParamFetchState.FETCHING ||
            state == ParamFetchState.APPLIED ||
            state == ParamFetchState.NO_MATCH ||
            processingInFlight
        ) {
            return
        }

        state = ParamFetchState.FETCHING

        // Panggilan balik SDK wakil.
        // Sahkan tandatangan terhadap AAR yang digunakan.
        OpoInstall.getInstance().getInstallParam(
            object : ResultCallBack<OpoData> {

                override fun onResult(opoData: OpoData?) {
                    mainHandler.post {
                        if (state != ParamFetchState.FETCHING) {
                            return@post
                        }

                        val rawData: Any? = opoData?.data

                        val payload = when (rawData) {
                            is String -> rawData
                            is JSONObject -> rawData.toString()
                            is Map<*, *> -> try {
                                JSONObject(rawData).toString()
                            } catch (_: Exception) {
                                null
                            }
                            else -> null
                        }

                        val record = payload?.let {
                            parseAndSanitizeRawPayload(it)
                        }

                        if (record == null) {
                            // Tiada data layak dipulihkan.
                            // Ini tidak mewujudkan pemasangan organik.
                            state = ParamFetchState.NO_MATCH
                            return@post
                        }

                        val store =
                            HostAppDependencies.transactionStore

                        if (store == null ||
                            !HostAppDependencies.isConfigured
                        ) {
                            state = ParamFetchState.RETRYABLE_FAILURE
                            return@post
                        }

                        activeRecord = record

                        // Jangan tuntut pemulihan tahan lama jika simpanan gagal.
                        val saved = try {
                            store.save(record)
                        } catch (_: Exception) {
                            false
                        }

                        if (!saved) {
                            Log.w(
                                "OpoInstall",
                                "Rekod tertunda tidak dapat dikekalkan"
                            )
                            state = ParamFetchState.RETRYABLE_FAILURE
                            return@post
                        }

                        state = ParamFetchState.RECEIVED
                        processRecord(record)
                    }
                }

                override fun onError(error: OpoError?) {
                    mainHandler.post {
                        if (state == ParamFetchState.FETCHING) {
                            Log.w(
                                "OpoInstall",
                                "Pengambilan parameter pemasangan gagal"
                            )
                            state = ParamFetchState.RETRYABLE_FAILURE
                        }
                    }
                }
            }
        )
    }

    private fun parseAndSanitizeRawPayload(
        rawJson: String
    ): PendingAttributionRecord? {
        return try {
            if (rawJson.length > 4096) return null

            val json = JSONObject(rawJson)
            val pattern = Regex("^[A-Za-z0-9_.-]{1,64}$")

            fun field(key: String): String? =
                (json.opt(key) as? String)?.takeIf {
                    pattern.matches(it)
                }

            val now = System.currentTimeMillis()

            val record = PendingAttributionRecord(
                utmSource = field("utm_source"),
                utmMedium = field("utm_medium"),
                utmCampaign = field("utm_campaign"),
                inviterId = field("inviter_id"),
                promoCode = field("promo_code"),
                idempotencyKey = UUID.randomUUID().toString(),
                createdAt = now,
                expiresAt = now + transactionTtlMs
            )

            record.takeIf { it.isValid() }
        } catch (_: Exception) {
            null
        }
    }

    private fun processRecord(
        record: PendingAttributionRecord
    ) {
        val processor = HostAppDependencies.parameterProcessor
        val store = HostAppDependencies.transactionStore

        if (processor == null || store == null ||
            !record.isValid()
        ) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        if (processingInFlight) return

        processingInFlight = true

        // Pemproses hos tidak boleh melaporkan COMPLETED sehingga
        // semua hasil khusus saluran yang diperlukan disahkan.
        processor.process(record) { outcome ->
            mainHandler.post {
                // Abaikan transaksi yang diganti atau basi.
                if (activeRecord?.idempotencyKey !=
                    record.idempotencyKey
                ) {
                    return@post
                }

                processingInFlight = false

                when (outcome) {
                    ProcessingOutcome.COMPLETED -> {
                        finalizeRecord(
                            record,
                            ParamFetchState.APPLIED
                        )
                    }

                    ProcessingOutcome.REJECTED -> {
                        finalizeRecord(
                            record,
                            ParamFetchState.REJECTED
                        )
                    }

                    ProcessingOutcome.RETRYABLE_FAILURE -> {
                        state =
                            ParamFetchState.RETRYABLE_FAILURE
                    }
                }
            }
        }
    }

    private fun finalizeRecord(
        record: PendingAttributionRecord,
        terminalState: ParamFetchState
    ) {
        val store = HostAppDependencies.transactionStore
            ?: run {
                state = ParamFetchState.RETRYABLE_FAILURE
                return
            }

        if (activeRecord?.idempotencyKey !=
            record.idempotencyKey
        ) {
            return
        }

        // Contoh ini secara sengaja membiarkan penjurnalan terminal tahan lama
        // dan pembersihan atomik kepada stor hos.
        // clear() mesti melaporkan sama ada pembersihan berjaya.
        val cleared = try {
            store.clear()
        } catch (_: Exception) {
            false
        }

        if (!cleared) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        activeRecord = null
        state = terminalState

        Log.i(
            "OpoInstall",
            "Pemprosesan parameter pemasangan selesai"
        )
    }
}

Sampel Android secara sengaja tidak menyertakan pelaksanaan lalai PendingTransactionStore atau InstallParameterProcessor.

Aplikasi hos mesti membekalkannya sebelum memanggil aliran kerja.

Khususnya, pemproses tidak boleh mengembalikan COMPLETED hanya kerana penyataan pembalakan dijalankan atau permintaan tidak segerak dimulakan. Hasil yang lengkap mesti sepadan dengan keperluan pemprosesan untuk beban data sebenar:

  • Beban data UTM-sahaja: Peristiwa atribusi yang diperlukan diterima dengan pasti oleh sistem analitis yang dikonfigurasikan atau baris gilir peristiwa tahan lama.
  • Beban data ganjaran-sahaja: Bahagian belakang yang disahkan mengembalikan hasil pengesahan terminal yang diterima.
  • Beban data bercampur UTM dan Ganjaran: Kedua-dua saluran yang diperlukan mencapai hasil yang ditakrifkan secara bebas, dan satu saluran tidak boleh membuang kerja yang diperlukan oleh saluran lain secara pramatang.

Jika aplikasi hos memerlukan pemulihan merentas proses, pemproses dan stor terlindung mesti tambahan pula mengekalkan kemajuan khusus saluran yang mencukupi dan mengekalkan pengecam transaksi yang stabil.

Aktiviti di atas menunjukkan sempadan integrasi; ia bukan pengganti untuk pelaksanaan khusus aplikasi tersebut.

Pelaksanaan Natif iOS: Pemulihan SDK dan Kontrak Integrasi Aplikasi

Integrasi iOS mengikuti sempadan yang sama.

SDK natif membekalkan parameter pemasangan calon. Aplikasi mengesahkan beban data, mengekalkan rekod tertunda melalui komponen storan yang disediakan oleh hos, dan mewakilkan pemprosesan perniagaan kepada penyelaras yang dimiliki oleh aplikasi.

Kod ini adalah wakil dan mesti disesuaikan dengan nama modul, protokol delegasi, dan tandatangan panggilan balik bagi keluaran SDK iOS OpoInstall yang dipasang.

// CODE_BLOCK_03: Integrasi iOS Wakil (Swift)
// Fail: AppDelegate.swift
//
// Sahkan modul dan kaedah SDK terhadap versi rangka kerja
// OpoInstall yang digunakan.

import UIKit
import libOpoInstallSDK

enum ParamFetchState: String {
    case notStarted = "NOT_STARTED"
    case fetching = "FETCHING"
    case received = "RECEIVED"
    case applied = "APPLIED"
    case rejected = "REJECTED"
    case noMatch = "NO_MATCH"
    case retryableFailure = "RETRYABLE_FAILURE"
}

struct PendingAttributionRecord: Codable {
    let utmSource: String?
    let utmMedium: String?
    let utmCampaign: String?
    let inviterId: String?
    let promoCode: String?
    let idempotencyKey: String
    let createdAt: TimeInterval
    let expiresAt: TimeInterval

    var hasAttributionData: Bool {
        return [
            utmSource, utmMedium, utmCampaign
        ].contains { !$0.isNilOrEmpty }
    }

    var hasRewardData: Bool {
        return !inviterId.isNilOrEmpty ||
               !promoCode.isNilOrEmpty
    }

    var hasAnyData: Bool {
        return hasAttributionData || hasRewardData
    }

    var isValid: Bool {
        let now = Date().timeIntervalSince1970
        let window = expiresAt - createdAt

        let pattern = "^[A-Za-z0-9_.-]{1,64}$"

        let fields = [
            utmSource, utmMedium, utmCampaign,
            inviterId, promoCode
        ]

        let validFields = fields.allSatisfy { field in
            guard let field = field else { return true }
            return field.range(
                of: pattern,
                options: .regularExpression
            ) != nil
        }

        return hasAnyData &&
               validFields &&
               UUID(uuidString: idempotencyKey) != nil &&
               window > 0 &&
               window <= 48 * 60 * 60 &&
               now >= createdAt &&
               now <= expiresAt
    }
}

private extension Optional where Wrapped == String {
    var isNilOrEmpty: Bool {
        return self?.isEmpty ?? true
    }
}

// Kontrak kegigihan terlindung yang disediakan oleh hos.
protocol PendingTransactionStore {
    func save(_ record: PendingAttributionRecord) -> Bool
    func load() throws -> PendingAttributionRecord?
    func clear() -> Bool
}

enum ProcessingOutcome {
    case completed
    case rejected
    case retryableFailure
}

// Penyelaras yang disediakan oleh hos untuk analitis dan pemprosesan ganjaran.
protocol InstallParameterProcessor {
    func process(
        record: PendingAttributionRecord,
        completion: @escaping (ProcessingOutcome) -> Void
    )
}

// Ini adalah kontrak aplikasi, bukan API OpoInstall.
struct HostAppDependencies {
    static var transactionStore: PendingTransactionStore?
    static var parameterProcessor: InstallParameterProcessor?

    static var isConfigured: Bool {
        return transactionStore != nil &&
               parameterProcessor != nil
    }
}

@UIApplicationMain
class AppDelegate:
    UIResponder,
    UIApplicationDelegate,
    OpoInstallDelegate {

    var window: UIWindow?

    private var state: ParamFetchState = .notStarted
    private var activeRecord: PendingAttributionRecord?
    private var processingInFlight = false

    private let transactionTTL: TimeInterval =
        24 * 60 * 60

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions:
            [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        // Permulaan SDK wakil.
        OpoInstallSDK.initWith(self)

        // Aplikasi hos mesti mendaftarkan kebergantungan sebenar
        // sebelum memulakan pemprosesan parameter.
        //
        // HostAppDependencies.transactionStore =
        //     hostProvidedProtectedStore
        //
        // HostAppDependencies.parameterProcessor =
        //     hostProvidedInstallParameterProcessor

        startParameterFlow()
        return true
    }

    private func startParameterFlow() {
        guard HostAppDependencies.isConfigured,
              let store = HostAppDependencies.transactionStore
        else {
            print(
                "OpoInstall: Kebergantungan hos yang diperlukan tidak tersedia"
            )
            state = .retryableFailure
            return
        }

        do {
            if let saved = try store.load() {
                if saved.isValid {
                    activeRecord = saved
                    state = .received
                    processRecord(saved)
                    return
                }

                guard store.clear() else {
                    state = .retryableFailure
                    return
                }
            }
        } catch {
            // Ralat baca tidak bersamaan dengan stor kosong.
            state = .retryableFailure
            return
        }

        requestInstallParameters()
    }

    private func requestInstallParameters() {
        guard HostAppDependencies.isConfigured,
              state != .fetching,
              state != .applied,
              state != .noMatch,
              !processingInFlight
        else {
            return
        }

        guard let manager = OpoInstallSDK.defaultManager()
        else {
            state = .retryableFailure
            return
        }

        state = .fetching

        // Tandatangan panggilan balik wakil.
        manager.getInstallParmsCompleted({
            [weak self] (appData: OpoinstallData?) in

            DispatchQueue.main.async {
                guard let self = self,
                      self.state == .fetching
                else {
                    return
                }

                let rawData: Any? = appData?.data

                guard let record =
                    self.parseAndSanitizeRawPayload(rawData)
                else {
                    // Tiada parameter tersuai yang layak dipulihkan.
                    // Ini bukan bukti muktamad trafik organik.
                    self.state = .noMatch
                    return
                }

                guard HostAppDependencies.isConfigured,
                      let store =
                          HostAppDependencies.transactionStore
                else {
                    self.state = .retryableFailure
                    return
                }

                self.activeRecord = record

                guard store.save(record) else {
                    // Jangan tuntut rekod tertunda yang tahan lama
                    // apabila kegigihan gagal.
                    self.state = .retryableFailure
                    return
                }

                self.state = .received
                self.processRecord(record)
            }
        })
    }

    private func parseAndSanitizeRawPayload(
        _ rawData: Any?
    ) -> PendingAttributionRecord? {

        guard let rawData = rawData else {
            return nil
        }

        var dictionary: [String: Any]?

        if let map = rawData as? [String: Any] {
            dictionary = map
        } else if let jsonString = rawData as? String,
                  jsonString.count <= 4096,
                  let data = jsonString.data(using: .utf8),
                  let parsed = try? JSONSerialization.jsonObject(
                      with: data
                  ) as? [String: Any] {
            dictionary = parsed
        }

        guard let payload = dictionary else {
            return nil
        }

        let pattern = "^[A-Za-z0-9_.-]{1,64}$"

        func field(_ key: String) -> String? {
            guard let value = payload[key] as? String,
                  value.range(
                    of: pattern,
                    options: .regularExpression
                  ) != nil
            else {
                return nil
            }

            return value
        }

        let now = Date().timeIntervalSince1970

        let record = PendingAttributionRecord(
            utmSource: field("utm_source"),
            utmMedium: field("utm_medium"),
            utmCampaign: field("utm_campaign"),
            inviterId: field("inviter_id"),
            promoCode: field("promo_code"),
            idempotencyKey: UUID().uuidString,
            createdAt: now,
            expiresAt: now + transactionTTL
        )

        return record.isValid ? record : nil
    }

    private func processRecord(
        _ record: PendingAttributionRecord
    ) {
        guard record.isValid,
              let processor =
                  HostAppDependencies.parameterProcessor
        else {
            state = .retryableFailure
            return
        }

        guard !processingInFlight else {
            return
        }

        processingInFlight = true

        // Pemproses hos mesti mengesahkan atribusi yang diperlukan
        // dan hasil ganjaran secara bebas.
        processor.process(record: record) {
            [weak self] outcome in

            DispatchQueue.main.async {
                guard let self = self,
                      self.activeRecord?.idempotencyKey ==
                          record.idempotencyKey
                else {
                    return
                }

                self.processingInFlight = false

                switch outcome {
                case .completed:
                    self.finalizeRecord(
                        record,
                        terminalState: .applied
                    )

                case .rejected:
                    self.finalizeRecord(
                        record,
                        terminalState: .rejected
                    )

                case .retryableFailure:
                    self.state = .retryableFailure
                }
            }
        }
    }

    private func finalizeRecord(
        _ record: PendingAttributionRecord,
        terminalState: ParamFetchState
    ) {
        guard activeRecord?.idempotencyKey ==
                  record.idempotencyKey,
              let store =
                  HostAppDependencies.transactionStore
        else {
            state = .retryableFailure
            return
        }

        // Stor hos mesti menyelaraskan penyelesaian tahan lama
        // dan pembersihan untuk keperluan kebolehpercayaan aplikasi.
        guard store.clear() else {
            state = .retryableFailure
            return
        }

        activeRecord = nil
        state = terminalState

        print(
            "OpoInstall: Pemprosesan parameter pemasangan selesai"
        )
    }
}

Contoh iOS tidak membekalkan stor transaksi tertunda yang disokong UserDefaults atau klien telemetri yang melaporkan kejayaan secara automatik.

Aplikasi hos mesti membekalkan pelaksanaan storan dan pemproses parameter yang sesuai dengan keperluan keselamatan, analitis, dan pengurusan akaunnya.

Di mana aplikasi memproses nilai yang membawa ganjaran, pemproses tidak boleh mengembalikan .completed sebelum pengesahan bahagian belakang selesai.

Di mana beban data hanya menyertakan data pemasaran, penyelesaian mesti mencerminkan penerimaan analitis yang ditakrifkan oleh aplikasi atau keperluan baris gilir yang boleh dipercayai.

Untuk rekod bercampur, pemproses mesti mengekalkan kerja yang belum selesai bagi setiap saluran dan memastikan panggilan balik basi tidak boleh memuktamadkan transaksi yang berbeza.

Kod wakil secara sengaja meninggalkan penjadual cuba semula yang lengkap dan jurnal transaksi berbilang saluran yang tahan lama. Kebimbangan itu milik pelaksanaan aplikasi hos dan mesti diuji terhadap kitar hayat dan bahagian belakangnya yang sebenar.

Sempadan Integrasi Pengeluaran dan Senarai Semak Pengesahan

Sebelum menggunakan sama ada contoh mudah alih, pasukan kejuruteraan harus mengesahkan keadaan berikut.

Sempadan Integrasi Pengesahan Pengeluaran yang Diperlukan
SDK Web Sahkan sumber CDN, konfigurasi pembina, kaedah muat turun, dan tingkah laku kegagalan tidak segerak untuk keluaran yang digunakan.
SDK Natif Sahkan nama pakej/modul, keperluan permulaan, tandatangan panggilan balik, dan jenis beban data terhadap AAR Android atau rangka kerja iOS yang tepat.
Skema Input Sahkan nama parameter yang disokong, panjang nilai, peraturan aksara, dan pengendalian kunci yang salah bentuk atau pendua.
Storan Terlindung Gunakan pelaksanaan storan yang disediakan oleh aplikasi dengan perlindungan, tamat tempoh, dan pelaporan ralat yang sesuai.
Telemetri Pemasaran Sahkan bahawa penyelesaian yang berjaya bermakna peristiwa analitis diterima dengan pasti atau dibarisi secara tahan lama, dan bukannya sekadar dilogkan.
Pengesahan Ganjaran Gunakan pengesahan bahagian belakang aplikasi yang disahkan; jangan sesekali memberikan ganjaran berdasarkan parameter yang disediakan oleh pelanggan semata-mata.
Beban Data Bercampur Kekalkan atribusi tertunda dan kerja ganjaran secara bebas sehingga setiap hasil yang diperlukan diselesaikan.
Idempotensi Kekalkan identiti transaksi yang stabil merentas cuba semula dan kuatkan keunikan ganjaran pada pelayan.
Binaan Keluaran Sahkan binaan Android yang dimini (minified) dan binaan iOS dengan artifak SDK sebenar.
Semakan Privasi Sahkan pengumpulan data sebenar SDK dan tingkah laku pemadanan terhadap keperluan platform dan kawal selia.

Ini adalah kriteria penerimaan integrasi, bukan dakwaan bahawa kod rujukan atau keluaran SDK tertentu telah pun melepasi ujian pengeluaran.

Matriks Perbandingan Strategi Penghantaran Parameter

Analisis Metodologi Strategi Pemulihan Parameter

Sistem pemerolehan mudah alih menggunakan pendekatan berbeza untuk mengekalkan konteks rujukan dan atribusi antara lawatan web dan aplikasi natif.

Pendekatan yang sesuai bergantung pada platform sasaran, jenis parameter yang diperlukan, geseran pengguna yang boleh diterima, sensitiviti data, dan mekanisme platform yang tersedia.

Strategi Parameter Liputan Pemulihan Geseran Pengguna Pertimbangan Privasi / Polisi Pengesahan Bahagian Belakang & Idempotensi Diperlukan
Kemasukan Kod Promo Manual Bergantung pada penyertaan pengguna dan ketersediaan kod. Tinggi; pengguna memasukkan kod secara manual. Tidak memerlukan pemadanan peranti untuk kemasukan kod, tetapi peraturan perlindungan data biasa masih terpakai. Ya, untuk penebusan ganjaran.
Google Play Install Referrer Pemasangan Google Play pada aliran Android yang disokong. Minimum apabila dilaksanakan dengan berjaya. Pengangkutan yang disokong stor rasmi; pemprosesan hiliran dan penggunaan data masih memerlukan semakan. Ya, apabila nilai perujuk boleh mencetuskan ganjaran.
Pemulihan Parameter OpoInstall Bergantung pada tingkah laku SDK yang digunakan, laluan masuk, platform, dan mekanisme pemadanan yang dibenarkan. Biasanya minimum apabila pemulihan berjaya. Memerlukan penilaian amalan data SDK, kebenaran, dan peraturan platform. Ya, untuk parameter yang membawa ganjaran.

Menilai Ketepatan, Pematuhan Privasi, Geseran Pengguna, dan Sempadan Keselamatan

Kemasukan kod promo manual adalah mudah untuk ditaakul secara teknikal tetapi boleh memperkenalkan geseran pengguna.

Google Play Install Referrer menawarkan saluran rujukan Android natif yang disokong stor.

SDK pemulihan parameter pihak ketiga boleh menyediakan lapisan integrasi peringkat aplikasi untuk konteks tertunda yang layak. Walau bagaimanapun, liputan penghantaran sebenar, mekanisme teknikal, dan ciri privasinya mesti dinilai menggunakan keluaran yang digunakan.

Tanpa mengira kaedah pemulihan, aplikasi mesti membezakan antara menerima parameter dan mengesahkan tindakan perniagaan.

Penyelesaian Masalah Pemulihan Parameter, Pengurusan Negeri, dan Ralat Integrasi SDK

Mengurus Peralihan Negeri Pihak Pelanggan: Mengendalikan Tanpa Padanan, Cuba Semula, dan Hasil Terminal

Beban data atribusi dan ganjaran bercampur memerlukan negeri penyelesaian bebas sebelum pembersihan.

Aliran kerja pemulihan parameter boleh diterangkan menggunakan tujuh negeri logik:

Negeri Makna
NOT_STARTED Tiada percubaan mendapatkan semula telah dimulakan.
FETCHING Aplikasi sedang meminta parameter pemasangan.
RECEIVED Beban data calon telah diterima dan diterima untuk pemprosesan.
APPLIED Pemproses hos melaporkan bahawa semua hasil berjaya yang diperlukan untuk rekod ini selesai.
REJECTED Pemproses hos melaporkan penolakan terminal yang ditakrifkan.
NO_MATCH Percubaan mendapatkan semula tidak menghasilkan parameter tersuai yang layak.
RETRYABLE_FAILURE Aliran kerja tidak dapat diteruskan pada masa ini dan boleh dicuba semula di bawah polisi terkawal.

Negeri ini menerangkan hasil peringkat aplikasi. Ia tidak menggantikan penyelarasan tahan lama aplikasi hos untuk analitis, ganjaran, storan, dan cuba semula.

Khususnya, rekod pemasaran-sahaja boleh selesai tanpa pengesahan ganjaran, manakala rekod yang membawa ganjaran mesti mendapatkan respons pelayan yang sesuai.

Hasil NO_MATCH tidak boleh ditafsirkan sebagai bukti muktamad bahawa pemasangan adalah organik.

Menyelesaikan Tamat Masa Permintaan Parameter dan Kelewatan Rangkaian dengan Lancar

Pemulihan parameter boleh terjejas oleh ketersambungan, masa panggilan balik, susunan permulaan aplikasi, atau perkhidmatan bahagian belakang yang tidak tersedia.

Aplikasi harus:

  • Pastikan kerja rangkaian tidak segerak.
  • Elakkan menyekat antara muka pengguna utama semasa mendapatkan semula parameter pemasangan.
  • Bezakan pengurus SDK yang tidak tersedia daripada hasil parameter kosong.
  • Anggap ralat kegigihan tempatan sebagai kegagalan pemprosesan, bukan sebagai pengesahan bahawa tiada rekod wujud.
  • Cuba semula mengikut polisi aplikasi terhad dan bukannya berulang kali mengeluarkan permintaan pada setiap peralihan latar depan.
  • Kekalkan rekod tertunda sedia ada apabila operasi perlu dicuba semula.

Sebarang pelaksanaan yang mencuba semula secara automatik harus menghalang permintaan bertindih dan menolak panggilan balik yang tergolong dalam transaksi yang diganti.

Mengurus Pemasangan Tindih Aplikasi dan Penimbal Parameter Basi

Pemulihan parameter biasanya menyasarkan pelancaran pertama selepas pemasangan yang layak, tetapi naik taraf aplikasi dan pelancaran berulang boleh mendedahkan keadaan tempatan yang disimpan sebelum ini.

Pasukan kejuruteraan harus mentakrifkan bila percubaan pemulihan layak, cara rekod yang telah digunakan dikenal pasti, dan cara data tertunda tamat tempoh.

Bagi parameter yang membawa ganjaran, idempotensi bahagian belakang adalah penting. Bendera peringkat pelanggan sahaja bukanlah perlindungan yang mencukupi terhadap ganjaran pendua.

Apabila aplikasi mengekalkan transaksi tertunda secara tempatan, ia harus mengeluarkan rekod hanya selepas hasil pemprosesan yang diperlukan telah diselesaikan secara tahan lama.

Menghalang Isu Permulaan Semula Berbilang Proses Android

Aplikasi Android dengan berbilang proses mungkin melaksanakan Application.onCreate() lebih daripada sekali.

Jika SDK natif bertujuan untuk dimulakan hanya dalam proses aplikasi utama, pembangun harus mengesahkan proses semasa sebelum memulakannya.

Sampel Android menunjukkan satu strategi perbandingan nama proses. Aplikasi pengeluaran harus menyemak pendekatan terhadap versi Android dan seni bina proses yang disokong.

Menyahpepijat Ketidakpadanan Tandatangan Panggilan Balik Pelanggan dan Peraturan Pengeliruan Pengecil Kod

Nama pakej, antara muka panggilan balik, dan pengisytiharan kelas SDK pihak ketiga boleh berbeza merentas keluaran.

Apabila integrasi gagal, pembangun harus mengesahkan SDK yang digunakan dan bukannya mengandaikan bahawa antara muka contoh kekal tidak berubah.

Bagi binaan keluaran Android:

  • Utamakan peraturan R8 / ProGuard pengguna yang dibekalkan dengan artifak SDK yang tepat.
  • Elakkan peraturan -keep atau -dontwarn peringkat pakej yang luas tanpa bukti.
  • Periksa keperluan pantulan (reflection) sebenar sebelum menambah peraturan tambahan yang sempit.
  • Uji panggilan balik parameter pemasangan dalam konfigurasi keluaran yang dimini.

Kod dalam panduan ini adalah integrasi wakil dan tidak harus diterangkan sebagai disahkan keluaran tanpa bukti binaan dan masa jalanan yang sepadan.

Soalan Lazim (FAQ)

Bagaimanakah parameter penjejakan kekal apabila pengguna memasang aplikasi melalui App Store?
Parameter penjejakan kekal melalui saluran paip pemulihan parameter tertunda. Apabila pengguna berinteraksi dengan halaman pendaratan web, SDK JS Web OpoInstall membungkus parameter pertanyaan dan menghantarnya ke bahagian belakang atribusi. Apabila pengguna melancarkan aplikasi yang baru dipasang buat kali pertama, SDK OpoInstall natif meminta API bahagian belakang atau Play Store, memadankan sesi dan menyampaikan parameter pertanyaan tersuai kepada aplikasi.
Apakah sekatan privasi yang terpakai pada beban data papan klip pada iOS dan Android?
Pada iOS 14 dan seterusnya, mengakses papan klip umum merentas aplikasi boleh mencetuskan sepanduk pemberitahuan sistem, dan iOS 16 memperkenalkan gesaan kebenaran tampal untuk bacaan programatik melainkan dimulakan melalui kawalan seperti `UIPasteControl`. Pada Android 10 dan seterusnya, akses papan klip dihadkan kepada aplikasi latar depan, dan Android 13 menyediakan pengesahan UI sistem apabila kandungan disalin. Oleh itu, beban data papan klip mesti dianggap sebagai isyarat pengangkutan sensitif privasi yang pilihan dan bukannya saluran utama yang tidak terhad.
Bolehkah parameter penjejakan membawa beban data JSON bersarang untuk penghalaan dalam apl dinamik?
Walaupun protokol rangkaian asas boleh mengangkut beban data JSON bertali, seni bina keselamatan pengeluaran—termasuk pelaksanaan rujukan dalam panduan ini—menguatkuasakan skema skalar yang dibenarkan (contohnya, rentetan alfanumerik dan token kempen sehingga 64 aksara) untuk mengurangkan risiko suntikan skrip dan risiko sempadan memori. Jika penghalaan dalam apl yang kompleks diperlukan, aplikasi harus menghantar pengecam laluan legap dan menyelesaikan laluan navigasi yang kaya dengan selamat daripada konfigurasi bahagian belakang yang dipercayai dan bukannya menghuraikan struktur JSON bersarang yang tidak disahkan secara terus pada klien.

Ringkasan dan Rangka Kerja Keputusan

Menyambung klik web ke pemasangan stor aplikasi mudah alih memerlukan pendekatan pemulihan parameter yang menghormati sempadan teknikal antara pelayar, stor aplikasi, dan aplikasi natif.

Untuk Android, API Google Play Install Referrer menawarkan mekanisme rujukan rasmi untuk aliran pemasangan yang disokong. Untuk iOS, parameter web sewenang-wenangnya tidak dihantar secara automatik melalui App Store, jadi pemulihan tertunda memerlukan pendekatan yang disahkan secara bebas dan sedar privasi.

Integrasi OpoInstall yang praktikal boleh menangkap parameter halaman pendaratan yang dibenarkan (allowlisted) dan meminta konteks yang layak melalui SDK natif. Aplikasi itu kemudiannya mesti menormalkan dan mengesahkan data yang dipulihkan sebelum menggunakannya untuk atribusi pemasaran, navigasi, atau aliran kerja promosi.

Perbezaan kejuruteraan utama adalah antara pemulihan parameter dan pemprosesan peringkat aplikasi. Memulihkan parameter tidak menjamin penghantaran analitis, kelayakan ganjaran, atau pengesahan yang berjaya.

Kod rujukan menunjukkan sempadan integrasi SDK dan kontrak yang ditakrifkan oleh aplikasi. Pasukan pengeluaran mesti menyediakan storan terlindung mereka sendiri, pengendalian analitis yang boleh dipercayai, pengesahan hak bahagian belakang, dan penyelarasan kitar hayat.

Untuk meneroka keperluan integrasi vendor, rujuk dokumentasi SDK OpoInstall atau halaman pendaftaran pembangun OpoInstall.

Bahan Berkaitan

Konsep: Pemasangan Parameter Aplikasi, Google Play Install Referrer, Pemulihan Parameter Tertunda, Telemetri Atribusi, Sempadan Pengesahan Bahagian Belakang.

Teknologi: SDK JS Web, SDK Mudah Alih Natif, Parameter Pertanyaan URL, Android Keystore, iOS Keychain Services.

Standard: Sintaks URI IETF RFC 3986, Standard Pengesahan Keselamatan Aplikasi Mudah Alih OWASP, API dan Peristiwa Papan Klip W3C.

API: API Parameter Pemasangan Natif OpoInstall, API Google Play Install Referrer, API UIPasteboard Apple, API Papan Klip Android.

Dokumentasi & Rujukan Rasmi:

Share this article