WebViewで「ファイルのダウンロード」(Blob形式)のデータを取得する [Android]
目次
1. 作るもの
2. 初期設定
3. 画面設計
4. リソース(/res/raw)
5. コーディング
1. 作るもの
WebViewで表示しているHTML(JavaScript)で作成したBlob形式の「ファイルのダウンロード」のデータをAndroid(Java)側に渡します。
Android側ではそのデータを元にファイルを/DownLoadに作成します。
本来はonDownloadStartイベントの第一引数の「url」で生データを取得できます。しかし、今回はurlがBlob形式なので特殊となります。
Blob形式から生データを取得するには「Blob形式のurl」をJavaScriptのAJAXで実行してBASE64形式の長い文字列を取得します。その長い文字列をAndroid(Java)側で受け取りバイナリに変換してPNGファイルをDownLoadフォルダに作成しています。
2. 初期設定
2-1. APIレベル
今回はWebViewコントロールを扱うのでAndroidの動作可能なAPIレベルは21(Android5.0/Lollipop)にしています。
設定方法はAndroid Studioで左のTreeViewにあるGradle Scriptsのbuild.gradleの「minSdkVersionを21」にします。
2-2. 権限
[AndroidManifest.xml]
ファイルを扱うので次のストレージ権限を追加します。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3. 画面設計
WebViewを1つ配置します。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@+id/webView"
android:layout_width="368dp"
android:layout_height="447dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
4. リソース(/res/raw)
今回のリソースはindex.htmlとtest.jpgです。両方ともresフォルダの中にrawフォルダを作成してその中に入れてください。尚、プロジェクトにrawのリソースを追加する方法はWebViewの基本操作をご覧下さい。
[index.html]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script>
// キャンバス
var src_canvas;
var src_ctx;
window.onload = function(){
// DOMを変数に格納するとアクセスが早くなる
src_canvas = document.getElementById("SrcCanvas");
src_ctx = src_canvas.getContext("2d");
var image = document.getElementById("img_source");
// 画像の設定
image.src = "test.jpg";
image.onload = function (){
src_canvas.width = image.width;
src_canvas.height = image.height;
// キャンバスに画像を描画
src_ctx.drawImage(image,0,0);
}
image.onerror = function (){
alert('この画像は読み込めません。');
};
}
// 画像をBlob形式でダウンロード
function run(){
var F = new TFileStream();
var png = src_canvas.toDataURL().replace("data:image/png;base64,","");
png = window.atob(png);
F.WriteString(png);
F.SaveToFile('dummy.png','image/png');
}
////////////////////////////////////////////////////////////////////////////////
// 汎用クラス
////////////////////////////////////////////////////////////////////////////////
// ---------------------
// TFileStream
// ---------------------
function TFileStream(BufferSize) {
if (BufferSize == undefined)
this.MemorySize = 5000000; // 5M
else
this.MemorySize = parseInt(BufferSize, 10);
this.Size = 0;
this.Stream = new Uint8Array(this.MemorySize);
}
// ---------------------
// TFileStream.Method
// ---------------------
TFileStream.prototype = {
_AsciiToUint8Array: function (S) {
var len = S.length;
var P = new Uint8Array(len);
for (var i = 0; i < len; i++) {
P[i] = S[i].charCodeAt(0);
}
return P;
},
WriteByte: function (value) {
var P = new Uint8Array(1);
P[0] = value;
this.WriteStream(P);
},
WriteWord: function (value) {
var P = new Uint8Array(2);
P[1] = (value & 0xFF00) >> 8;
P[0] = (value & 0x00FF);
this.WriteStream(P);
},
WriteDWord: function (value) {
var P = new Uint8Array(4);
P[3] = (value & 0xFF000000) >> 24;
P[2] = (value & 0x00FF0000) >> 16;
P[1] = (value & 0x0000FF00) >> 8;
P[0] = (value & 0x000000FF);
this.WriteStream(P);
},
WriteWord_Big: function (value) {
var P = new Uint8Array(2);
P[1] = (value & 0x00FF);
P[0] = (value & 0xFF00) >> 8;
this.WriteStream(P);
},
WriteDWord_Big: function (value) {
var P = new Uint8Array(4);
P[3] = (value & 0x000000FF)
P[2] = (value & 0x0000FF00) >> 8;
P[1] = (value & 0x00FF0000) >> 16;
P[0] = (value & 0xFF000000) >> 24;;
this.WriteStream(P);
},
WriteString: function (S) {
var P = this._AsciiToUint8Array(S);
// メモリの再編成
if (this.Stream.length <= (this.Size + P.length)) {
var B = new Uint8Array(this.Stream);
this.Stream = new Uint8Array(this.Size + P.length + this.MemorySize);
this.Stream.set(B.subarray(0, B.length));
}
this.Stream.set(P, this.Size);
this.Size = this.Size + P.length;
},
WriteStream: function (AStream) {
// メモリの再編成
if (this.Stream.length <= (this.Size + AStream.length)) {
var B = new Uint8Array(this.Stream);
this.Stream = new Uint8Array(this.Size + AStream.length + this.MemorySize);
this.Stream.set(B.subarray(0, B.length));
}
this.Stream.set(AStream, this.Size);
this.Size = this.Size + AStream.length;
},
getFileSize: function () {
return this.Size;
},
SaveToFile: function (FileName,type) {
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(new Blob([this.Stream.subarray(0, this.Size)], { type: type }), FileName);
} else {
var a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([this.Stream.subarray(0, this.Size)], { type: type }));
//a.target = '_blank';
a.download = FileName;
document.body.appendChild(a); // FF specification
a.click();
document.body.removeChild(a); // FF specification
}
},
}
</script>
</head>
<body>
<br>
<input type="button" onclick="run();" value="実行する"><br>
<br>
<canvas id="SrcCanvas" ></canvas>
<img id="img_source" style="display:none;">
</body>
</html>
[test.jpg]
各自で用意してください。
5. コーディング
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.webkit.DownloadListener;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Date;
import java.text.SimpleDateFormat;
public class MainActivity extends AppCompatActivity {
////////////////////////////////////////////////////////////////////////////////////////////////////
// 汎用メソッド
////////////////////////////////////////////////////////////////////////////////////////////////////
// パーミッションダイアログ
public boolean CheckPermission(Activity actibity, String permission, int requestCode){
// 権限の確認
if (ActivityCompat.checkSelfPermission(actibity, permission) !=
PackageManager.PERMISSION_GRANTED) {
// 権限の許可を求めるダイアログを表示する
ActivityCompat.requestPermissions(actibity, new String[]{permission},requestCode);
return false;
}
return true;
}
// リソースからファイルを生成する(/data/data/パッケージ名/files/)
public boolean setRawResources(Context context , int resourcesID, String fileName){
boolean result = false;
// リソースの読み込み
InputStream is = context.getResources().openRawResource(resourcesID);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte [] buffer = new byte[1024];
try{
// 1024バイト毎、ファイルを読み込む
while(true) {
int len = is.read(buffer);
if(len < 0) break;
baos.write(buffer, 0, len);
}
}catch (Exception e){
e.printStackTrace();
return result;
}
// ファイルの生成
File file = new File(context.getFilesDir() + "/" + fileName);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
result = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fos != null){
try{
fos.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
return result;
}
// 現在日時の取得
public String getSysdate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
return sdf.format(new Date(System.currentTimeMillis()));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// 内部用クラス
////////////////////////////////////////////////////////////////////////////////////////////////////
// JavaScriptInterface
// ※JavaScriptからこのクラスのメソッドを呼び出す
private class JavaScriptInterface {
@JavascriptInterface
public void base64ToFile(String base64) {
// Downloadフォルダへファイルを作成する
String fullPath = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS) + "/" + getSysdate() + ".png";
final File file = new File(fullPath);
// Base64の長い文字列をバイナリに変換して保存する
byte[] pByteArray = Base64.decode(base64.replaceFirst("^data:image/png;base64,",
""), 0);
FileOutputStream fos;
try {
fos = new FileOutputStream(fullPath, false);
fos.write(pByteArray);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
// Androidにファイルを認識させる
MediaScannerConnection.scanFile(MainActivity.this, new String[]{fullPath},
null, null);
Toast.makeText(MainActivity.this,"DownloadフォルダにPNGファイルを生成しました。",
Toast.LENGTH_SHORT).show();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// イベント
////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ***************
// 権限設定
// ***************
// ストレージの権限確認
CheckPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
1000);
// ***************
// リソース
// ***************
// リソースからファイルを生成する(/data/data/パッケージ名/files/に作成)
setRawResources(MainActivity.this, R.raw.index, "index.html");
setRawResources(MainActivity.this, R.raw.test, "test.jpg");
// ***************
// WebView
// ***************
final WebView webView = findViewById(R.id.webView);
// キャッシュクリア
// ※開発時のみ有効にする
webView.clearCache(true);
// JavaScriptを有効にする
webView.getSettings().setJavaScriptEnabled(true);
// JavaScriptInterfaceの初期設定
webView.addJavascriptInterface(new JavaScriptInterface(), "android");
// test.jpgを読み込めるようにする
// ※コレを設定しないと次のエラーになります。
// ※Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported
webView.getSettings().setAllowFileAccessFromFileURLs(true);
// ダウンロードイベントをオーバーライドする
// ※JavaScriptのrun()で作成したファイルのダウンロード
webView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition,
String mimeType, long contentLength) {
// 第一引数の「url」は「BlobのURL」です。
// AJAXで「BlobのURL」(blob:file:///xxx形式)からデータを読み込み、
// base64データをAndroid側のbase64ToFileメソッドに渡しています。
String js =
"javascript:" +
" var xhr = new XMLHttpRequest();" +
" xhr.open('GET', '" + url + "', true);" +
" xhr.setRequestHeader('Content-type','image/png');" +
" xhr.responseType = 'blob';" +
" xhr.onload = function(e) {" +
" if (this.status == 200) {" +
" var blobUrl = this.response;" +
" var reader = new FileReader();" +
" reader.readAsDataURL(blobUrl);" +
" reader.onloadend = function() {" +
" base64data = reader.result;" +
" android.base64ToFile(base64data);" +
" }" +
" }" +
"};" +
"xhr.send();";
// 上記のJavaScriptを実行する
webView.loadUrl(js);
}
});
// ファイルを読み込む
webView.loadUrl("file:///" + MainActivity.this.getFilesDir() + "/index.html");
}
}
今回のコードは次の参考URLを参考にしています。感謝です。
参考URL
Download Blob file from Website inside Android WebViewClient
関連記事
| 前の記事: | WebViewの<input type="file">でカメラ、画像からデータを取得する [Android] |
| 次の記事: | WebViewの<input type="file" multiple>でファイルを複数選択する [Android] |
プチモンテ ※この記事を書いた人
![]() | |
![]() | 💻 ITスキル・経験 サーバー構築からWebアプリケーション開発。IoTをはじめとする電子工作、ロボット、人工知能やスマホ/OSアプリまで分野問わず経験。 画像処理/音声処理/アニメーション、3Dゲーム、会計ソフト、PDF作成/編集、逆アセンブラ、EXE/DLLファイルの書き換えなどのアプリを公開。詳しくは自己紹介へ |
| 🎵 音楽制作 BGMは楽器(音源)さえあれば、何でも制作可能。歌モノは主にロック、バラード、ポップスを制作。歌詞は抒情詩、抒情的な楽曲が多い。楽曲制作は🔰2023年12月中旬 ~ | |









