お使いのブラウザは、バージョンが古すぎます。

このサイトは、Internet Explore8・Internet Explore9には対応しておりません。
恐れ入りますが、お使いのブラウザをバージョンアップしていただきますよう宜しくお願いいたします。

【Android】URLからウェブページをキャプチャしようとしたけど…

こんにちわ。pencoです。
タイトル、回りくどい書き方をしてしまいましたかね?
端的にいうと「WebViewをキャプチャする」ですね。はい。
 
まぁ、なにがしたかったかというと、ブラウザのメニューから「共有」で自分のアプリを選択し、URLと画面のキャプチャが取れたらいいなーと思って始めました。
PCでいうショートカット的なものが作りたかったのですが。
 
WebViewをキャプチャするで調べると、WebViewを使ってる最中(表示中)にキャプチャしている例はよくみるのですが、バックグラウンドで取得してる例ってのが見つからなかったんですね。
 
で、じゃあバックグラウンドで取得できたのか?
結論をいうと出来ませんでした。
ビューとして表示しないとキャプチャ取得できませんでした!!!
 
悲しいけれど、これ、現状なのよね。。。
私が至らないだけで、本当は方法あります!とかだったら大変申し訳ないのですが、悪あがきした過程を一応書きたいと思います。
 
 
まずはブラウザの「共有」を選択した時に飛んでくるIntentをキャッチできるようにします。AndroidManifest.xml の Intentを受ける Activity に intent-filterを設定します。URLは mimeTypeが text/plainで飛んできますので、textが受けられるよう設定します。

<activity android:name=".WizActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/*"/>
    </intent-filter>
</activity>

これで、他のアプリが action.SENDの Intentを発行した際に、受け入れ先のアプリ一覧に自分のアプリが表示されるようになります。今回はブラウザの「共有」が選択された時に出てくるアプリ一覧に自分のアプリがあればOKです。
 
ちなみに、この受け入れるdataのタイプを増やしたければ、dataタグを追加していけば良いですし、逆に限定したければ、上記のように*(ワイルドカード)は使わず、text/plainのように直接指定します。
 
 
では次に、Intentを受けた後の処理です。

Intent i = getIntent();
if(i != null && Intent.ACTION_SEND.equals(i.getAction())){
    if(i.getType().equals("text/plain")){
        String url = i.getExtras().getCharSequence(Intent.EXTRA_TEXT).toString();
        //ここにWebViewをキャプチャする処理を記述
    }
}

受けた Intentの Actionと Typeを判断し、URLを取得します。
このURLのページをキャプチャします。
 
 
ここからが本題の WebViewをキャプチャする処理です。
どのようなことをするかというと、

  1.  WebViewを作って取得したURLを読み込む
  2.  WebViewの読み込みが完了した所をキャプチャする

という流れになります。
 
 
ではWebViewを作るところから。

web = new WebView(this);
web.getSettings().setJavaScriptEnabled(true);

//--- ここがないとキャプチャを取得できない ---
wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        WindowManager.LayoutParams.MATCH_PARENT,
        WindowManager.LayoutParams.MATCH_PARENT,
        WindowManager.LayoutParams.TYPE_APPLICATION,
        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
        PixelFormat.TRANSLUCENT);
wm.addView(web, params);
//--- ここまで ---

web.loadUrl(url);

ポイントはWebViewはViewとして描画しなければキャプチャが出来ないという点です。
 
上の例でいうと、コメントで囲った部分がないとこれから説明するキャプチャの取得ができません。正確にいうなら、取得できるが真っ黒いキャプチャ画像になります。
setContentView()にしろ addView()にしろ、作った WebViewが描画されるようにしなければいけません。

今回は WindowManagerの APPLICATIONレイヤーに WebViewを追加しています。
もちろん支障がなければ Intentを受けた Activityのビューに追加しても構いませんが、キャプチャを取得したら消すビューなので、WindowManagerを使用しました。そういった面ではダイアログでもいいのかもしれません。
 
 
次に、WebViewの読み込みが完了した所をキャプチャするところです。
先程のコードに以下を追加します。

final String path = Environment.getExternalStorageDirectory().toString() + "/wiztemp/"+title+"/thumb.jpg";
web = new WebView(this){
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        try {
		    if(web.getContentHeight() != 0){
			    FileOutputStream fos = null;
                fos = new FileOutputStream(path);
                if(fos != null){
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos );
                    fos.close();
                }
			}    	
        }catch(Exception e){
            e.printStackTrace();
        }
    }
};

web.setWebViewClient(new WebViewClient(){
    public void onPageFinished(WebView view, String url){
        bitmap = Bitmap.createBitmap(view.getWidth(), view.getWidth(), Bitmap.Config.ARGB_8888);			
        Canvas c = new Canvas(bitmap);
	    view.draw(c);
    }
});

 
わ…わかんないですよね。すいません。解説します。
 
まず、読み込みが完了したという状態を取得するため、WebViewClientを WebViewに登録します。
ページの読み込み完了時には onPageFinished()が呼ばれるので、そこでキャプチャを取得するための処理をしています。
 
次に、WebViewをキャプチャする方法ですが、WebViewクラスの capturePicture()というメソッドがありましたが、APIレベル19で非推奨になってしまいました。代わりに onDraw()を使うようです。
onDraw()は WebViewクラスの draw()で呼び出します。
必ず superも呼んで下さい。
 
draw()には Canvasが引数として必要ですので、Bitmapを1つ作って、そこから Canvasを生成します。draw()を呼ぶことで、Canvasに WebViewがレンダリングされるため、最終的に Canvasの元になった Bitmapに WebViewが画像としてキャプチャできます。
 
