Кратко: атрибуция устройств Android без GAID с использованием OAID или IMEI. Практические рекомендации по просмотру данных атрибуции в AppsFlyer.
Атрибуция приложений Android на внутреннем рынке Китая
В Китае владельцы приложений сталкиваются с рядом проблем:
- Сервисы Google Play (GPS) не активны на большинстве устройств Android, а значит, на них нет GAID.
- Использование сторонних магазинов приложений Android создает возможности для перехвата магазинов производителями устройств.
- Серверы атрибуции недоступны или отвечают медленно.
- Управление данными атрибуции из Google Play и сторонних магазинов приложений Android и их просмотр.
Используя AppsFlyer, эти проблемы можно решить следующим образом:
- Реализация атрибуции на основе OAID
- Использование IMEI устройства в качестве альтернативы GAID на устройствах с Android версии 28 или более ранней
- Подготовка APK с уникальными идентификаторами для обнаружения перехвата магазинов Android
- Настройка ссылок атрибуции, распознаваемых в Китае
Подготовка приложения
Интегрируйте SDK в приложение и подготовьте APK одним из способов в зависимости от версии Android на целевых устройствах.
Интегрируйте SDK в приложение
Как правило, сервисы Google Play не устанавливаются на устройства Android, поставляемые в Китае. Это означает, что нет GAID, который можно использовать как уникальный идентификатор атрибуции.
Альтернативой использованию GAID могут быть IMEI и/или OAID, если они доступны, для обеспечения записи атрибуции.
OAID:
- Это рекламный идентификатор, который пользователь может сбросить, аналогичный GAID и IDFA.
- Требуется AppsFlyer Android SDK версии 5.1.0 или более поздней.
- Начиная с AppsFlyer SDK V5.4.0, SDK по умолчанию будет автоматически пытаться собирать OAID.
- Несколько производителей завершили тестирование совместимости атрибуции OAID с AppsFlyer. В том числе Huawei, Lenovo, OPRO, Vivo, Samsung и Xiaomi,
IMEI:
- SDK AppsFlyer блокирует доступ к IMEI, если не настроено иначе. Обычно предполагается, что на устройстве активны сервисы Google Play и IMEI не требуется. В Китае это не так.
- При интеграции SDK AppsFlyer в приложение необходимо включить доступ к IMEI.
- При необходимости OAID и IMEI следует реализовать одновременно, чтобы обеспечить атрибуцию независимо от версии Android.
- Это связано с тем, что доступ к IMEI ограничен начиная с Android 10 (API level 29), выпущенного в конце 2019 года. Ни ваше приложение, ни SDK AppsFlyer не могут собирать эти идентификаторы. Если идентификатор недоступен, AppsFlyer использует атрибуцию на основе вероятностного моделирования.
Прежде чем начать:
- Требуется AppsFlyer Android SDK версии 5.1.0 или более поздней.
Используйте одну из следующих процедур:
OAID начиная с Android API уровня 29
IMEI Android API уровня 23–28
Есть два метода сбора IMEI:
- setImeiData
- setCollectIMEI(true)
Используйте следующий пример кода для интеграции SDK. В этом примере кода используется метод onConversionDataSuccess. Это название метода для получения данных конверсии, начиная с SDK V5. Если вы используете версию SDK ниже V5, название метода — onInstallConversionDataLoaded. Мы рекомендуем обновить SDK до текущей версии.
Примечание: Для доступа к IMEI на Android с уровнем API 23 и выше требуется разрешение пользователя, запрашиваемое через диалоговое окно. В результате этого IMEI может быть получен после активации API StartTracking. Начиная с уровня API 29 доступ к IMEI ограничен.
Public class AFApplication extends Application {
private static final String AF_DEV_KEY = "";
private static AFApplication instance;
@Override
public void onCreate() {
//If you want to show debug log.
AppsFlyerLib.getInstance().setDebugLog(true); //Разработчики собирают IMEI и идентификатор Android, а затем отправляют их в SDK AppsFlyer.
AppsFlyerLib.getInstance().setImeiData("customer imei"); \n\t AppsFlyerLib.getInstance().setAndroidIdData("customer android_id");\n //Или используйте два API ниже, чтобы SDK AppsFlyer собирал IMEI & android id //независимо от того, установлены службы Google Play или нет.
AppsFlyerLib.getInstance().setCollectIMEI(true);\n AppsFlyerLib.getInstance().setCollectAndroidID(true);\n //Настройте минимальное время между двумя сессиями; рекомендуется 2 секунды。\n AppsFlyerLib.getInstance().setMinTimeBetweenSessions(2);\n final AppsFlyerConversionListener conversionDataListener = new \nAppsFlyerConversionListener() {\n @Override\n public void onConversionDataSuccess(Map<String, Object=""> map) {}\n @Override\n public void onConversionDataFail(String error) {}\n @Override\n public void onAppOpenAttribution(Map<string, string=""> map) {}\n @Override\n public void onAttributionFailure(String s) {}\n }\n AppsFlyerLib.getInstance().init(AF_DEV_KEY, conversionDataListener);\n AppsFlyerLib.getInstance().startTracking(context, AF_DEV_KEY);\n } \n @Override\n protected void attachBaseContext(Context base) {\n instance = this;\n super.attachBaseContext(base);\n }\n public static synchronized AFApplication getAppInstance() {\n return instance;\n }\n}
При первом запуске приложения пользователю отправляется запрос разрешения READ_PHONE_STATE в обратном вызове onResume() в MainActivity.
build.gradle в модуле app
allprojects {
repositories {
....
maven { url 'https://jitpack.io'}
}
}
dependencies {
implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
}
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startIMEILogic();
}
private void startIMEILogic() {
SharedPreferences permissions_flags = getSharedPreferences("appsflyer-data", MODE_PRIVATE);
if (!permissions_flags.contains(Manifest.permission.READ_PHONE_STATE)) {
requestIMEIPermissions();
}
}
private void requestIMEIPermissions() {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
} else {
//Permission was already granted
SharedPreferences.Editor permissionsFlagsEditor = getSharedPreferences("appsflyer-data", MODE_PRIVATE).edit();
permissionsFlagsEditor.putBoolean(Manifest.permission.READ_PHONE_STATE, true);
permissionsFlagsEditor.apply();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
SharedPreferences.Editor permissionsFlagsEditor = getSharedPreferences("appsflyer-data", MODE_PRIVATE).edit();
boolean granted;
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// разрешение предоставлено!
granted = true;
AppsFlyerLib.getInstance().setCollectIMEI(true);
} else {
// разрешение отклонено!
granted = false;
}
AppsFlyerLib.getInstance().setMinTimeBetweenSessions(0);
AppsFlyerLib.getInstance().reportTrackSession(this);
AppsFlyerLib.getInstance().setMinTimeBetweenSessions(5);
permissionsFlagsEditor.putBoolean(Manifest.permission.READ_PHONE_STATE, granted);
permissionsFlagsEditor.apply();
}
}
}
public class MainActivity extends AppCompactActivity {
@Override
protected void onResume() {
super.onResume();
final RxPermissions rxPermissions = new RxPermissions(this);
if(RxPreference.Instance().getBoolean(RxPreference.KEY_PERMISSION_DIALOG_HAS_SHOW, false){
return;
}
if(rxPermissions.isGranted(Manifest.permission.READ_PHONE_STATE)) {
return;
}
RxPreference.Instance().putBoolean(RxPreference.KEY_PERMISSION_DIALOG_HAS_SHOW, true);
rxPermissions.request(Manifest.permission.READ_PHONE_STATE)
.subscribe(granted -> {
if (granted) {
AppsFlyerLib.getInstance().setImeiData("customer imei");
AppsFlyerLib.getInstance().setAndroidIdData("customer android_id");
//or
AppsFlyerLib.getInstance().setCollectIMEI(true);
AppsFlyerLib.getInstance().setCollectAndroidID(true);
//NOTE: Here the report session API is reportTrackSession() not the startTracking()
AppsFlyerLib.getInstance().reportTrackSession(AFApplication.getAppInstance());
} else {
}
});
}
}
Существует два метода сбора IMEI:
- setImeiData
- setCollectIMEI(true)
Используйте следующий пример кода для интеграции SDK. Примечание: В примере кода для получения данных конверсии используется метод onConversionDataSuccess. Это название метода, начиная с SDK V5. Если вы используете версию SDK ниже 5.0.0, название метода — onInstallConversionDataLoaded. Мы рекомендуем обновить SDK до версии 5.0.0. Подробнее см. здесь.
public class AFApplication extends Application {
private static final String AF_DEV_KEY = "";
private static AFApplication instance;
@Override
public void onCreate() {
//If you want to show debug log.
AppsFlyerLib.getInstance().setDebugLog(true);
//Разработчик собирает IMEI и идентификатор Android, а затем отправляет их в SDK AppsFlyer.
AppsFlyerLib.getInstance().setImeiData("customer imei");
AppsFlyerLib.getInstance().setAndroidIdData("customer android_id");
//Или используйте два API ниже, чтобы SDK AppsFlyer собирал IMEI & android id независимо от того,
//установлен сервис Google Play или нет.
AppsFlyerLib.getInstance().setCollectIMEI(true);
AppsFlyerLib.getInstance().setCollectAndroidID(true);
final AppsFlyerConversionListener conversionDataListener = new AppsFlyerConversionListener() {
@Override
public void onConversionDataSuccess(Map<String, Object=""> map) {}
@Override
public void onConversionDataFail(String error) {}
@Override
public void onAppOpenAttribution(Map<string, string=""> map) {}
@Override
public void onAttributionFailure(String s) {}
};
AppsFlyerLib.getInstance().init(AF_DEV_KEY, conversionDataListener);
AppsFlyerLib.getInstance().startTracking(context, AF_DEV_KEY);
}
@Override
protected void attachBaseContext(Context base) {
instance = this;
super.attachBaseContext(base);
}
public static synchronized AFApplication getAppInstance() {
return instance;
}
}
Подготовка APK
Выберите, как в AppsFlyer будут отображаться данные атрибуции:
- (лучшие практики) Одно приложение: данные атрибуции из всех сторонних магазинов приложений Android (внутренний рынок Китая) отображаются в одном приложении, но отдельно. Это означает, что в AppsFlyer вы видите данные атрибуции из всех китайских магазинов внутреннего рынка в одном приложении. Чтобы использовать этот метод, перейдите к разделу Добавление приложения в AppsFlyer, который приведен ниже.
- Одно приложение, объединенное с магазином Google Play: атрибуция как из сторонних магазинов приложений, так и из магазина Google Play отображается как единая сущность. Для этого варианта требуется, чтобы имя пакета в магазине Google Play и в сторонних магазинах было одинаковым. При использовании этого варианта предполагается, что приложение уже определено в AppsFlyer. Чтобы использовать этот метод, перейдите к разделу Подготовка APK/manifest, который приведен ниже.
- Несколько приложений означает, что данные атрибуции для каждого магазина отображаются в отдельном приложении. Например, отдельно отображаются Google Play Store, сторонний магазин A, сторонний магазин B и т. д. Чтобы использовать этот метод, см. статью multiple apps. Чтобы использовать этот метод, см. множество приложений.
Добавление приложения в AppsFlyer
Чтобы добавить приложение в AppsFlyer:
- В AppsFlyer нажмите My Apps.
- Нажмите Добавить приложение.
Откроется окно Add Your App. - Выберите Android out of store APK.
- Заполните следующие поля:
- Android package name: произвольный текст
- Channel name: должно совпадать с именем канала в манифесте, как описано в следующем разделе. В качестве лучшей практики рекомендуется задать для этого поля значение cn_market.
- Ссылка на приложение
Примечание: вы можете создать приложение, выбрав вариант Ожидает одобрения или не опубликовано, но это не рекомендуется. Перед тем как использовать этот метод, проконсультируйтесь со своим CSM.
Подготовка APK/манифеста
Чтобы подготовить отдельный APK/манифест для каждого стороннего магазина приложений:
- Добавьте следующее в манифест, чтобы идентифицировать трафик из Китая:
Примечание: Параметры чувствительны к регистру. Мы рекомендуем задать для канала значение cn_market.< meta-data android:name="CHANNEL" android:value="cn_market"> - Выберите один из следующих методов, чтобы идентифицировать магазин:
-
Метод манифеста: Добавьте следующую строку в файл AndroidManifest.xml. Значение AF_STORE должно быть уникальным для каждого магазина.
--ИЛИ--<meta-data android:name="AF_STORE" android:value="example_store"/> -
Метод API: Подготовьте отдельный APK для каждого стороннего магазина приложений. Чтобы установить значение AF_STORE, вызовите API setOutOfStore. Задайте уникальное значение для каждого магазина.
AppsFlyerLib.getInstance().setOutOfStore("example_store")
-
Метод манифеста: Добавьте следующую строку в файл AndroidManifest.xml. Значение AF_STORE должно быть уникальным для каждого магазина.
Описанные методы задают параметр AF_STORE, который, в свою очередь, задает поле install_app_store в данных атрибуции. Это поле доступно:
- когортный анализ
- Отчеты по необработанным данным
Дополнительные аспекты атрибуции
Обходной путь для магазинов без поддержки ссылок атрибуции
Многие сторонние магазины приложений Android в Китае также продают трафик, то есть одновременно являются и рекламными платформами. В некоторых случаях эти магазины не поддерживают использование ссылок атрибуции. В качестве альтернативы можно использовать следующие обходные решения:
- install_app_store field: (рекомендуется) Если установка выполняется без ссылки атрибуции, AppsFlyer относит установку к органическим установкам. Используя поле install_app_store, вы можете определить фактический источник установки. Примечание: поле install_app_store задается с помощью параметра AF_STORE, описанного в предыдущем разделе.
- Название предустановки в файле манифеста. Использование названия предустановки, содержащегося в манифесте. Недостаток этого метода в том, что если предустановленный APK попадет на рынок, информация об атрибуции будет некорректной. При использовании этого метода убедитесь, что у вас есть соответствующие коммерческие условия, чтобы защитить APK.
Ссылки атрибуции для внутреннего трафика в Китае
Ссылки для Китая
Для медиаисточников, у которых есть только внутренний (китайский) трафик, используйте доменные ссылки, указанные здесь. Эти ссылки распознаются внутри Китая и обеспечивают более удобный пользовательский опыт.
Для обычных ссылок атрибуции используйте: https://app.aflink.com - (aflink.com)
Для ссылок атрибуции OneLink используйте: https://go.onelnk.com (onelnk.com). Примечание: прежде чем использовать домен onelnk.com, обратитесь к своему CSM в AppsFlyer.
Обратите внимание: в weChat в список разрешенных включен только onelnk.com, тогда как onelink.me заблокирован
Используйте af_r
Используйте af_r, чтобы пользователи перенаправлялись либо на URL-адрес для загрузки APK, либо в магазин APK, а не в Google Play.
Примечание: часто у интегрированных медиаисточников с внутренним китайским трафиком этот параметр по умолчанию задан как &redirect=false в шаблоне URL-адреса атрибуции. Это означает, что перенаправление выполняют рекламные платформы, а не AppsFlyer.
Подробнее об интегрированных медиаисточниках:Продвижение приложения Android в Китае
Выявление перехвата магазина приложений
В сценариях со сторонними магазинами приложений пользователь сначала устанавливает приложение из стороннего магазина медиаисточника (рекламной сети). В случае перехвата установки всплывающее предупреждение предлагает пользователю установить или обновить приложение напрямую из магазина приложений производителя устройства (телефона). Если пользователь соглашается, его перенаправляют для загрузки из магазина приложений производителя, после чего APK загружается. Это означает, что производитель устройства перехватил установку.
Следующий пример иллюстрирует как легитимный сценарий, так и сценарий с перехватом:
Легитимный сценарий
Пользователь приложения нажимает на рекламу, показанную media-example, и либо сразу загружает приложение, либо перенаправляется в legitimate-app-store для загрузки приложения. В AppsFlyer записывается следующая информация об атрибуции:
- Медиаисточник: media-example
- Магазин приложений для установки: legitimate-app-store
Перехваченный поток
Пользователь приложения кликает по рекламному объявлению, показанному media-example. При запуске загрузки появляется всплывающее предупреждение с предложением скачать приложение из магазина приложений производителя устройства. Если пользователь соглашается, его перенаправляют в магазин производителя, где он скачивает приложение. В AppsFlyer записывается следующая информация об атрибуции:
- Медиаисточник: media-example
- Install-app-store: manufacturer-store
Идентификация события перехвата в AppsFlyer
Чтобы выявить перехваченную установку, сравните поля «медиаисточник» и install-app-store. Несоответствие между ожидаемым значением install-app-store и фактическим значением install-app-store указывает на перехват установки.