[Android]WebViewにAdblockを実装する

# WebViewの広告を消したい
javascriptをオフにすれば大抵の広告は消えるけど、javascriptが使えないと何かと不便なので、adblockを実装したときのメモです。英語ではそれなりに資料が合ったのですが、日本語のページが見つからなかったので。

## 参考
このページを追えば実装できます
https://www.hidroh.com/2016/05/19/hacking-up-ad-blocker-android/

広告のホスト一覧
~~https://sites.google.com/site/hosts2ch/~~
↑無くなっていたので↓を使わせていただくのが良さそうです。
https://qiita.com/AKKYM/items/9772884e925c0647adb7

## だいたいの流れ
WebViewClient.shouldInterceptRequest()をオーバーライドして、ページ内からのリクエストURLが広告配信ホストかどうかを判定する。
もし広告の場合は空のレスポンスを返す。
以上。

## 実装
### 広告ホスト一覧を扱いやすいように成形する
https://qiita.com/AKKYM/items/9772884e925c0647adb7 で、広告を配信しているホストの一覧がダウンロードできます。(めっちゃ便利..!!)
ダウンロードするとこんな感じになってると思うので、扱いやすいように成形します。


```ja.txt
# hosts2ch - Japanese mobile ad servers hosts file.
# This file is public domain, of course.
#
# file size: 34 kB
# entries: 1279
#
# ---------- last updated: 2019-01-16  ---------- 


127.0.0.1 100234.advision-adnw.jp
127.0.0.1 100291.adnico.jp
127.0.0.1 100602.advision-adnw.jp
127.0.0.1 100651.advision-adnw.jp
127.0.0.1 100800.advision-adnw.jp
127.0.0.1 101623.sprout-ad.com
127.0.0.1 107008.speead.jp
127.0.0.1 150601.fc2rs.com
127.0.0.1 1tmg.info
127.0.0.1 2.chmato.me
・
・
・
```

これのコメント行とIPアドレスの列を削除してホストだけにし、ファイル名をhosts.txtにします。
こんな感じ↓

```hosts.txt
100234.advision-adnw.jp
100291.adnico.jp
100602.advision-adnw.jp
100651.advision-adnw.jp
100800.advision-adnw.jp
101623.sprout-ad.com
107008.speead.jp
150601.fc2rs.com
1tmg.info
2.chmato.me
・
・
・
```

### hosts.txtをプロジェクトファイルに追加する
assetsフォルダに追加します。もしassetsフォルダがない場合は作ってください。

<img width="353" alt="スクリーンショット 2019-01-22 15.57.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/324552/1a613a88-0e82-2474-8574-0a1f67ed77bb.png">

### AdBlockを実装する

```AdBlocker.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);
        }
        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()));
    }
}
```

### WebViewClient.shouldInterceptRequest()を実装する
WebViewClientのshouldInterceptRequest()メソッドをオーバーライドして実装します。
webviewで読み込んだソースからのリクエストは全て一度shouldInterceptRequest()を通る(という認識なのですが合ってるのでしょうか)


```MyWebViewClient.java
private Map<String, Boolean> loadedUrls = new HashMap<>();

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@override99 
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        boolean ad;
        if (!loadedUrls.containsKey(url)) {
            ad = AdBlocker.isAd(url);
            loadedUrls.put(url, ad);
        } else {
            ad = mLoadedUrls.get(url);
        }
        return ad ? AdBlocker.createEmptyResource() :
                super.shouldInterceptRequest(view, url);
    }
```

### Applicationを実装する
Applicationを継承したMyApplicationの中でAdBlockerを初期化します。

```MyApplication.java
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        AdBlocker.init(this);
    }
}
```

AndroidManifestにも忘れず追加しましょう。

```AndroidManifest.xml
<application
        android:name="MyApplication"
        ....
        ....
/>
```

## 以上
https://www.hidroh.com/2016/05/19/hacking-up-ad-blocker-android/ ほとんどこのページにある通りなので、詳細はこちらを参考にされるといいと思います。