不正クリック対策

このブログは個人的なプログラミング学習の備忘録を綴ったもので。この実装で動作することは確認済みですが、もっと簡単でいい方法があったり 、あるいはセキュリティや保守の面からあまり好ましい実装ではない可能性があります。また、コードに関する言い回し等も不正確なことが多々あると思われますので、参考にされる場合は十分に検証の上、自己責任でお願いします。
※尚、このブログは適宜加筆修正いたします。



無効なトラフィックの問題
不正クリックが発生した時の対応
連続クリック出来ないようにする
最後に



無効なトラフィックの問題

アプリ等に広告を掲載していてなにより気を付けたいのが、不正クリックなどのポリシー違反でアカウントを停止されることです。これは運営者にとって死活問題なので何としても避けたいところですよね。

無効なトラフィックの問題についてはGoogle側でもしっかり対策をしており不正と思われるクリックはカウントされないようになっていますのでいきなりアカウントが停止になるようなことは少ないようです…がっ! 安心してはいけません。

それはあくまでも有効なクリックにカウントされないということであって、無効なクリック、延いては不正クリックにカウントされれば当然運営者のペナルティにはつながります。

たとえ第三者によるものだとしても最終的にはサイト運営者様の責任で、不正な操作が行われないように広告を管理していただく必要がありますと明記されていますので出来る限りの対応はするべきですよね。


不正クリックが発生した時の対応


とにかくユーザーや広告主の不利益にならないようにトラフィックを監視して、不正が疑われる場合は 「無効なクリックの連絡フォーム」 から報告しろということなので、運営者としてはそれに従ってGoogleアナリティクスでトラフィックを継続的に監視し、もし不正が疑われる場合はその都度Googleに報告することが肝要になります。(そのまんまですね^^;)

ここで注意するべきことはAdMobやAdSenseのレポートを確認するのではなく、アナリティクスでトラフィックを確認するということです。

何故かというと、無効なトラフィックは自動的に排除されるので AdMobやAdSenseのレポートに不正クリックはカウントされていないからです(一旦されるけど消える)。クリック率がいつもと同じだからと安心していたらいきなり広告配信が制限されたなんて事態も十分にあり得ます。


スポンサーリンク



連続クリック出来ないようにする


さて、不正クリックが疑われる事案が発生した時はGoogleに報告するとして、アナリティクスで個別のIPアドレスは特定できないので個別にブロックすることはできません。
これでは不正クリックされては報告してを延々繰り返す羽目になりかねませんし(Google側で排除してくれるのかもしれませんが)、本来的にはそもそも不正クリック(連続クリック)が出来ないようにしたいですよね。

そこで今回は、 (やっと本題です^^; )
Androidアプリで広告がクリックされたら一定期間広告を表示させないようにする実装方法を綴りたいと思います。連続クリックできなくしておけば少し安心できますよね(^^)

サンプルコードではバナー広告を1回クリックしたら3日間広告をリクエストしないようにして広告スペースにアプリのタイトル(文字列)が表示されるようにしています。

では早速、まずはレイアウトから…

※アプリ作成や広告の実装方法は理解している前提なので、アプリ全体のレイアウトやmanifest、gradle等については割愛します。


activity_main.xml

<!-- 広告枠 -->
<LinearLayout 
    android:id="@+id/ad_layout"
    android:layout_width="match_parent"
    android:layout_height="50dp">

    <com.google.android.gms.ads.AdView 
        android:id="@+id/ad_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        ads:adSize="BANNER"
        ads:adUnitId="@string/banner_id">
    </com.google.android.gms.ads.AdView>

</LinearLayout>

<!-- 広告非表示時の代替枠 -->
<LinearLayout 
    android:id="@+id/alt_layout"
    android:visibility="gone"
    android:layout_width="match_parent"
    android:layout_height="50dp">

    <TextView 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/app_name"/>

</LinearLayout>


広告を配置するための枠(LinearLayout)を用意し、Javaのコードで呼び出すためのidを設定。
高さは通常はwrap_contentにしますが、代替枠の高さを決めるためにここではバナー広告の高さである50dpにしています。

つぎにadViewにも適当なid(ad_view)を設定し、adSizeは”BANNER”にして、adUnitIdに広告IDを設定します。

そして、広告非表示時にレイアウトが変わらないようにするために同じサイズのレイアウトを用意します。
代替レイアウトはvisibilityを”gone”に設定し非表示にし、それ以外は広告のレイアウトと同じで、中身はTextViewでアプリ名を表示するようにしています。


ではJavaのコードの方を見ていきましょう。



MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import android.widget.LinearLayout;
import android.content.SharedPreferences;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
import android.view.View;
import java.util.*;

public class MainActivity extends AppCompatActivity{

    private SharedPreferences preferences;
    private SharedPreferences.Editor editor;
    private boolean firstFlag = true;
    private Calendar date; 
    private String strToday; //今日の日付を代入する変数
    private String timeStamp; //今日の日付と比較する前回保存した日付
    private LinearLayout adSpace;  //広告枠     
    private LinearLayout alternateSpace; //代替枠
    private boolean dateFlag= false;//日付比較のフラグ

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //レイアウトで設定したIDと紐づける。
        adSpace = findViewById(R.id.ad_layout);
        //代替スペースも同じく
        alternateSpace = findViewById(R.id.alt_layout);

        //今日の日付を取得して変数strTodayに代入
        date = Calendar.getInstance();
        int year = date.get(Calendar.YEAR);
        int month = date.get(Calendar.MONTH);
        int day = date.get(Calendar.DAY_OF_MONTH);
        strToday = String.format(Locale.getDefault(), 
                      "%02d / %02d / %02d", year, month + 1, day);

