AndroidのWebViewでHTMLソースを取得する

パッケージJava製品開発担当の大です。こんにちは。

最近、趣味でAndroidアプリの開発をやっています。
自分で使っている携帯端末で、自分の作ったアプリを手軽に動かせるというのは、率直に言ってとても楽しいですね。学生のころに、Z80のマイコンボードに打ち込んだプログラムが動いて、はじめてLEDが点灯したときのような感動があります。

さて、Androidに標準であるWebViewクラスを使用してウェブアクセスを行うアプリを作成していて、ちょっと困ったことがありました。WebViewクラスを使用してウェブにアクセスするのはとても簡単なのですが、アクセスしているウェブページのHTMLソースを取得することが出来ないのです。タイトルとかURLとかは取得できるんですけどね。そこで、HTMLソースを取得する方法を考えてみました。

方法1: iframeを使う

思いついたひとつめの方法は、iframeを持つHTMLファイルを用意して、iframe内にターゲットのページを読み込むというものです。こんな単純なHTMLです。

<html>
<head>
<script type="text/javascript">
function load(src, callback) {
  var myframe = document.getElementById("myframe");
  myframe.onload = function() {
    window.activity[callback](myframe.contentDocument.documentElement.outerHTML);
  }
  myframe.src = src;
}
</script>
</head>
<body style="padding: 0px; margin: 0px">
<iframe id="myframe" src="" style="padding: 0px; margin: 0px; width:100%; height:100%">
</iframe>
</body>
</html>

ここで定義しているload関数は、引数で指定されたsrciframeで読み込む(9行目)のですが、読み込む前にiframeonloadで細工をしています。onloadの中身を見る前に、このHTMLをロードする側を見てみましょう。

今回は、こんな適当な画面を用意しました。入力されたURLをボタンクリックでWebViewに表示、下のTextViewにそのHTMLソースを表示します。

画面の設計

画面の設計(クリックで拡大)

Activityはこんな感じです。

public class TestActivity extends Activity implements OnClickListener {
	private Handler handler = new Handler();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        WebView web = (WebView) findViewById(R.id.webView1);
        web.getSettings().setJavaScriptEnabled(true);
        web.getSettings().setBuiltInZoomControls(true);
        web.loadUrl("file:///android_asset/iframe.html");
        web.addJavascriptInterface(this, "activity");
        
        Button button = (Button) findViewById(R.id.button1);
        button.setOnClickListener(this);
    }

	@Override
	public void onClick(View v) {
		EditText text = (EditText) findViewById(R.id.editText1);
		String url = text.getText().toString();
        WebView web = (WebView) findViewById(R.id.webView1);
		web.loadUrl("javascript:load('" + url + "', 'viewSource')");
	}
	
	public void viewSource(final String src) {
		handler.post(new Runnable() {
			@Override
			public void run() {
				TextView text = (TextView) findViewById(R.id.textView1);
				text.setText(src);
			}
		});
	}
}

12行目でローカルのassetsフォルダに置いたHTMLを読み込みます。また、JavaScriptとの橋渡しをするインタフェースとして、このオブジェクトを「activity」という名前で登録しています(この名前はなんでもいいみたいです)。HTML側7行目の「window.activity」はこれのことです。

ボタンのonClickで、定義したJavaScriptのload関数を呼び出します(24行目)。第二引数に、viewSourceというのを指定していますね。これが、JavaScript側でコールバックとして呼び出されます。

あとは、viewSourceiframe内のHTMLが引数で渡されてきますので、加工するなりスクレイピングするなり好きにすればいいと思います。ここではTextViewに流し込んでいます(描画スレッドではないので扱いには注意が必要です)。

実行結果

実行結果(クリックで拡大)

方法2: WebViewClientを使う

ふたつめの方法は、WebViewClientを使用するものです。こちらの場合は特にローカルのHTMLを用意したりする必要はありません。Activityのソースは、方法1のものとほとんど同じです。

public class TestActivity extends Activity implements OnClickListener {
	private Handler handler = new Handler();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        WebView web = (WebView) findViewById(R.id.webView1);
        web.getSettings().setJavaScriptEnabled(true);
        web.getSettings().setBuiltInZoomControls(true);
        web.setWebViewClient(new ViewSourceClient());
        web.addJavascriptInterface(this, "activity");
        
        Button button = (Button) findViewById(R.id.button1);
        button.setOnClickListener(this);
    }

	@Override
	public void onClick(View v) {
		EditText text = (EditText) findViewById(R.id.editText1);
		String url = text.getText().toString();
        WebView web = (WebView) findViewById(R.id.webView1);
		web.loadUrl(url);
	}
	
	public void viewSource(final String src) {
		handler.post(new Runnable() {
			@Override
			public void run() {
				TextView text = (TextView) findViewById(R.id.textView1);
				text.setText(src);
			}
		});
	}

	private static class ViewSourceClient extends WebViewClient {
		@Override
		public void onPageFinished(WebView view, String url) {
			view.loadUrl("javascript:window.activity.viewSource(document.documentElement.outerHTML);");
		}
	}
}

WebViewClientのサブクラスとして、ここではViewSourceClientというクラスを定義しています(37行目~)。12行目でこのクラスのインスタンスをWebViewに登録しています。ボタンのonClickでは、方法1のようにJavaScriptの関数を呼び出すのではなく、普通にloadUrlで指定されたURLをロードします(24行目)。ページが終了するとViewSourceClientonPageFinishedが呼び出されます。その中で、ActivityviewSourceメソッドを呼び出しています。

最後に

ほかにも、実現する方法はあるかもしれません。色々試してみると面白いですね。