ホーム > カテゴリ > Java・Android >

WebViewで「ファイルのダウンロード」(Blob形式)のデータを取得する [Android]

Android Studioの使い方(目次)

目次

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





関連記事



公開日:2018年06月04日
記事NO:02675