Android WebViewの広告表示を制限する


概要

WebViewでロードしたページの広告表示を制限する

Hacking up an ad blocker for Android | Ha Duy Trung’s Blog こちらのページを参考に実装を行った。


ミュートする広告配信ホスト一覧の取得

https://sites.google.com/site/hosts2ch/ja より日本国内向けの広告配信事業者のホスト一覧が取得できる。

「ja」というファイル名になるので、hosts.txtというようにリネームを行っておく。

リネームを行ったhosts.txtファイルをAndroidプロジェクトのassetsフォルダに配置する。


okhttpの導入

後述するAdBlockerクラスにokhttpに梱包されているライブラリ(okio)を使用するのでokhttpを導入しておく。

app/build.gradle

implementation("com.squareup.okhttp3:okhttp:4.2.0")


AdBlockerの実装

ほぼ、上記の参考サイト通りの実装で良い。Java -> Kotlin書き直すのが面倒なので、Javaのままで取り込んだ。

public class AdBlocker {
    private static final String AD_HOSTS_FILE = "hosts.txt";
    private static final Set<String> AD_HOSTS = new HashSet<>();

    public static void init(final Context context) {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    loadFromAssets(context);
                } catch (IOException e) {
                    // noop
                }
                return null;
            }
        }.execute();
    }

    @WorkerThread
    private static void loadFromAssets(Context context) throws IOException {
        InputStream stream = context.getAssets().open(AD_HOSTS_FILE);
        BufferedSource buffer = Okio.buffer(Okio.source(stream));
        String line;
        while ((line = buffer.readUtf8Line()) != null) {
            AD_HOSTS.add(line.replace("127.0.0.1 ", ""));
        }
        buffer.close();
        stream.close();
    }

    public static boolean isAd(String url) {
        HttpUrl httpUrl = HttpUrl.parse(url);
        return isAdHost(httpUrl != null ? httpUrl.host() : "");
    }

    private static boolean isAdHost(String host) {
        if (TextUtils.isEmpty(host)) {
            return false;
        }
        int index = host.indexOf(".");
        return index >= 0 && (AD_HOSTS.contains(host) ||
                index + 1 < host.length() && isAdHost(host.substring(index + 1)));
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static WebResourceResponse createEmptyResource() {
        return new WebResourceResponse("text/plain", "utf-8", new ByteArrayInputStream("".getBytes()));
    }
}

参考サイトとの違いは

AD_HOSTS.add(line.replace("127.0.0.1 ", ""));

この部分のみ。

hosts.txtにミュートするドメインの前にローカルループバックアドレスが記載されているので、ループバックアドレスを除外したホスト名のみを広告配信ホストの一覧として取り扱う。

ざっと説明すると、init()でhosts.txtを読み込み、AD_HOSTSに広告配信を行うホスト一覧を展開する。

isAd()で与えられたurlが広告を配信するホストであるかを判定する。


AdBlockerを初期化する

多分、WebViewを使用する各Activityでも大丈夫だと思うんだけど、参考サイトにしたがってApplicationクラスで行うようにした。

Applicationを継承した`MyApplicationクラスを作成する。すでにApplicationを継承したクラスが存在する場合は、onCreateメソッドに追記を行う。

class MyApplication: Application(){

    override fun onCreate() {
        super.onCreate()
        AdBlocker.init(this)
    }
}

AndroidManifest.xml

<application
    android:name="MyApplication"
    ....


WebViewのセットアップ

loadUrlしたページがページ上でリクエストしたURLをキャプチャし、そのURLが広告配信ホストであるかを行う判定を行う。

ここは参考サイトのコードを少し改変した。

webView.apply {
    webViewClient = object: WebViewClient(){
        override fun shouldInterceptRequest( view: WebView?, request: WebResourceRequest? ): WebResourceResponse? {
            var ad: Boolean = false
            request?.let {
                ad = AdBlocker.isAd(it.url.toString())
            }
            return if (ad) {
                AdBlocker.createEmptyResource()
            }else {
                super.shouldInterceptRequest(view, request)
            }
        }
}

先ず、参考サイト上だと名前解決できないmloadedUrlsという配列が現れるが、これは多分loadedUrlsのタイポだと思う。

オーバライドしたshouldInterceptRequestは参考サイト上だとshouldInterceptRequest( view: WebView?, url: String? )にしていたが、shouldInterceptRequest( view: WebView?, request: WebResourceRequest? )にてWebViewからのリクエストをキャプチャするようにした。

loadedUrlsはそのWebView上で一度、広告配信ホストと判断したホストをキャッシングする配列のようだが、そもそもAdBlocker.initで広告配信ホスト一覧をメモリに展開しているので不要だと判断した。一覧のレコード数は1,300件ほどあるが、パフォーマンスに影響を与える件数ではないと判断した。


このセットアップを行ったWebViewでloadUrlを行うとhosts.txt内に記載されたホストからの広告をブロックできる。

その他参考にしたページ

[Android]WebViewにAdblockを実装する - Qiita