すべてのプロダクト
Search
ドキュメントセンター

Web Application Firewall:Android用SDKの統合

最終更新日:Jul 29, 2026

コンソールでボット管理のシナリオベースの対クロールルールを設定するには、Web Application Firewall (WAF) アプリ保護 SDK を統合する必要があります。本トピックでは、Android アプリに SDK を統合する方法について説明します。

背景情報

アプリ保護 SDK は、アプリクライアントからのリクエストに署名します。WAF サーバーは、これらのリクエスト署名を検証してリスクを特定し、悪意のあるリクエストをブロックしてアプリを保護します。

制限事項

  • Android の場合、SDK は arm64-v8a および armeabi-v7a CPU アーキテクチャをサポートしています。

  • Android の API レベルは 16 以上である必要があります。

  • init メソッドは時間がかかる場合があります。完全な保護を確保するため、init メソッドと vmpSign メソッドの呼び出しの間に少なくとも 2 秒の間隔を空けることを推奨します。この遅延は最適な保護のために推奨されますが、必須ではありません。ビジネスニーズに応じてこの遅延を調整できますが、遅延が短いとセキュリティ機能が完全に有効にならない可能性があります。

  • ProGuard を使用してコードの難読化を行う場合は、-keep オプションを使用して SDK のメソッドを保持してください。例:

    -keep class com.aliyun.TigerTally.** {*;}
    -keep class com.aliyun.captcha.* {*;}
    -keepclassmembers,allowobfuscation class * {
         @com.alibaba.fastjson.annotation.JSONField <fields>;
    }
    -keep class com.alibaba.fastjson.** {*;}

前提条件

  • Android アプリ用の SDK を取得していること。

    SDK を取得するには、製品技術の専門家に チケット を送信してください。

    説明

    Android SDK には、AliTigerTally_X.Y.Z.aarAliCaptcha_X.Y.Z.aar の 2 つの AAR ファイルが含まれています。X.Y.Z はバージョン番号です。

  • SDK の認証キー (appkey とも呼ばれます) を取得済みであること。

    ボット管理を有効にした後、Web Application Firewall 3.0 コンソールにログインします。 トップメニューバーから、WAF インスタンスのリソースグループとリージョン (中国本土 または 中国本土以外) を選択します。。 左側のナビゲーションウィンドウで、保護設定 > BOT 管理 > Bot 管理テンプレート を選択します。 アプリ 保護 タブに移動し、右上隅にある AppKey の取得と複製 をクリックして SDK 認証キーを取得します。 このキーは SDK を初期化するために必須であり、統合コードに含める必要があります。

    説明

    Alibaba Cloud アカウントには、WAF によって保護されているすべてのドメイン名に対して一意の appkey があります。この appkey は、Android、iOS、および HarmonyOS アプリ全体の SDK 統合に使用されます。

    認証キーの例:****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****

ステップ1:プロジェクトの作成

Android Studio で、設定ウィザードに従って新しい Android プロジェクトを作成します。プロジェクトのディレクトリ構造は次のとおりです。


tigertally-demo-apk [TigerTallyDemo]
  .gradle
  .idea
  app
    build
    libs
      AliCaptcha_xxx.aar
      AliTigerTally_xxx.aar
    src
      androidTest
      main
      test [unitTest]
    .gitignore
    build.gradle
    proguard-rules.pro
  gradle
    wrapper
      gradle-wrapper.jar
      gradle-wrapper.properties
  .gitignore
  .java-version
  build.gradle
  gradle.properties
  gradlew
  gradlew.bat
  local.properties
  settings.gradle
  tigertally.jks
  External Libraries
  Scratches and Consoles

ステップ2:AARの統合

  1. tigertally-X.Y.Z-xxxxxx-android.tgz SDK ファイルを解凍し、解凍したフォルダーからすべての AAR ファイルをメインモジュールの libs ディレクトリにコピーします (正確なパスはプロジェクトの設定によって異なります)。

  2. アプリの build.gradle ファイルを開き、libs ディレクトリにある AliTigerTally_X.Y.Z.aar と AliCaptcha_X.Y.Z.aar の依存関係を追加します。

    重要

    AliTigerTally_X.Y.Z.aar と AliCaptcha_X.Y.Z.aar ファイル名の X.Y.Z を、ご使用の AAR ファイルのバージョン番号に置き換えてください。

    設定は次のとおりです:

    dependencies {
        // ...
        implementation files('libs/AliTigerTally_X.Y.Z.aar')
        implementation files('libs/AliCaptcha_X.Y.Z.aar')
      
        // サードパーティライブラリの依存関係
        implementation 'com.alibaba:fastjson:1.2.83_noneautotype'
        implementation 'com.squareup.okhttp3:okhttp:3.11.0'
        implementation 'com.squareup.okio:okio:1.14.0'
    }

