Guide to attributing out-of-market Android apps in China

  • Advertisers
  • Developers

At a glance: Attribution of Android devices without GAID using OAID or IMEI. Best practice for viewing attribution data in AppsFlyer. Use AppsFlyer to aid in the detection of device manufacturer app-store hijacking.

In China, there are several challenges that app owners face:

  • Google Play Services (GPS) is not active in the majority of Android devices, meaning it has no GAID.
  • The use of out-of-market stores creates opportunities for store hijacking by device manufacturers.
  • Attribution servers are inaccessible or slow to respond.
  • Managing and viewing attribution data from both Google Play store and out-of-market stores

Using AppsFlyer, these challenges can be overcome by:

  • Implementing attribution based on OAID or device IMEI as an alternative to GAID
  • Preparing APKs with unique identifiers to detect Android store hijacking
  • Configuring attribution links that are recognized within China

Preparing the App

Integrate the SDK in the app and prepare the APK using the instructions that follow.

Integrate the SDK in the app

Use IMEI and/or OAID instead of GAID

In general, Google Play Services is not installed on Android devices supplied in China. This means that there is no GAID to use as a unique attribution identifier.

An alternative to using GAID is IMEI and/or OAID, where this is available, to enable attribution recording.

OAID:

  • Requires AppsFlyer Android SDK version 4.10.3 and later.
  • Provides a unique identifier that is not device-dependent.
  • As of October 2019, OAID is available on Huawei devices running HMS 2.6.2 or later.

IMEI:

  • AppsFlyer SDK prevents access to IMEI unless configured otherwise. The general assumption is that Google Play Services is active on the device and that IMEI is not needed. In China, this is not the case.
  • When integrating the AppsFlyer SDK in your app, access to IMEI needs to be enabled.
  • OAID and IMEI should be implemented simultaneously to ensure attribution irrespective of the Android version. 
    This is because access to IMEI is restricted starting with Android 10 (API level 29), released in late 2019.  Neither your app nor the AppsFlyer SDK can collect these identifiers. Where no identifier is available AppsFlyer resorts to attribution by  fingerprinting

Before you start:

  • AppsFlyer Android SDK 4.10.3 or later is required

Use one of the following procedures:

  • To enable OAID and IMEI complete the following sections:
    1. Enable OAID
    2. Enable IMEI Android API level 23-28
  • To enable IMEI complete: Enable IMEI Android API level up to 22

Enable OAID

To enable the collection of OAID, the setCollectOaid API needs to be enabled as follows:
AppsFlyerLib.getInstance().setCollectOaid(true);

Enable IMEI Android API level 23-28

Use the following code example to integrate the SDK. This code example makes use of the method onConversionDataSuccess. This is the name of the method for getting conversion data starting SDK V5. If you are using an SDK version lower than 5.0.0, the name of the method is onInstallConversionDataLoaded . We recommend that you upgrade to SDK 5.0.0. To learn more, click here.

Note: Android API level 23 or later requires user permission, using a prompt, to access the IMEI. This may result in the IMEI being retrieved after the activation of the StartTracking API. Starting from API level 29 access to the IMEI is restricted.

            
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);
      //Or use the two APIs below let AppsFlyer SDK collect IMEI & android id
      //no matter if the Google Play Service exists or not.
      AppsFlyerLib.getInstance().setCollectIMEI(true);
      AppsFlyerLib.getInstance().setCollectAndroidID(true);
      //Configure the min time between two sessions, the recommendation is 2 seconds。
      AppsFlyerLib.getInstance().setMinTimeBetweenSessions(2);
      final AppsFlyerConversionListener conversionDataListener = new 
AppsFlyerConversionListener() {
       @Override
       public void onConversionDataSuccess(Map<string, string=""> map) {}
       @Override
       public void onInstallConversionFailure(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;
    }
}

On the first launch of the app a READ_PHONE_STATE permission request is sent to the user in onResume() cal back of the MainActivity.

build.gradle in the app module

allprojects {
    repositories {
       ....
       maven { url 'https://jitpack.io'}
    }
}
dependencies {
    implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
}
MainActivity
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().setCollectIMEI(true);
                        AppsFlyerLib.getInstance().setCollectAndroidID(true);
                        //NOTE: Here the report session API is reportTrackSession() not the startTracking()
                        AppsFlyerLib.getInstance().reportTrackSession(AFApplication.getAppInstance());
                    } else {
                    }
                });
    }
}

Enable IMEI Android API level up to 22

Use the following code example to integrate the SDK. Note: the code example uses the method onConversionDataSuccess to get conversions data. This is the name of the method starting SDK V5. If you are using an SDK version lower than 5.0.0, the name of the method is onInstallConversionDataLoaded . We recommend that you upgrade to SDK 5.0.0. To learn more, click here.


 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);
      //Use the two APIs below let AppsFlyer SDK collect IMEI & android id no matter the 
      //Google Play Service is exist or not.
      AppsFlyerLib.getInstance().setCollectIMEI(true);
      AppsFlyerLib.getInstance().setCollectAndroidID(true);  
      final AppsFlyerConversionListener conversionDataListener = new AppsFlyerConversionListener() {
          @Override
          public void onConversionDataSuccess(Map<string, string=""> map) {} 
    	  @Override
          public void onInstallConversionFailure(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;
    }
}