        //保存データを取得
        preferences = getSharedPreferences("savedValue", MODE_PRIVATE);

        //初回起動か否かをチェック。(データがなければ初期値はtrue)
        firstFlag = preferences.getBoolean("firstFlag", firstFlag);
        
        //もし初回起動ならfirstFlagをfalseにして、
        //timeStampにとりあえず今日の日付を保存しておく。
        if(firstFlag){
            editor = preferences.edit();
            editor.putBoolean( "firstFlag", false );
            editor.putString( "timeStamp", strToday );
            editor.apply();
        }
    }

    //アクティビティが復帰するたびに比較したいので処理はonResumeに書く
    @Override
    public void onResume() {
        super.onResume();

        //前回保存した日付を取得
        timeStamp = preferences.getString("timeStamp", strToday);
        try {

            //日付を比較するためのフォーマット
            @SuppressLint("SimpleDateFormat")
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy / MM / dd");
            Date today = sdf.parse(strToday);
            Date savedDay = sdf.parse(timeStamp);

            //日付を比較して
            //今日の日付が保存された日付より大きいか同じならtrue(広告表示)
            if (Objects.requireNonNull(today).getTime() >= 
                         Objects.requireNonNull(savedDay ).getTime()) {
                dateFlag = true;

            //保存された日付の方が大きければfalse(広告非表示)
            }else {
                dateFlag = false;
            }

        } catch (ParseException e) {
            e.printStackTrace();
        }

        //1回クリックされたら3日間広告を非表示にしてタイトルを表示
        //dateFlagがtrueなら広告を表示
        if (dateFlag) {

            //Adviewインスタンスにレイアウトで設定したadviewのIDを紐づけて
            AdView mAdView = findViewById(R.id.ad_view);

            //アドリスナーをセット
            mAdView.setAdListener(new AdListener() {
                
                @Override // ↓ 広告がクリックされたら呼ばれるメソッド
                public void onAdOpened() {

                    //onCreateとonResumeで日付が変わっている可能性があるので
                    //今日の日付を取得し直し3足して変数plus3に代入
                    date.add(Calendar.DAY_OF_MONTH, 3);
                    int year = date.get(Calendar.YEAR);
                    int month = date.get(Calendar.MONTH);
                    int day = date.get(Calendar.DAY_OF_MONTH);
                    String plus3 = String.format(Locale.getDefault(), 
                                "%02d / %02d / %02d", year, month + 1, day);

                    //plus3をpreferencesの"timeStamp"に保存する
                    editor = preferences.edit();
                    editor.putString("timeStamp", plus3);
                    editor.apply();

                    //広告枠と代替枠を入れ替える(注1)
                    adSpace.setVisibility(View.GONE);
                    alternateSpace.setVisibility(View.VISIBLE);
                }
            });

            //広告をリクエストして表示
            AdRequest adRequest = new AdRequest.Builder().build();
            mAdView.loadAd(adRequest);

        } else {

            //dateFlagがfalseなら広告を呼ばずに代替枠に切り替える。
            adSpace.setVisibility(View.GONE);
            alternateSpace.setVisibility(View.VISIBLE);
        }
    }
}

解説はコード内のコメントを見てください。コードエディターなどにコピペすれば見やすいと思います。


(注1)について補足しておきます。
アクティビティを終了すれば次回から広告をリクエストしないのですが、 ここで広告枠を入れ替えないと広告の遷移先からデバイスのバックボタンで戻ってきたときに広告が残っていることがあり連続クリックの防止にならないので応急的にここで広告枠を入れ替えて非表示にしています。

リクエストした広告をレイアウトで非表示にするのはちょっと微妙な感じですが、これは一度表示されクリックされた広告が残っているもので新たにリクエストしたわけではありませんし、AdMobのガイドライン「バナー広告の導入における禁止事項」にも特に記載はないのでポリシーには抵触しないと思います。…が、AdSenseではリクエストした広告をcssなどで隠す行為は禁止されているようなので、気になる場合はこのタイミングでfinish()でアクティビティを破棄してもいいかと思います。


最後に


要点を簡単にまとめますと、第三者による不正クリック対策としては、

アナリティクスなどを利用してトラフィックを監視し、 第三者による不正クリックが疑われる場合はGoogleへ報告する。
●広告を連続でクリック出来ない実装にするなど工夫をする。

あと、記事内では触れませんでしたが日頃から気を付けたほうが良さそうなこととして、

●むやみに敵を作らない(←アタリマエ)
●SNSなどからの誘導はなるべく行わない。
●純粋な検索からのユーザーが定着するように優良なコンテンツを提供するよう心掛ける。

などなど…


どんなジャンルでもそうだと思いますが、 いいものを作れば自ずと売れるのかと言えばそうではありませんし、どれだけいいものを作ったとしても知ってもらえなければ価値はありません 。
そして知ってもらうためにはそれなりに宣伝や売込みも必要になるわけですが、もしその宣伝が不正クリックなどのいたずらを招いているのだとしたら、もうどうすればいいのやらってかんじですね…


まぁいろいろ難しいですけど、サイト運営やアプリ開発自体は楽しいですからね。 いろいろ気を配りながら楽しく頑張りましょう。


以上、不正クリック対策(Androidアプリで不正クリックを予防する実装方法)を綴りました。長くなりましたがお付き合いありがとうございました。


スポンサーリンク


コメントは受け付けていません。