ステップ3:SOのCPUアーキテクチャのフィルタリング

プロジェクトで SO ファイルを使用していない場合は、次の設定を build.gradle ファイルに追加します。

android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a', 'armeabi-v7a'
        }
    }
}

ステップ4:権限のリクエスト

  • 必須の権限

    <uses-permission android:name="android.permission.INTERNET"/>
  • 任意の権限

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
説明

Android 6.0 以降では、android.permission.READ_EXTERNAL_STORAGEandroid.permission.WRITE_EXTERNAL_STORAGE を動的にリクエストする必要があります。

ステップ5:統合コードの追加

1. ヘッダーファイルの追加

import com.alibaba.fastjson.*;
import com.aliyun.TigerTally.*;

2. データ署名のセットアップ

  1. 各エンドユーザーにカスタム ID を設定します。これにより、WAF の緩和ポリシーをより柔軟に設定できます。

    /**
    * ユーザーアカウントを設定します。
    *
    * @param account ユーザーアカウント。
    * @return エラーコード。
    */
    public static int setAccount(String account)
    • パラメーター

      • account: String。 ユーザーを識別する文字列。非機密化された形式を使用することを推奨します。

    • 戻り値: int。 成功した場合は 0、失敗した場合は -1 を返します。

    • サンプルコード

      // ゲストの場合、setAccount をスキップして直接 SDK を初期化できます。ユーザーがログインした後、setAccount を呼び出して再初期化します。
      String account = "user001";
      TigerTallyAPI.setAccount(account);
  2. SDK を初期化し、初回のデータ収集を実行します。

    初回のデータ収集では、デバイス情報が一度収集されます。ビジネス要件に基づいて、init 関数を再度呼び出して新しい収集を実行できます。完全収集、カスタムプライバシー収集、非プライバシー収集の 3 つの収集モードが利用可能です。非プライバシーモードでは、imei、imsi、simSerial、wifiMac、wifiList、bluetoothMac、androidId など、エンドユーザーのプライバシーに関連するフィールドからデータは収集されません。

    説明

    コンプライアンス要件に準拠し、データの完全性を確保する収集モードを選択してください。完全なデータはリスク識別の精度を向上させます。

    // 初期化コールバック
    public interface TTInitListener {
        // codeパラメーターは、インターフェイス呼び出しのステータスコードを示します。
        void onInitFinish(int code);
    }
    
    /**
     * コールバックを使用してSDKを初期化します。
     *
     * @param context アプリケーションのコンテキスト。
     * @param appkey SDKのappkey。
     * @param collectType データ収集モード。
     * @param otherOptions 任意のパラメーター。
     * @param listener 初期化コールバック。
     * @return エラーコード。
     */
    public static int init(Context context, String appkey, int collectType,
                           Map<String, String> otherOptions, TTInitListener listener);
    • パラメーター

      • context: Context。 アプリケーションのコンテキスト。

      • appkey: String。 SDK の appkey。

      • collectType: int。 収集モード。有効な値:

        パラメーター

        説明

        TT_DEFAULT

        すべてのデータを収集します。

        TigerTallyAPI.TT_DEFAULT

        TT_NO_BASIC_DATA

        基本的なデバイスデータを収集しません。

        内容:デバイス名 (Build.DEVICE)、Androidバージョン (Build.VERSION#RELEASE)、画面解像度。

        TigerTallyAPI.X | TigerTallyAPI.Y

        (X と Y の両方が収集されないことを示します。X と Y は特定の項目のフィールド名を表します。)

        TT_NO_IDENTIFY_DATA

        デバイス識別子データを収集しません。

        内容:IMEI、IMSI、SimSerial、BuildSerial (SN)、MACアドレス。

        TT_NO_UNIQUE_DATA

        一意の識別子データを収集しません。

        内容:OAID、Google Advertising ID、Android ID。

        TT_NO_EXTRA_DATA

        拡張デバイスデータを収集しません。

        内容:悪意のある/グレーゾーンのアプリリスト、LAN IP、DNS IP、接続中のWi-Fi情報 (SSID, BSSID)、近くのWi-Fiリスト、位置情報、センサー情報。

        TT_NOT_GRANTED

        前述のプライバシーデータを一切収集しません。

        TigerTallyAPI.TT_NOT_GRANTED

      • otherOptions: Map<String, String>。 任意のデータ収集パラメーター。null も指定可能です。利用可能なパラメーター:

        パラメーター

        説明

        IPv6

        デバイス情報を報告するためにIPv6ドメイン名を使用するかどうかを指定します。

        • 0 (デフォルト):IPv4ドメイン名を使用します。

        • 1:IPv6ドメイン名を使用します。

        1

        Intl

        中国本土以外のリージョンにデバイス情報を報告するかどうかを指定します。

        • 0 (デフォルト):中国本土に報告します。

        • 1:中国本土以外のリージョンに報告します。

        1

        CustomUrl

        データ報告サーバーのドメイン名。

        https://cloudauth-device.us-west-1.aliyuncs.com

        CustomHost

        データ報告サーバーのホスト。

        cloudauth-device.us-west-1.aliyuncs.com

        説明

        ほとんどの国際サイトでは、Intl パラメーターのみを設定します。特定のサイトにデータを報告するには、CustomUrl および CustomHost パラメーターを設定します。利用可能なサイトは次のとおりです。

        • Intl0 に設定されている場合、データは中国 (上海) のデフォルトサイトである https://cloudauth-device.cn-shanghai.aliyuncs.com に報告されます

        • Intl1 に設定されている場合:

          • デフォルトサイトはシンガポールです:https://cloudauth-device.ap-southeast-1.aliyuncs.com

          • インドネシア (ジャカルタ):https://cloudauth-device.ap-southeast-5.aliyuncs.com

          • US (Silicon Valley):https://cloudauth-device.us-west-1.aliyuncs.com

          • ドイツ (フランクフルト):https://cloudauth-device.eu-central-1.aliyuncs.com

          • 中国 (香港):https://cloudauth-device.cn-hongkong.aliyuncs.com

      • listener: TTInitListener。 SDK の初期化コールバック。コールバックを使用して初期化ステータスを確認できます。null も指定可能です。

        TTCode

        コード

        説明

        TT_SUCCESS

        0

        SDK は正常に初期化されました。

        TT_NOT_INIT

        -1

        SDK が初期化されていません。

        TT_NOT_PERMISSION

        -2

        SDK に必要な Android 権限が付与されていません。

        TT_UNKNOWN_ERROR

        -3

        不明なシステムエラー。

        TT_NETWORK_ERROR

        -4

        ネットワークエラー。

        TT_NETWORK_ERROR_EMPTY

        -5

        ネットワークエラー:レスポンスが空です。

        TT_NETWORK_ERROR_INVALID

        -6

        無効なネットワークレスポンス形式です。

        TT_PARSE_SRV_CFG_ERROR

        -7

        サーバー設定の解析に失敗しました。

        TT_NETWORK_RET_CODE_ERROR

        -8

        ゲートウェイが失敗レスポンスを返しました。

        TT_APPKEY_EMPTY

        -9

        appkey が空です。

        TT_PARAMS_ERROR

        -10

        無効なパラメーター。

        TT_FGKEY_ERROR

        -11

        キーの計算エラー。

        TT_APPKEY_ERROR

        -12

        SDK バージョンと appkey が一致しません。

    • 戻り値: int。 成功した場合は 0、失敗した場合は -1 を返します。

    • サンプルコード

      // appkey は Alibaba Cloud プラットフォームで割り当てられた認証キーです。
      final String appkey="******";
      // IPv6 と海外リージョンへのレポートを設定するための任意のパラメーター。
      Map<String, String> options = new HashMap<>();
      options.put("IPv6", "0");   // IPv4を使用します。
      options.put("Intl", "0");   // 中国本土に報告します。
      //options.put("Intl", "1"); // 中国本土以外のリージョンに報告します。
      
      // US (Silicon Valley) に報告します。
      //options.put("CustomUrl", "https://cloudauth-device.us-west-1.aliyuncs.com"); 
      //options.put("CustomHost", "cloudauth-device.us-west-1.aliyuncs.com"); 
      
      // 初回のデータ収集では、デバイス情報が一度収集されます。ビジネス要件に基づいて、init 関数を再度呼び出して新しい収集を実行できます。
      // 完全収集。
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_DEFAULT, options, null);
      
      // 収集するプライバシーデータを指定します。「|」演算子を使用して、さまざまなフラグを組み合わせることができます。
      int privacyFlag = TigerTallyAPI.TT_NO_BASIC_DATA | TigerTallyAPI.TT_NO_UNIQUE_DATA;
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, privacyFlag, options, null);
      
      // プライバシーフィールドを収集しません。
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_NOT_GRANTED, options, null);
      Log.d("AliSDK", "ret:" + ret);
  3. データをハッシュ化します。

    このカスタム署名メソッドは、入力データのハッシュを計算し、生成された whash 文字列をカスタム署名データとして返します。POST、PUT、PATCH リクエストの場合は、リクエストボディを渡します。GET および DELETE リクエストの場合は、完全な URL を渡します。whash 文字列は、HTTP リクエストヘッダーの ali_sign_whash フィールドに追加する必要があります。

    // リクエストタイプ:
    public enum RequestType { GET, POST, PUT, PATCH, DELETE }
    
    /**
     *  カスタム署名用にデータをハッシュ化します。
     *
     * @param type  リクエストタイプ。
     * @param input ハッシュ化するデータ。
     * @return whash文字列。
     */
    public static String vmpHash(RequestType type, byte[] input);
    • パラメーター

    • type: RequestType。 リクエストタイプ。有効な値:

      • GET: GETリクエスト。

      • POST: POSTリクエスト。

      • PUT: PUTリクエスト。

      • PATCH: PATCHリクエスト。

      • DELETE: DELETEリクエスト。

    • input: byte[]。 ハッシュ化するデータ。

    • 戻り値: String。 whash 文字列を返します。

    • サンプルコード

      // GETリクエスト
      String url = "https://tigertally.aliyun.com/apptest";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes());
      Log.d("AliSDK", "whash:" + whash);
      
      // POSTリクエスト
      String body = "hello world";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes());
      Log.d("AliSDK", "whash:" + whash);
      説明

      このメソッドはデフォルトの署名には必要ありません。カスタム署名の場合は、このメソッドを呼び出して署名する前にデータをハッシュ化する必要があります。

  4. データに署名します。

    このメソッドは VMP テクノロジーを使用して入力データに署名し、リクエスト認証用の wtoken 文字列を返します。

    /**
     * データに署名します。
     *
     * @param type 署名タイプ。
     * @param input 署名するデータ。
     * @return wtoken文字列。
     */
    public static String vmpSign(int type, byte[] input);
    • パラメーター

      • type: int。 データ署名タイプ。値は 1 である必要があります。

      • input: byte[]。 署名するデータ。これは通常、完全なリクエストボディ、またはカスタム署名からの whash 文字列です。

    • 戻り値: String。 wtoken 文字列を返します。

    • サンプルコード

      // コンソールでデフォルト署名が設定されている場合 (カスタム署名は選択されていません)、このコードを使用します。
      String body = "i am the request body, encrypted or not!";
      String wtoken = TigerTallyAPI.vmpSign(1, body.getBytes("UTF-8"));
      Log.d("AliSDK", "wToken:" + wtoken);
      
      // コンソールでカスタム署名が設定されている場合、このコードを使用します。
      // GETリクエスト
      String url = "https://tigertally.aliyun.com/apptest";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes());
      String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
      Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken);
      
      // POSTリクエスト
      String body = "hello world";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes());
      String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
      Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken);
      説明
      • カスタム署名のために vmpHash を呼び出す場合、vmpSign メソッドの input パラメーターは生成された whash 文字列です。アプリのボット対策ポリシーを設定する際、カスタム署名フィールドの値を ali_sign_whash に設定する必要があります。

      • vmpHash を呼び出して GET リクエストの whash 文字列を生成する場合、入力 URL がネットワークリクエストで使用される最終的な URL と同一であることを確認してください。一部のフレームワークは自動的に漢字やパラメーターをエンコードするため、URL エンコーディングには特に注意してください。

      • vmpHash メソッドの input パラメーターは、空のバイト配列や空の文字列をサポートしていません。入力が URL の場合、パスまたはパラメーターを含んでいる必要があります。

      • vmpSign を呼び出す際、リクエストボディが空の場合 (たとえば、GET リクエストや空のボディを持つ POST リクエスト)、null オブジェクトまたは空文字列のバイト値 ("".getBytes("UTF-8") など) を渡します。

      • whash または wtoken の値が次のいずれかの文字列である場合、SDK はエラーを返します。

        • you must call init first: SDK が初期化されていません。init() を先に呼び出してください。

        • you must input correct data: 無効な入力データです。

        • you must input correct type: 無効な入力タイプです。

