質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.78%

  • Android

    4518questions

    Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

  • Chrome

    332questions

    Google Chromeは携帯、テレビ、デスクトップなどの様々なプラットフォームで利用できるウェブブラウザです。Googleが開発したもので、Blink (レンダリングエンジン) とアプリケーションフレームワークを使用しています。

  • ファイル

    94questions

    ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

  • アップロード

    41questions

    アップロードは特定のファイルをウェブサーバに送るプロセスのことを指します。

androidアプリのWebViewで<input type="file">が叩けない

解決済

回答 2

投稿 2015/09/16 11:18

  • 評価
  • クリップ 1
  • VIEW 4,965

gittib_gittib

score 90

現在、WebViewを使ったandroidアプリを開発しています。
その中で、<input type="file">の要素をクリックした時にギャラリーやカメラを起動し、選択/撮影した画像をアップロードしたいのですが、うまくいっていません。
以下のページを参考にWebChromeClientを実装したのですが、onShowFileChooser / openFileChooser が実行されず、その原因が分からない状況です。

【Qiita】LollipopのWebViewでinput type fileで画像アップロード
【Qiita】androidのwebViewでinput type fileに対応する

Android OSのバージョンは5.0です。
また、実装したWebChromeClient拡張クラスは以下になります。
protected class MyWebChromeClient extends WebChromeClient {

    private static final String TAG = "MyWebChromeClient";

    /**
     * JSのアラートをトースト表示にする
     */
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        Log.d("onJsAlert", url);
        Log.d("onJsAlert", message);
        Log.d("onJsAlert", result.toString());
        Toast.makeText(context, message, Toast.LENGTH_LONG).show();
        //return super.onJsAlert(view, url, message, result);
        return true;
    }

    @Override
    public boolean onConsoleMessage(@NonNull ConsoleMessage cm) {
        Log.d(TAG, cm.message() + " -- From line "
                + cm.lineNumber() + " of "
                + cm.sourceId());
        return true;
    }

    /**
     * input type="file" 対応 (androidOS 5.0 以上)
     */
    @Override
    public boolean onShowFileChooser(WebView webView,
            ValueCallback<Uri[]> filePathCallback,
            FileChooserParams fileChooserParams)
    {
        Log.d(TAG, "onShowFileChooser started.");
        Toast.makeText(context, "ファイルを選択して下さい", Toast.LENGTH_LONG).show();
        super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        if( mUploadMessageForAfterLollipop != null) {
            mUploadMessageForAfterLollipop.onReceiveValue(null);
            mUploadMessageForAfterLollipop = null;
        }
        mUploadMessageForAfterLollipop = filePathCallback;
        Intent i = fileChooserParams.createIntent();
        try {
            ItemListViewActivity.this.startActivityForResult(i, FILE_CHOOSER_RESULT_CODE);
        } catch (ActivityNotFoundException e) {
            mUploadMessageForAfterLollipop = null;
            return false;
        }
        return true;
    }

    // input type="file" 対応 (androidOS 4.1)
    // 参考URL : http://qiita.com/masahide318/items/06af79ed8081ef725d76
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        Log.d(TAG, "openFileChooser start");
        Log.d(TAG, "acceptType : " + acceptType);
        Log.d(TAG, "capture : " + capture);

        mUploadMessage = uploadMsg;
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");
        startActivityForResult(Intent.createChooser(i, "title"), FILE_CHOOSER_RESULT_CODE);
    }

    // input type="file" 対応 (androidOS 3.0 以上)
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
        openFileChooser(uploadMsg, acceptType, "");
    }

    // input type="file" 対応 (androidOS 3.0 未満)
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
        openFileChooser(uploadMsg, "", "");
    }
}

ご教授いただける事などありましたら、よろしくお願い致します。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

check解決した方法

0

どうしても<input type="file" />をキャッチできなかったので、JavascriptInterfaceを提供し、そのメソッドをWebサーバから叩いてもらう形で対応しました。ざっくりとした流れについては、以下のようになります。

  1. WebサイトからJavascriptInterfaceを叩いて、ファイルアップロードの要求をキックする
  2. 要求を受け取ったらインテントを生成し、カメラまたはギャラリーを起動する
  3. onActivityResultで、カメラorギャラリーからの復帰を検知
  4. 撮影/選択したファイルパスを取得し、Fileオブジェクトを生成する
  5. 生成したFileオブジェクトを後続処理で利用する

実際に作成したコードは以下のようになります。

