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

WebViewの<input type="file">でカメラ、画像からデータを取得する [Android]

Android Studioの使い方(目次)

目次

1. 作るもの
2. 初期設定
3. 画面設計
4. HTMLファイル
5. コーディング

1. 作るもの

WebViewで表示するHTMLの<input type="file">で「カメラ or 画像ファイル」の選択ダイアログを表示します。

何も設定しない初期状態の<input type="file">ではカメラ撮影や画像ファイルを選択できません。それ所か、ボタンを押しても何も動作しません。

それで、ネットで色々探して、後述する参考URLのGoogleサイトにあるコードがありましたが、次のようになっていました。

Android5(実機)は正常に動作。 Android7(実機)はカメラの写真の画像が取れない事が多い。Android8では完全にカメラの写真の画像が取れない。

という状況でしたが元コードを改変してAndroid5/7/8で動作確認しました。

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. HTMLファイル

HTMLをリソースに追加するにはWebViewの基本操作をご覧下さい。

[index.html]

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <script>
    
function onAddFile(event) {
  var reader = new FileReader();
  var files = event.target.files;
  var image = document.getElementById("img_source");
  
  reader.onload = function (event) {   
     
    image.onload = function (){
      document.getElementById("msg").innerHTML =
        image.width+"x" +image.height;        
    };      
       
    image.onerror  = function (){           
      alert('この画像は読み込めません。');  
    };

    image.src = reader.result;       
  };
  
  reader.onerror = function (){  
    alert('このファイルは読み込めません。');  
  }
  
  if (files[0]){    
    reader.readAsDataURL(files[0]);
  }
}   

  </script>
  </head>
  <body>
    <br>
    <input type="file" id="inputfile" accept="image/*"  onchange="onAddFile(event);"><br>
    <br>
    <div id="msg"></div>
    <img id="img_source">
  </body>
</html>

5. コーディング

import android.Manifest;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    // URIリスト(テンポラリ)
    private ArrayList<Uri> UriList = new ArrayList<>();
    // INIファイルのアクセス用
    private SharedPreferences.Editor editor;

    // カメラ & 画像選択ダイアログ用
    public static final int INPUT_FILE_REQUEST_CODE = 1;
    private ValueCallback<Uri[]> mFilePathCallback;
    private String mCameraPhotoPath;

////////////////////////////////////////////////////////////////////////////////////////////////////
// 汎用メソッド
////////////////////////////////////////////////////////////////////////////////////////////////////

    // パーミッションダイアログ
    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;
    }

////////////////////////////////////////////////////////////////////////////////////////////////////
// イベント
////////////////////////////////////////////////////////////////////////////////////////////////////
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ***************
        //  INI設定
        // ***************
        // INIファイルのアクセス用
        // ※「カメラ or 画像」の選択画面を表示する度に
        // ※Picturesフォルダに0バイトのファイルが作成されるので,それを削除する為にです。
        SharedPreferences prefs = getSharedPreferences("MyINI", MODE_PRIVATE);
        editor = prefs.edit();

        // INIファイルからURIリストの情報を読み込む
        int count = prefs.getInt("count", 0);
        for (int i = 0; i < count; i++) {
            try {
                String str = prefs.getString("uri" + i, "");
                UriList.add(Uri.parse(str));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // ***************
        //  権限設定
        // ***************
        // ストレージの権限確認
        CheckPermission(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                1000);

        // ***************
        //  リソース
        // ***************
        // リソースからファイルを生成する(/data/data/パッケージ名/files/に作成)
        setRawResources(MainActivity.this, R.raw.index, "index.html");

        // ***************
        //  WebView
        // ***************
        WebView webView = findViewById(R.id.webView);

        // キャッシュクリア
        // ※開発時のみ有効にする
        webView.clearCache(true);

        // JavaScriptを有効にする
        webView.getSettings().setJavaScriptEnabled(true);

        // <input type="file">で「カメラ、画像の選択」を表示する
        // ※元ソースはGoogleです。参考URLを参照してください。
        // ※そのままでは動作しないので改変しています。
        webView.setWebChromeClient(new WebChromeClient() {
            public boolean onShowFileChooser(
                    WebView webView, ValueCallback<Uri[]> filePathCallback,
                    WebChromeClient.FileChooserParams fileChooserParams) {
                if (mFilePathCallback != null) {
                    mFilePathCallback.onReceiveValue(null);
                }
                mFilePathCallback = filePathCallback;

                mCameraPhotoPath = null;
                Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(MainActivity.this.getPackageManager()) != null) {
                    String filename = Environment.getExternalStorageDirectory() + "/dummy.jpg";
                    ContentValues values = new ContentValues();
                    values.put(MediaStore.Images.Media.TITLE, filename);
                    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

                    Uri uri = getContentResolver().
                            insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
                    UriList.add(uri);
                    // INIファイルにURIリストの情報を書き込む
                    for (int i = 0; i < UriList.size(); i++) {
                        editor.putString("uri" + i, UriList.get(i).toString());
                    }
                    editor.putInt("count", UriList.size());
                    editor.apply();

                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

                    mCameraPhotoPath = uri.toString();
                }

                Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                contentSelectionIntent.setType("image/*");

                Intent[] intentArray;
                if (takePictureIntent != null) {
                    intentArray = new Intent[]{takePictureIntent};
                } else {
                    intentArray = new Intent[0];
                }

                Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                chooserIntent.putExtra(Intent.EXTRA_TITLE, "選択");
                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

                startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
                return true;
            }
        });

        // ファイルを読み込む
        webView.loadUrl("file:///" + MainActivity.this.getFilesDir() + "/index.html");
    }

    // <input type="file">で「カメラ、画像の選択」を表示する
    // ※元ソースはGoogleです。参考URLを参照してください。
    // ※そのままでは動作しないので改変しています。
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
            super.onActivityResult(requestCode, resultCode, data);
            return;
        }

        Uri[] results = null;

        if (resultCode == Activity.RESULT_OK) {
            if (data == null) {
                if (mCameraPhotoPath != null) {
                    results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                }
            } else {
                String dataString = data.getDataString();
                if (dataString != null) {
                    results = new Uri[]{Uri.parse(dataString)};

                // このコードがないとAndroid8ではカメラ写真を取れない
                } else {
                    if (mCameraPhotoPath != null) {
                        results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                    }
                }
            }
        }
        mFilePathCallback.onReceiveValue(results);
        mFilePathCallback = null;
        return;
    }

    @Override
    protected void onDestroy() {

        // URIリストをクリア
        for (Uri uri : UriList) {
            // File.exists()はアクセス権限がないファイルが"false"になるので使用しない
            try {
                getContentResolver().delete(uri, null, null);
            } catch (Exception e) {
            }
        }
        super.onDestroy();
    }
}

記事を書いている時点で気づいたのですが「ファイルの複数選択」は確認していません。でも、1つのファイルは確実に取得できているので、このコードで通らなくても多少の修正でいけるはずです。

追記:WebViewの<input type="file" multiple>でファイルを複数選択する の記事も書きました。

参考URL(Google)

https://gauntface.com/blog/2014/10/17/what-you-need-to-know-about-the-webview-in-l

https://github.com/googlearchive/chromium-webview-samples/blob/master/input-file-example/app/src/main/java/inputfilesample/android/chrome/google/com/inputfilesample/MainFragment.java





関連記事



公開日:2018年06月03日 最終更新日:2018年06月12日
記事NO:02674