3. 二次検証の実行

  1. 結果を評価します。

    レスポンスcookie および body フィールドをチェックして、二次検証が必要かどうかを判断します。このメソッドを呼び出す前に、複数の Set-Cookie エントリを単一の cookie 文字列にマージします。

    /**
     * 二次検証を実行するかどうかを判断します。
     *
     * @param cookie cookie文字列。
     * @param body body文字列。
     * @return パスした場合は0、二次検証が必要な場合は1。
     */
    public static int cptCheck(String cookie, String body)
    • パラメーター

      • cookie: String。 HTTP レスポンスからのすべての cookie

      • body: String。 HTTP レスポンスからの完全な body

    • 戻り値: int。 リクエストがパスした場合は 0、二次検証が必要な場合は 1 を返します。

    • サンプルコード

      String cookie = "key1=value1;kye2=value2;";
      String body = "....";
      int recheck = TigerTallyAPI.cptCheck(cookie, body);
      Log.d("AliSDK", "recheck:" + recheck);
  2. スライダーを作成します。

    cptCheck が 1 を返した場合、スライダーオブジェクトを作成します。TTCaptcha オブジェクトは、スライダーウィンドウを表示および非表示にするための show() および dismiss() メソッドを提供します。TTOption はスライダーの設定可能なパラメーターをカプセル化し、TTListener は成功および失敗状態のコールバックを提供します。カスタムスライダーウィンドウを使用するには、カスタムページの URL を渡します。ローカル HTML ファイルとリモートページの両方がサポートされています。

    /**
     * スライダーオブジェクトを作成します。
     *
     * @param activity スライダーが表示されるアクティビティ。
     * @param option スライダーのパラメーター。
     * @param listener スライダーのコールバック。
     * @return スライダー検証オブジェクト。
     */
    public static TTCaptcha cptCreate(Activity activity, TTOption option, TTListener listener);
    
    
    /**
     * スライダーオブジェクト。
     */
    public class TTCaptcha {
        /**
         * スライダーを表示します。
         */
        public void show();
    
        /**
         * スライダーを非表示にします。
         */
        public void dismiss();
    
        /**
         * データ統計用のスライダーtraceIdを取得します。
         */
        public String getTraceId();
    }
    
    /**
     * スライダーのパラメーター。
     */
    public static class TTOption {
        // 空白領域をクリックしてスライダーを閉じることができるかどうかを指定します。
        public boolean cancelable;
    
        // カスタムページ。ローカルHTMLファイルとリモートURLがサポートされています。
        public String customUri;
    
        // 言語。
        public String language;
    }
    
    /**
     * スライダーイベントのコールバック。
     */
    public interface TTListener {
        /**
         * 検証が成功したときに呼び出されます。
         *
         * @param captcha スライダーオブジェクト。
         * @param data トークン。デフォルト値はcertifyIdです。
         */
        void success(TTCaptcha captcha, String data);
    
        /**
         * 検証が失敗したときに呼び出されます。
         *
         * @param captcha スライダーオブジェクト。
         * @param code エラーコード。
         */
        void failed(TTCaptcha captcha, String code);
    }
    • パラメーター

      • activity: Activity。 現在のページのアクティビティ。

      • option: TTOption。 スライダーの設定パラメーター。

      • listener: TTListener。 スライダーのステータスコールバック。

    • 戻り値: TTCaptcha。 スライダーオブジェクト。

    • サンプルコード

    TTCaptcha.TTOption option = new TTCaptcha.TTOption();
    // option.customUri  = "file:///android_asset/ali-tt-captcha-demo.html";
    option.language   = "cn";
    option.cancelable = false;
    
    TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
        @Override
        public void success(TTCaptcha captcha, String data) {
            Log.d(TAG, "captcha check success:" + data);
        }
        @Override
        public void failed(TTCaptcha captcha, String code) {
            Log.d(TAG, "captcha check failed:" + code);
        }
    });
    captcha.show();
    説明

    検証の失敗は、ユーザーがスライダーチャレンジを完了した後に例外が検出されたことを示します。

    エラーコードは次のとおりです:

    • 1001: 検証に失敗しました。

    • 1002: システム例外。

    • 1003: 無効なパラメーター。

    • 1005: 検証がキャンセルされました。

    • 8001: スライダーの表示に失敗しました。

    • 8002: スライダー検証データが異常です。

    • 8003: 内部スライダー検証例外。

    • 8004: ネットワークエラー。