onDraw()ではその Bitmapを JPEGとしてファイルに書き出す処理をしています。
ただし、WebViewを作った段階でも onDraw()は呼ばれるので、HTMLコンテンツの高さが0でない場合という条件をつけています。

 
 
ここまででも十分なのですが、もう少しお付き合いください。
 
先程「ページの読み込み完了時にはの onPageFinished()が呼ばれる」と申しましたが、これは HTMLの読み込みが完了した時のようです。
どうやら javascriptなどで表示している部分の読み込みは待ってくれないようです。
そのため、キャプチャ取得時にまだページが白い…なんてことにもなります。
私はそれがすごーーーく嫌でしたので、完璧とはいかないまでも、もう少し悪あがきしてみました。

//キャプチャを取る前に変数初期化
int count = 0;
int display_h = 0;
Display display = getWindowManager().getDefaultDisplay();
Point p = new Point();
display.getSize(p);
display_h = p.y;

web.setWebViewClient(new WebViewClient(){
    public void onPageFinished(WebView view, String url){
        if(view.getContentHeight() == 0){
		    count++;
			view.reload();
		}
    }
});

web.setWebChromeClient(new WebChromeClient() {
	@Override
	public void onProgressChanged(WebView view, int progress) {
		if(progress == 100){
			if((count == 0) && (view.getContentHeight() < display_h)){
				count++;
				view.reload();
			}else{
				bitmap = Bitmap.createBitmap(view.getWidth(), view.getWidth(), Bitmap.Config.ARGB_8888);			
				Canvas c = new Canvas(bitmap);
				view.draw(c);
		    }
		}
	}
});

 
WebChromeClient サブクラスはブラウザのUIに影響を与える可能性のある処理が起こったときに呼ばれるクラスだそうです。例えばアップデートの進捗など。
色々試した結果、WebChromeClient の onProgressChanged() は onPageFinished() が呼ばれた後も呼ばれていたため、この progressが100になった時にキャプチャを取得するように変更しました。
 
また、先に説明した理由でページが白いキャプチャが取得されるのを防ぐため、HTMLコンテンツの高さが希望する高さ未満の場合は一度リロードをかけることにしました。
これで、勝率80%くらいまでは上がったかな?という感じです。
 
 
コードまとめますね。

Intent i = getIntent();
if(i != null && Intent.ACTION_SEND.equals(i.getAction())){
    if(i.getType().equals("text/plain")){
        String url = i.getExtras().getCharSequence(Intent.EXTRA_TEXT).toString();
        //ここにWebViewをキャプチャする処理を記述
		createThumbnailImage(url);
    }
}

public void createThumbnailImage(String url){
    final String path = Environment.getExternalStorageDirectory().toString() + "/wiztemp/"+title+"/thumb.jpg";
	int count = 0;
	int display_h = 0;
	Display display = getWindowManager().getDefaultDisplay();
	Point p = new Point();
	display.getSize(p);
	display_h = p.y;

    web = new WebView(this){
		@Override
		protected void onDraw(Canvas canvas) {
			super.onDraw(canvas);
			try {
				if(web.getContentHeight() != 0){
					FileOutputStream fos = null;
					fos = new FileOutputStream(path);
					if(fos != null){
						bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos );
						fos.close();
					}
				}		
			}catch(Exception e){
				e.printStackTrace();
			}
		}
    };
	
	web.getSettings().setJavaScriptEnabled(true);
	web.setWebViewClient(new WebViewClient(){
		public void onPageFinished(WebView view, String url){
			if((count == 0) && (view.getContentHeight() == 0)){
				count++;
				view.reload();
			}
		}
	});
	
	web.setWebChromeClient(new WebChromeClient() {
		@Override
		public void onProgressChanged(WebView view, int progress) {
			if(progress == 100){
				if((count == 0) && (view.getContentHeight() < display_h)){
					count++;
					view.reload();
				}else{
					bitmap = Bitmap.createBitmap(view.getWidth(), view.getWidth(), Bitmap.Config.ARGB_8888);			
					Canvas c = new Canvas(bitmap);
					view.draw(c);
				}
			}
		}
	});
	
	wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
	WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        WindowManager.LayoutParams.MATCH_PARENT,
        WindowManager.LayoutParams.MATCH_PARENT,
        WindowManager.LayoutParams.TYPE_APPLICATION,
        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
        PixelFormat.TRANSLUCENT);
	wm.addView(web, params);
	
	web.loadUrl(url);
}

結論としては、
白いページがキャプチャされるのを完全に避けられるわけではないこと、
通信環境やウェブページの如何によっては、読み込みが完了するまでに時間がかかること、などの理由からバックグラウンドでの処理は諦めました。
リロードボタンを出して、ユーザーに手動でリロードかけてもらう方法で現在検討中です。
 
 
本当に悪あがきな記事で申し訳ありません。部分的にでも参考になれば幸いです。
閲覧ありがとうございました。
 
 
参考にさせて頂きました。
http://y-anz-m.blogspot.jp/2010/07/androidwebview.html

コメントをどうぞ

メールアドレスは公開されません。* が付いている欄は必須項目です。


お気軽にお問い合わせください。

日本VTR実験室では、お仕事のご依頼、ブログ・コラムのご感想などを受け付けております。
アプリ開発・コンテンツ制作でお困りでしたら、お気軽にご相談ください。
ご連絡お待ちしております。

お問い合わせはこちらから

03-3541-1230

info@nvtrlab.jp

電話受付対応時間:平日AM9:30〜PM6:00