File Upload in WebView

后端 未结 19 1923
旧巷少年郎
旧巷少年郎 2020-11-22 00:28

I have been struggling to upload files from WebView since last few days and there is no progress. I googled and implemented all suggested solutions but none works, like: sol

相关标签:
19条回答
  • 2020-11-22 00:55

    In 5.0 Lollipop, Google added an official method, WebChromeClient.onShowFileChooser. They even provide a way to automatically generate the file chooser intent so that it uses the input accept mime types.

    public class MyWebChromeClient extends WebChromeClient {
            // reference to activity instance. May be unnecessary if your web chrome client is member class.
        private MyActivity activity;
    
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
            // make sure there is no existing message
            if (myActivity.uploadMessage != null) {
                myActivity.uploadMessage.onReceiveValue(null);
                myActivity.uploadMessage = null;
            }
    
            myActivity.uploadMessage = filePathCallback;
    
            Intent intent = fileChooserParams.createIntent();
            try {
                myActivity.startActivityForResult(intent, MyActivity.REQUEST_SELECT_FILE);
            } catch (ActivityNotFoundException e) {
                myActivity.uploadMessage = null;
                Toast.makeText(myActivity, "Cannot open file chooser", Toast.LENGTH_LONG).show();
                return false;
            }
    
            return true;
        }
    }
    
    
    public class MyActivity extends ... {
        public static final int REQUEST_SELECT_FILE = 100;
        public ValueCallback<Uri[]> uploadMessage;
    
        protected void onActivityResult(int requestCode, int resultCode, Intent data){
            if (requestCode == REQUEST_SELECT_FILE) {
                    if (uploadMessage == null) return;
                    uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
                    uploadMessage = null;
                }
            }
        }
    }
    

    For Android versions before KitKat, the private methods mentioned in the other answers work. I have not found a good workaround for KitKat (4.4).

    0 讨论(0)
  • 2020-11-22 00:55

    I researched on this issue for almost one month. Everything failed. All codes seen in multiple websites does n't work good. But here exists the best solution

    https://github.com/chiclaim/android-webview-upload-file

    Steps

    1) Click on clone or download

    2) Get the zip fle in your local directory

    3) unzip the zip file

    4) Open Android studio

    5) Goto File ----> Open ---> Navigate to directory where you unzipped the contents.

    6) Change the required web url in webView.loadUrl("your url hre"); in MainActivity.java

    7) Works good with version Android studio 3.4.2

    0 讨论(0)
  • 2020-11-22 00:57

    Kotlin solution for Android 8:

    private var mUploadMessage: ValueCallback<Uri>? = null
    private var uploadMessage: ValueCallback<Array<Uri>>? = null
    

    Constants:

    const val FILECHOOSER_RESULTCODE = 1
    const val REQUEST_SELECT_FILE = 100
    

    WebView setup:

    webView.webChromeClient = object : WebChromeClient() {
            override fun onPermissionRequest(request: PermissionRequest?) {
                Log.d("MainActivity", "onPermissionRequest")
                requestPermission(request)
            }
    
    
            // For Android 3.0+
            fun openFileChooser(uploadMsg: ValueCallback<*>, acceptType: String) {
                mUploadMessage = uploadMsg as ValueCallback<Uri>
                val i = Intent(Intent.ACTION_GET_CONTENT)
                i.addCategory(Intent.CATEGORY_OPENABLE)
                i.type = "*/*"
                this@MainActivity.startActivityForResult(
                        Intent.createChooser(i, "File Browser"),
                        FILECHOOSER_RESULTCODE)
            }
    
            //For Android 4.1
            fun openFileChooser(uploadMsg: ValueCallback<Uri>, acceptType: String, capture: String) {
                mUploadMessage = uploadMsg
                val i = Intent(Intent.ACTION_GET_CONTENT)
                i.addCategory(Intent.CATEGORY_OPENABLE)
                i.type = "image/*"
                this@MainActivity.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE)
    
            }
    
            protected fun openFileChooser(uploadMsg: ValueCallback<Uri>) {
                mUploadMessage = uploadMsg
                val intent = Intent(Intent.ACTION_GET_CONTENT)
                intent.addCategory(Intent.CATEGORY_OPENABLE)
                intent.type = "*/*"
                startActivityForResult(Intent.createChooser(intent, "File Chooser"), FILECHOOSER_RESULTCODE)
            }
    
            override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
                uploadMessage?.onReceiveValue(null)
                uploadMessage = null
    
                uploadMessage = filePathCallback
    
                val intent = fileChooserParams!!.createIntent()
                try {
                    startActivityForResult(intent, REQUEST_SELECT_FILE)
                } catch (e: ActivityNotFoundException) {
                    uploadMessage = null
                    Toast.makeText(applicationContext, "Cannot Open File Chooser", Toast.LENGTH_LONG).show()
                    return false
                }
    
                return true
            }
    
        }
    

    And the onAcrtivityResult part:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (requestCode === REQUEST_SELECT_FILE) {
                if (uploadMessage == null)
                    return
                print("result code = " + resultCode)
                var results: Array<Uri>? = WebChromeClient.FileChooserParams.parseResult(resultCode, data)
                uploadMessage?.onReceiveValue(results)
                uploadMessage = null
            }
        } else if (requestCode === FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage)
                return
            // Use MainActivity.RESULT_OK if you're implementing WebView inside Fragment
            // Use RESULT_OK only if you're implementing WebView inside an Activity
            val result = if (intent == null || resultCode !== RESULT_OK) null else intent.data
            mUploadMessage?.onReceiveValue(result)
            mUploadMessage = null
        } else
            Toast.makeText(applicationContext, "Failed to Upload Image", Toast.LENGTH_LONG).show()
    }
    

    Please, pay attention that our intent variable called "data".

    0 讨论(0)
  • 2020-11-22 00:58

    I found it necessary to define public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture), in Android 4.1. Then I followed Michel Olivier's solution.

    0 讨论(0)
  • 2020-11-22 00:59

    This solution also works for Honeycomb and Ice Cream Sandwich. Seems like Google introduced a cool new feature (accept attribute) and forgot to to implement an overload for backwards compatibility.

    protected class CustomWebChromeClient extends WebChromeClient
    {
        // For Android 3.0+
        public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) 
        {  
            context.mUploadMessage = uploadMsg;  
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
            i.addCategory(Intent.CATEGORY_OPENABLE);  
            i.setType("image/*");  
            context.startActivityForResult( Intent.createChooser( i, "File Chooser" ), MainActivity.FILECHOOSER_RESULTCODE );  
        }
    
        // For Android < 3.0
        public void openFileChooser( ValueCallback<Uri> uploadMsg ) 
        {
            openFileChooser( uploadMsg, "" );
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:59

    Ive actually managed to get the file picker to appear in Kitkat, to select a image and to get the filepath in activity result but the only thing that im not able to "fix" (cause this workaround) is to make the input filed to fill out with file data.

    Does anyone know any way how to access the input-field from a activity ? Am using this example comment. Is just this last piece, the last brick in the wall that i just have to put into right place (tho i could trigger upload of image file directly from code.

    UPDATE #1

    Im no hardcore Android dev so i'll show code on newbie level. Im creating a new Activity in already existing Activity

    Manifest part

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <application android:label="TestApp">
     <activity android:name=".BrowseActivity"></activity>
    </application>
    

    Am creating my BrowseActivity class from this example answer. The WebChromeClient() instance basically looks the same, except last piece, triggering the picker UI part...

    private final static int FILECHOOSER_RESULTCODE=1;  
    private final static int KITKAT_RESULTCODE = 2;
    
    ...
    
    // The new WebChromeClient() looks pretty much the same, except one piece...
    
    WebChromeClient chromeClient = new WebChromeClient(){  
        // For Android 3.0+
        public void openFileChooser(ValueCallback<Uri> uploadMsg) { /* Default code */ }  
    
        // For Android 3.0+
        public void openFileChooser( ValueCallback uploadMsg, String acceptType ) { /* Default code */ }  
    
        //For Android 4.1, also default but it'll be as example
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            BrowseActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), BrowseActivity.FILECHOOSER_RESULTCODE);
    
        }  
    
        // The new code
        public void showPicker( ValueCallback<Uri> uploadMsg ){  
            // Here is part of the issue, the uploadMsg is null since it is not triggered from Android
            mUploadMessage = uploadMsg; 
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            BrowseActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), BrowseActivity.KITKAT_RESULTCODE);
        }}
    

    And some more stuff

    web = new WebView(this);
    // Notice this part, setting chromeClient as js interface is just lazy
    web.getSettings().setJavaScriptEnabled(true);
    web.addJavascriptInterface(chromeClient, "jsi" );
    web.getSettings().setAllowFileAccess(true);
    web.getSettings().setAllowContentAccess(true);
    web.clearCache(true);
    web.loadUrl( "http://as3breeze.com/upload.html" );
    web.setWebViewClient(new myWebClient());
    web.setWebChromeClient(chromeClient);
    
    
    @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) {  
      Log.d("Result", "("+requestCode+ ") - (" +resultCode  + ") - (" + intent + ") - " + mUploadMessage);  
        if (null == intent) return;  
        Uri result = null;  
        if(requestCode==FILECHOOSER_RESULTCODE)  
        {  
            Log.d("Result","Old android");  
            if (null == mUploadMessage) return;  
            result = intent == null || resultCode != RESULT_OK ? null  : intent.getData();  
            mUploadMessage.onReceiveValue(result);  
            mUploadMessage = null;  
        } else if (requestCode == KITKAT_RESULTCODE) {  
            Log.d("Result","Kitkat android");  
            result = intent.getData();  
            final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION  | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  
            String path = getPath( this, result);  
            File selectedFile = new File(path); 
    //I used you example with a bit of editing so thought i would share, here i added a method to upload the file to the webserver
    File selectedFile = new File(path);  
                UploadFile(selectedFile);
    
    
            //mUploadMessage.onReceiveValue( Uri.parse(selectedFile.toString()) );  
            // Now we have the file but since mUploadMessage was null, it gets errors
        }  
    }
    
     public void UploadFile(File selectedFile)
    {
        Random rnd = new Random();
        String sName = "File" + rnd.nextInt(999999) + selectedFile.getAbsolutePath().substring(selectedFile.getAbsolutePath().lastIndexOf("."));
        UploadedFileName = sName;
        uploadFile = selectedFile;
        if (progressBar != null && progressBar.isShowing())
        {
            progressBar.dismiss();
        }
     // prepare for a progress bar dialog
        progressBar = new ProgressDialog(mContext);
        progressBar.setCancelable(true);
        progressBar.setMessage("Uploading File");
        progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER);            
        progressBar.show();
        new Thread() {
    
            public void run() 
            {
                int serverResponseCode;
                String serverResponseMessage;
                HttpURLConnection connection = null;
                DataOutputStream outputStream = null;
                DataInputStream inputStream = null;
                String pathToOurFile = uploadFile.getAbsolutePath();
                String urlServer = "http://serveraddress/Scripts/UploadHandler.php?name" + UploadedFileName;
                String lineEnd = "\r\n";
                String twoHyphens = "--";
                String boundary =  "*****";
    
                int bytesRead, bytesAvailable, bufferSize;
                byte[] buffer;
                int maxBufferSize = 1*1024*1024;
    
                try
                {
                    FileInputStream fileInputStream = new FileInputStream(uploadFile);
    
                    URL url = new URL(urlServer);
                    connection = (HttpURLConnection) url.openConnection();
                    Log.i("File", urlServer);
    
                    // Allow Inputs &amp; Outputs.
                    connection.setDoInput(true);
                    connection.setDoOutput(true);
                    connection.setUseCaches(false);
    
                    // Set HTTP method to POST.
                    connection.setRequestMethod("POST");
    
                    connection.setRequestProperty("Connection", "Keep-Alive");
                    connection.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);
                    Log.i("File", "Open conn");
    
                    outputStream = new DataOutputStream( connection.getOutputStream() );
    
                    outputStream.writeBytes(twoHyphens + boundary + lineEnd);
                    outputStream.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + pathToOurFile +"\"" + lineEnd);
                    outputStream.writeBytes(lineEnd);
                    Log.i("File", "write bytes");
    
                    bytesAvailable = fileInputStream.available();
                    bufferSize = Math.min(bytesAvailable, maxBufferSize);
                    buffer = new byte[bufferSize];
                    Log.i("File", "available: " + fileInputStream.available());
    
                    // Read file
                    bytesRead = fileInputStream.read(buffer, 0, bufferSize);
    
                    Log.i("file", "Bytes Read: " + bytesRead);
                    while (bytesRead > 0)
                    {
                        outputStream.write(buffer, 0, bufferSize);
                        bytesAvailable = fileInputStream.available();
                        bufferSize = Math.min(bytesAvailable, maxBufferSize);
                        bytesRead = fileInputStream.read(buffer, 0, bufferSize);
                    }
    
                    outputStream.writeBytes(lineEnd);
                    outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
    
                    // Responses from the server (code and message)
                    serverResponseCode = connection.getResponseCode();
                    serverResponseMessage = connection.getResponseMessage();
                    Log.i("file repsonse", serverResponseMessage);
    
    //once the file is uploaded call a javascript function to verify the user wants to save the image
                    progressBar.dismiss();
                    runOnUiThread(new Runnable() 
                    {
    
                        @Override
                        public void run() 
                        {
                            Log.i("start", "File name: " + UploadedFileName);
                            WebView myWebView = (WebView) findViewById(R.id.webview);
                            myWebView.loadUrl("javascript:CheckImage('" + UploadedFileName + "')");
                        }
                    });
    
    
                    fileInputStream.close();
                    outputStream.flush();
                    outputStream.close();
                }
                catch (Exception ex)
                {
                    Log.i("exception", "Error: " + ex.toString());
                }               
            }
        }.start();
    

    }

    Lastly, some more code to get the actual file path, code found on SO, ive added post url in comments as well so the author gets credits for his work.

    /**
     * Get a file path from a Uri. This will get the the path for Storage Access
     * Framework Documents, as well as the _data field for the MediaStore and
     * other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @author paulburke
     * @source https://stackoverflow.com/a/20559175
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static String getPath(final Context context, final Uri uri) {
    
        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    
        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
    
                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
    
                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {
    
                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    
                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
    
                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }
    
                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };
    
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }
    
        return null;
    }
    
    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     * @source https://stackoverflow.com/a/20559175
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {
    
        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };
    
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }
    
    
    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     * @source https://stackoverflow.com/a/20559175
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }
    
    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     * @source https://stackoverflow.com/a/20559175
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }
    
    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     * @source https://stackoverflow.com/a/20559175
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }  
    

    Lastly, the HTML page needs to trigger that new method of showPicker (specificaly when on A4.4)

    <form id="form-upload" method="post" enctype="multipart/form-data">
        <input id="fileupload" name="fileupload" type="file" onclick="javascript:prepareForPicker();"/>
    </form>
    <script type="text/javascript">
    function getAndroidVersion() {
        var ua = navigator.userAgent; 
        var match = ua.match(/Android\s([0-9\.]*)/);
        return match ? match[1] : false;
    };
    function prepareForPicker(){
        if(getAndroidVersion().indexOf("4.4") != -1){
            window.jsi.showPicker();
            return false;
        }
    }
    
    function CheckImage(name)
    {
    //Check to see if user wants to save I used some ajax to save the file if necesarry
    }
    </script>
    
    0 讨论(0)
提交回复
热议问题