WebViewの<input type="file">でカメラ、画像からデータを取得する [Android]
目次
1. 作るもの
2. 初期設定
3. 画面設計
4. HTMLファイル
5. コーディング
1. 作るもの
WebViewで表示するHTMLの<input type="file">で「カメラ or 画像ファイル」の選択ダイアログを表示します。
何も設定しない初期状態の<input type="file">ではカメラ撮影や画像ファイルを選択できません。それ所か、ボタンを押しても何も動作しません。
それで、ネットで色々探して、後述する参考URLのGoogleサイトにあるコードがありましたが、次のようになっていました。
という状況でしたが元コードを改変して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