Preparing the APK

Select how attribution data displays in AppsFlyer from:

  • (best practice) Single app: The data of all out-of-market (China domestic) stores displays under a single but separate app. This means that you the marketer see all the domestic Chinese stores attribution data under one app in AppsFlyer. To use this method continue to adding the app in AppsFlyer section that follows.
  • Single app consolidated with Google Play store: The attribution of both the out-of-market Chinese stores and Google Play store display as a single entity. This option requires that the package name of both the Google Play store and the out-of-market-stores are identical. Using this option assume that the app is already defined in AppsFlyer. To use this method continue to preparing the APK/manifest in the section that follows.
  • Multiple apps means that the attribution data of each store is shown under a separate app. For example, each of the following is shown separately, Google Play Store, out-of-market store A, out-of-market-store B, etc. To use this method, see multiple apps.

Adding the app in AppsFlyer

To add the app in AppsFlyer:

  1. In AppFlyer, click My Apps.
  2. Click Add App.
    The Add Your App window opens.
  3. Select Android out of store APK.
  4. Complete the following fields:
    • Android package name: free text
    • Channel name: Must be identical to the channel name in the manifest as explained in the section that follows. Best practice is to set this field to cn_market.
    • App URL

Note: You can create the app using the Pending approval or unpublished option, but this is not recommended. Consult with your CSM before using this method.

Preparing the APK/manifest

To Prepare a separate APK/manifest for each out-of-market store as follows:

  1. Add the following to the manifest to identify Chinese traffic:
    < meta-data android:name="CHANNEL" android:value="cn_market">
    Note: Parameters are case sensitive. We recommend that you set the channel to cn_market.
  2. Chose one of the following methods to identify the store:
    • Manifest method: Add the following line to the AndoridManifest.xml file. The AF_STORE value needs to be unique for each store.
      <meta-data android:name="AF_STORE" android:value="example_store"/>
      --OR--
    • API method: Prepare a separate APK for each out-of-store market. Call the setOutOfStore API to set the AF_STORE value. Set a unique value for each store.
      AppsFlyerLib.getInstance().setOutOfStore("example_store")

The methods described set the parameter AF_STORE. The value of this parameter is shown in AppsFlyer raw data reports in the install_app_store field. You can get raw data by download, Pull API, and Data locker. Raw data reports are an AppsFlyer premium feature.

 

Additional attribution considerations

Workaround for stores without attribution link support

Many out-of-store markets in China also sell traffic, meaning they are also an ad platform. In some cases, these stores do not support the use of attribution links. The following workaround solutions can be used as an alternative:

  • install_app_store field: (recommended) When installation takes place without an attribution link, AppsFlyer attributes the installation to organic installs. By using the install_app_store field, you can identify the actual source of the install. Note: The install_app_store field is set using the AF_STORE parameter described in the previous section.
  • Preinstall name in the manifest file. Using the preinstall name contained n the manifest. The disadvantage of this method is that where the preinstall APK is leaked to the market, attribution information will be incorrect. When using this method ensure that you have the appropriate commercial terms to protect your APK.

China domestic attribution links

When preparing attribution links take into account the following:

China-based links

For media sources that only have domestic (Chinese) traffic, use the domain links detailed here. These links are recognized from within China and provide a superior user experience.

For regular attribution links use: https://app.aflink.com (aflink.com)

For OneLink attribution links use: https://go.onelnk.com (onelnk.com) Note: Before using the onelnk.com domain, contact your AppsFlyer CSM.

Note that in weChat only onelnk.com is whitelisted whereas onelink.me is blocked

Use af_r

Use af_r to make sure users are redirected to either an APK download URL or an APK store and not to Google Play Store.

Note: Frequently, China domestic integrated media sources have this parameter set as &redirect=false in the default attribution URL template. Meaning that redirection will be done by the ad platforms and not by AppsFlyer.

For more information about integrated media sources:
Android app promotion in China

Detecting store hijacking

In out-of-market scenarios, users first install the app from the media source's (ad networks) out-of-market store. In the case of an installation hijack, a warning pop-up suggests the user installs/updates the app directly from the device (phone) manufacturer's app store. Where the user agrees, the user is re-directed to download from the manufacturer's app store and the APK downloaded. Meaning that the device manufacturer has hijacked the install.

The following example illustrates both a legitimate flow and a hijacked flow:

Legitimate flow

An app user clicks on an ad displayed by media-example, and either immediately downloads the app or is redirected to the legitimate-app-store to download the APP. In AppsFlyer the following attribution information is recorded:

  • Media source: media-example
  • Install app store: legitimate-app-store

Hijacked flow

An app user clicks on an ad displayed by media-example. When the download is initiated, a warning pop-up appears, suggesting that the user downloads the app from the device manufacturer's app store. If the user agrees, they are redirected to the manufacturer's store and download the app. In AppsFlyer the following attribution information is recorded:

  • Media source: media-example
  • Install-app-store: manufacturer-store

Identifying the hijack event in AppsFlyer

To identify the hijacked install, you compare the media source and install-app-store fields. A mismatch between the expected install-app-store and the actual install-app-store indicates an installation hijack.

Was this article helpful?
0 out of 0 found this helpful