ベストプラクティスの例

package com.aliyun.tigertally.apk;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.aliyun.TigerTally.TigerTallyAPI;
import com.aliyun.TigerTally.captcha.api.TTCaptcha;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class DemoActivity extends AppCompatActivity {
    private final static String TAG = "TigerTally-Demo";

    private final static String APP_HOST = "******";
    private final static String APP_URL  = "******";
    private final static String APP_KEY  = "******";

    private final static OkHttpClient okHttpClient = new OkHttpClient();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);

        doTest();
    }

    private void doTest() {
        Log.d(TAG, "captcha flow");
        new Thread(() -> {
            // SDK を初期化します。
            Map<String, String> options = new HashMap<>();
            //options.put("Intl", "1"); // 海外リージョンへのレポートを有効にするには。
            // 完全なデータ収集モードを使用します。
            int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_DEFAULT, options, null);
            // または、プライバシーフィールドを収集しません。
            // int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_NOT_GRANTED, null, null);
            Log.d(TAG, "tiger tally init: " + ret);

            // 初期化が完了するのを待ちます。
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            // データに署名します。
            String data = "hello world";
            String whash = null, wtoken = null;
            // カスタム署名を使用します。
            whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, data.getBytes());
            wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
            Log.d(TAG, "tiger tally vmp: " + whash + ", " + wtoken);

            // 標準署名を使用します。
            // wtoken = TigerTallyAPI.vmpSign(1, data.getBytes());
            // Log.d(TAG, "tiger tally vmp: " + wtoken);


            // リクエストを送信します。
            doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
                // スライダーが必要かどうかを確認します。
                int recheck = TigerTallyAPI.cptCheck(cookie, body);
                Log.d(TAG, "captcha check result: " + recheck);

                if (recheck == 0) return;
                this.runOnUiThread(this::doShow);
            });
        }).start();
    }

    // スライダーを表示します。
    public void doShow() {
        Log.d(TAG, "captcha show");

        TTCaptcha.TTOption option = new TTCaptcha.TTOption();
        // option.customUri = "file:///android_asset/ali-tt-captcha-demo.html";
        option.language   = "cn";
        option.cancelable = false;

        TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
            @Override
            public void success(TTCaptcha captcha, String data) {
                Log.d(TAG, "captcha check success:" + data);
            }

            @Override
            public void failed(TTCaptcha captcha, String code) {
                Log.d(TAG, "captcha check failed:" + code);
            }
        });

        captcha.show();
    }

    // リクエストを送信します。
    public static void doPost(String url, String host, String whash, String wtoken, String body, Callback callback) {
        Log.d(TAG, "start request post");

        int responseCode = 0;
        String responseBody = "";
        StringBuilder responseCookie = new StringBuilder();
        try {
            Request.Builder builder = new Request.Builder()
                    .url(url)
                    .addHeader("wToken", wtoken)
                    .addHeader("Host",   host)
                    .post(RequestBody.create(MediaType.parse("text/x-markdown"), body.getBytes()));

            if (whash != null) {
                builder.addHeader("ali_sign_whash", whash);
            }
            Response response = okHttpClient.newCall(builder.build()).execute();

            responseCode = response.code();
            responseBody = response.body() == null ? "" : response.body().string();
            for (String item : response.headers("Set-Cookie")) {
                responseCookie.append(item).append(";");
            }

            Log.d(TAG, "response code:" + responseCode);
            Log.d(TAG, "response cookie:" + responseCookie);
            Log.d(TAG, "response body:" + (responseBody.length() > 100 ? responseBody.substring(0, 100) : ""));

            if (response.isSuccessful()) {
                Log.d(TAG, "success: " + response.code() + ", " + response.message());
            } else {
                Log.e(TAG, "failed: " + response.code() + ", " + response.message());
            }

            response.close();
        } catch (Exception e) {
            e.printStackTrace();
            responseCode = -1;
            responseBody = e.toString();
        } finally {
            if (callback != null) {
                callback.onResponse(responseCode, responseCookie.toString(), responseBody);
            }
        }
    }

    public interface Callback {
        void onResponse(int code, String cookie, String body);
    }
}