private Uri mCameraCaptureUri;

    /**
     * 画像選択をさせるためのJavascriptInterface
     */
    private class MyJsInterface {
        private static final String TAG = "MyJsInterface";
        public static final String objectName = "androidJs";

        /**
         * JavaScriptからカメラまたはギャラリーを起動し、アップロードするファイルを撮影/選択させる
         */
        @JavascriptInterface
        public void openFileDialog(String selector) {
            Log.d(TAG, "openFileDialog start");

            //カメラの起動Intentの用意
            String photoName = System.currentTimeMillis() + ".jpg";
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.Images.Media.TITLE, photoName);
            contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            mCameraCaptureUri = getContentResolver()
                    .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

            Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, mCameraCaptureUri);

            // ギャラリー用のIntent作成
            Intent intentGallery;
            if (Build.VERSION.SDK_INT < 19) {
                intentGallery = new Intent(Intent.ACTION_GET_CONTENT);
                intentGallery.setType("image/*");
            } else {
                intentGallery = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                intentGallery.addCategory(Intent.CATEGORY_OPENABLE);
                intentGallery.setType("image/jpeg");
            }
            Intent intent = Intent.createChooser(intentGallery, getString(R.string.select_image));
            intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] {intentCamera});
            startActivityForResult(intent, JS_FILE_CHOOSER_RESULT_CODE);
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        // input type="file" でファイルを指定した時呼ばれる

        if (resultCode == Activity.RESULT_CANCELED) {
            // ファイル選択をキャンセルされた場合
            if (mCameraCaptureUri != null) {
                getContentResolver().delete(mCameraCaptureUri, null, null);
                mCameraCaptureUri = null;
            }
            return;
        }

        Log.d(TAG, "onActivityResult start");
        Log.d(TAG, "requestCode : " + requestCode);
        Log.d(TAG, "resultCode : "  + resultCode);
        Log.d(TAG, "intent : "      + ((intent == null) ? "null" : intent.toString()));

        /**
         * 画像ファイルの読込に失敗した場合に投げる専用の例外クラス
         */
        class ImageLoadFailureException extends RuntimeException {
            public ImageLoadFailureException(String s) {
                super(s);
            }
        }

        try {
            switch (requestCode) {
                case JS_FILE_CHOOSER_RESULT_CODE:
                    Log.d(TAG, "androidJSのJavascriptInterfaceからのコール");
                    String path = getImagePathFromIntent(intent);
                    if (path.equals("")) {
                        throw new ImageLoadFailureException("[JsInterface] file path get failure.");
                    }
                    // 取得したファイルパスを用いて画像ファイルを開く
                    File file = new File(path);
                    Log.d(TAG, path);
                    Log.d(TAG, files.toString());

                    :
                    :
                    (開いたファイルを使って後続処理)
                    :
                    :

                    break;
            }
        } catch (RuntimeException e) {
            e.printStackTrace();
            Toast.makeText(context, R.string.image_load_failure, Toast.LENGTH_LONG).show();
        }
    }


    /**
     * ギャラリーやカメラから与えられた画像を取得し、その情報を返す
     *
     * @param intent    画像提供元のIntent
     *
     * @return String 画像情報文字列。取得失敗した場合は空文字列""を返す
     */
    private String getImagePathFromIntent (Intent intent) {
        Log.d(TAG, "getImagePathFromIntent start");

        // intentがnullだったらカメラで撮影したパターンのはず
        boolean bCamera = (intent == null);

        String sImage = "";
        Uri uri = intent != null ? intent.getData() : null;
        if (uri == null) {
            if (mCameraCaptureUri == null) {
                // カメラ画像のUriもNULLだった
                Log.d(TAG, "uri ぬるぽ");
                return "";
            }
            uri = mCameraCaptureUri;
            bCamera = true;
        } else {
            if (mCameraCaptureUri != null) {
                getContentResolver().delete(mCameraCaptureUri, null, null);
                mCameraCaptureUri = null;
            }
        }
        Log.d(TAG, "画像Uri : " + uri.toString());

        Bitmap captureImage;
        try {
            captureImage = getBitmapFromUri(uri);
            if (captureImage == null) {
                throw new IOException("captureImage == null. 画像情報ロード失敗");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
        if (bCamera) {
            Log.d(TAG, "カメラで撮影されたのでギャラリーへスキャンを促す");
            MediaScannerConnection.scanFile(
                    this,
                    new String[]{uri.getPath()},
                    new String[]{"image/jpeg"},
                    null
            );
        }
        Log.d(TAG, captureImage.toString());

        Cursor cur = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !bCamera) {
            try {
                String id = DocumentsContract.getDocumentId(uri);
                String selection = "_id=?";
                String[] selectionArgs = new String[]{id.split(":")[1]};
                cur = getContentResolver().query(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        new String[]{MediaStore.MediaColumns.DATA},
                        selection,
                        selectionArgs,
                        null);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
                throw e;
            }
        }

        if (cur == null) {
            // カメラで撮った場合または旧いOS向けの処理
            String[] projection = {MediaStore.Images.Media.DATA};
            cur = getContentResolver().query(uri, projection, null, null, null);
        } else {
            if (cur.moveToFirst()) {
                sImage = cur.getString(0);
            }
            cur.close();
        }

        if (sImage == null) {
            Log.w(TAG, "sImage is null!!!");
            sImage = "";
        }
        Log.d(TAG, "sImage : " + sImage);

        return sImage;
    }

投稿 2015/12/25 12:57

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    以下のような回答は評価を下げられます

    • 間違っている回答
    • 質問の回答になっていない投稿
    • 不快な投稿

    評価を下げる際はその理由をコメントに書き込んでください。

0

ふと思ったのですが、「input type="file"」に対して動くメソッドが2つあるときはどちらが起動するのでしょうか。「順番に両方動く」、「片方動く」、「両方動かない」
いろいろなサイトを巡ったところ片方しか書いていないところが多いので…

投稿 2015/11/11 15:51

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    以下のような回答は評価を下げられます

    • 間違っている回答
    • 質問の回答になっていない投稿
    • 不快な投稿

    評価を下げる際はその理由をコメントに書き込んでください。

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.78%

関連した質問

同じタグがついた質問を見る

  • Android

    4518questions

    Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

  • Chrome

    332questions

    Google Chromeは携帯、テレビ、デスクトップなどの様々なプラットフォームで利用できるウェブブラウザです。Googleが開発したもので、Blink (レンダリングエンジン) とアプリケーションフレームワークを使用しています。

  • ファイル

    94questions

    ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

  • アップロード

    41questions

    アップロードは特定のファイルをウェブサーバに送るプロセスのことを指します。

閲覧数の多いAndroidの質問