安卓WebView文件上传

37

我正在开发一个Android应用程序。基本上它是一个WebView和进度条。Facebook的移动站点(m.facebook.com)被加载到WebView中。

当我点击选择文件按钮上传图像时,没有任何反应。我已经尝试了所有的解决方案,但都不起作用。我在运行4.0.3的Galaxy Note(GT-N7000)上进行测试。我的最小SDK版本是8。

My App
(来源:istyla.com)

以下是我的代码,以获取更多信息...

public class IStyla extends Activity {
    private ValueCallback<Uri> mUploadMessage;
    private final static int FILECHOOSER_RESULTCODE = 1;

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage)
                return;
            Uri result = intent == null || resultCode != RESULT_OK ? null
                    : intent.getData();
            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;

        }
    }
    private class MyWebChromeClient extends WebChromeClient {
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            IStyla.this.startActivityForResult(Intent.createChooser(i, "Image Browser"), FILECHOOSER_RESULTCODE);
        }
    
        @Override
        public boolean onJsAlert(WebView view, String url, String message,final JsResult result) {
            //handle Alert event, here we are showing AlertDialog
            new AlertDialog.Builder(IStyla.this)
                .setTitle("JavaScript Alert !")
                .setMessage(message)
                .setPositiveButton(android.R.string.ok,
                    new AlertDialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            // do your stuff
                            result.confirm();
                        }
                    }).setCancelable(false).create().show();
            return true;
        }
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_istyla);
        WebView webView = (WebView) findViewById(R.id.webView1);
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webView.setWebChromeClient(new MyWebChromeClient(){
            public void onProgressChanged(WebView view, int progress) {
                // Activities and WebViews measure progress with different scales.
                // The progress meter will automatically disappear when we reach 100%
                ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar1);
                if(progress < 100 && progressBar.getVisibility() == ProgressBar.GONE){
                    progressBar.setVisibility(ProgressBar.VISIBLE);
                }
                progressBar.setProgress(progress);
                if(progress == 100) {
                    progressBar.setVisibility(ProgressBar.GONE);
                }
            }
            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
                mUploadMessage = uploadMsg;
                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                i.addCategory(Intent.CATEGORY_OPENABLE);
                i.setType("image/*");
                IStyla.this.startActivityForResult(Intent.createChooser(i, "Image Browser"), FILECHOOSER_RESULTCODE);
            }
        });
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar1);
                progressBar.setVisibility(ProgressBar.VISIBLE);
                return true;
            }
            
        });
        webView.loadUrl("https://m.facebook.com");

    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK){
            if(((WebView)findViewById(R.id.webView1)).canGoBack()){
                ((WebView)findViewById(R.id.webView1)).goBack();
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_istyla, menu);
        return true;
    }

}

谢谢


也许您需要权限,例如READ_EXTERNAL_STORAGE - neworld
我已经添加了权限,但它仍然不起作用。我已经在上面添加了我的代码。 - Jacques Blom
2
请查看https://dev59.com/G2025IYBdhLWcg3wnXSf。 - David Esteves
1
如何在混淆后处理此方法? 我在没有混淆的情况下使用相同的方法,它可以正常工作,但是当我的应用程序部署时进行了混淆,这个方法就不会被调用。 我已经在Proguard中保留了这个方法。 有人遇到过这个问题吗? - Harshawardhan
这个Webview子类可以自动处理文件上传,可能会很有用:https://github.com/delight-im/Android-AdvancedWebView - caw
5个回答

13

这是我在我的应用程序中使用的方式

 private class MyWebChromeClient extends WebChromeClient {
    //The undocumented magic method override
    //Eclipse will swear at you if you try to put @Override here


    // For Android 3.0+
    public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
        mUploadMessage = uploadMsg;
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE);
    }

    //For Android 4.1+ only
    protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        mUploadMessage = uploadMsg;
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE);
    }

    protected void openFileChooser(ValueCallback<Uri> uploadMsg) {
        mUploadMessage = uploadMsg;
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        startActivityForResult(Intent.createChooser(intent, "File Chooser"), FILECHOOSER_RESULTCODE);
    }

    // For Lollipop 5.0+ Devices
    public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
        if (uploadMessage != null) {
            uploadMessage.onReceiveValue(null);
            uploadMessage = null;
        }

        uploadMessage = filePathCallback;
        Intent intent = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            intent = fileChooserParams.createIntent();
        }
        try {
            startActivityForResult(intent, REQUEST_SELECT_FILE);
        } catch (ActivityNotFoundException e) {
            uploadMessage = null;
            Toast.makeText(getApplicationContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show();
            return false;
        }
        return true;
    }

1
很好,它对我有用。但是当我在Marshmallow设备中从文档选择器中选择图库时,它无法获取文件。 - Jatinkumar Patel
什么是uploadMessage?什么是REQUEST_SELECT_FILE? - asdjfiasd
uploadMessage是ValueCallback<Uri>(请检查问题代码),而REQUEST_SELECT_FILE是任何整数,用于跟踪onActivityResult()。 - Salman Nazir
刚刚不得不在 onActivityResult 中适应这段代码 Uri[] uris = new Uri[1]; uris[0] = result; uploadMessage.onReceiveValue(uris); - AlvaroCryptogram
FileChooserParams 已经超过 21+,还有 onShowFileChooser - Miha_x64
顺便说一下,你无法测试100%。 - Moradnejad

5

Mike Olivier的参考资料Fr33dan分享,非常重要。我将分享在类似情况下对我有用的内容。

wv.setWebChromeClient(new WebChromeClient()  {

// For Android 3.0+
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {  
mUploadMessage = uploadMsg;  
Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
i.addCategory(Intent.CATEGORY_OPENABLE);  
i.setType("image/*");  
MainActivity.this.startActivityForResult( Intent.createChooser( i, getString(R.string.fileselect) ), MainActivity.FILECHOOSER_RESULTCODE ); 
}

// For Android < 3.0
public void openFileChooser( ValueCallback<Uri> uploadMsg ) {
openFileChooser( uploadMsg, "" );
}


// For Android > 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){
openFileChooser( uploadMsg, "" );
}
});

注意:我在Stack Overflow和m0s的博客中找到了这个内容。

另外,忽略lint警告。对于API 8及以上版本,这个很有效。


很多用户仍在使用Android 3.0及以下版本。甚至在Stack Overflow社区中我也发现了这一情况。:) - Chirag
即使我实现了这个函数,什么也没有发生。这不是递归函数调用吗? - Sagar

4
考虑到投票数量很高,我猜没人注意到第三条评论(来自David Esteves),其中包含回答此问题的链接。 Michel Olivier说:

这个解决方案也适用于蜂窝和冰激凌三明治。看起来谷歌引入了一个很酷的新功能(接受属性)并忘记了实现向后兼容的重载。

所以您只需要向MyWebChromeClient类添加一个接受字符串参数的openFileChooser重载,然后调用那个没有参数的函数即可。
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) 
{  
    this.openFileChooser(uploadMsg);
}

通过这样做,我能够使您的代码按预期运行。


3
在WebChromeClient.OpenFileChooser中调用AlertDialog时,需要在按下返回按钮的情况下通知webchrome未做出选择。由于您的对话框中可能会按下后退按钮,因此必须处理对话框的OnCancel事件,并告知WebChrome选择已完成。
例如:
private ValueCallback<Uri> mUploadMessage;
private final static int FILECHOOSER_RESULTCODE = 1;
private final static int CAMERA_RESULTCODE = 2;

...

    webView.setWebChromeClient(new WebChromeClient() {
      public boolean onConsoleMessage(ConsoleMessage cm) {
        Log.d("MyApplication", cm.message() + " -- From line "
                             + cm.lineNumber() + " of "
                             + cm.sourceId() );
        return true;
      }

      public void onExceededDatabaseQuota(String url, String
            databaseIdentifier, long currentQuota, long estimatedSize, long
            totalUsedQuota, QuotaUpdater quotaUpdater) {
                            quotaUpdater.updateQuota(estimatedSize+65536);
            } 

     @SuppressWarnings("unused")
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType, String capture) {
            this.openFileChooser(uploadMsg);
         }

     @SuppressWarnings("unused")
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType) {
        this.openFileChooser(uploadMsg);
     }

     public void openFileChooser(ValueCallback<Uri> uploadMsg) {

         mUploadMessage = uploadMsg;
        PesScreen.this.openImageIntent();
     }
    });        

...

private void openImageIntent() {

final File root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); //file storage


    root.mkdirs();
    int nCnt = 1;
    if ( root.listFiles() != null )
        nCnt = root.listFiles().length;
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    final String fname =  String.format("/dest-%s-%d.jpg", sdf.format(Calendar.getInstance().getTime()), nCnt);

    final File sdImageMainDirectory = new File(root.getAbsolutePath() + fname);
    outputFileUri = Uri.fromFile(sdImageMainDirectory);
//selection Photo/Gallery dialog
    AlertDialog.Builder alert = new AlertDialog.Builder(this);

    alert.setTitle("Select source");

    final CharSequence[] items = {"Photo", "Gallery"};
    alert.setItems(items, new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog, int whichButton) {

            dialog.dismiss();
            if( whichButton == 0)
            {
                Intent chooserIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                chooserIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
                startActivityForResult(chooserIntent, CAMERA_RESULTCODE);
            }
            if( whichButton == 1)
            {
                Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
                chooserIntent.setType("image/*");
                startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
            }
      }
    });
    alert.setOnCancelListener(new DialogInterface.OnCancelListener() {
        @Override
        public void onCancel(DialogInterface dialog) {

        //here we have to handle BACK button/cancel 
            if ( mUploadMessage!= null ){
                mUploadMessage.onReceiveValue(null);
            }
            mUploadMessage = null;
            dialog.dismiss();
        }
    });
    alert.create().show();

2

这段代码将有助于解决问题

只需使用以下代码更新MainActivity.Java文件即可

public class MainActivity extends Activity{
    private WebView mWebview ;
    private ValueCallback<Uri> mUploadMessage;
    public ValueCallback<Uri[]> uploadMessage;
    public static final int REQUEST_SELECT_FILE = 100;
    private final static int FILECHOOSER_RESULTCODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        mWebview  = (WebView) findViewById(R.id.help_webview);
        WebSettings webSettings = mWebview.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setAllowFileAccess(true);
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setPluginState(WebSettings.PluginState.ON);
        webSettings.setSupportZoom(true);
        webSettings.setAllowContentAccess(true);



        final Activity activity = this;
        mWebview.setWebViewClient(new WebViewClient() {
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                Toast.makeText(activity, description, Toast.LENGTH_SHORT).show();
            }
        });

        mWebview .loadUrl("http://www.google.com");

        String permission = Manifest.permission.CAMERA;
        int grant = ContextCompat.checkSelfPermission(this, permission);
        if (grant != PackageManager.PERMISSION_GRANTED) {
            String[] permission_list = new String[1];
            permission_list[0] = permission;
            ActivityCompat.requestPermissions(this, permission_list, 1);
        }


        mWebview.setWebChromeClient(new WebChromeClient()
        {
            // For 3.0+ Devices (Start)
            // onActivityResult attached before constructor
            protected void openFileChooser(ValueCallback uploadMsg, String acceptType)
            {
                mUploadMessage = uploadMsg;
                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                i.addCategory(Intent.CATEGORY_OPENABLE);
                i.setType("image/*");
                startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE);
            }


            // For Lollipop 5.0+ Devices
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)
            {
                if (uploadMessage != null) {
                    uploadMessage.onReceiveValue(null);
                    uploadMessage = null;
                }

                uploadMessage = filePathCallback;

                Intent intent = fileChooserParams.createIntent();
                try
                {
                    startActivityForResult(intent, REQUEST_SELECT_FILE);
                } catch (ActivityNotFoundException e)
                {
                    uploadMessage = null;
                    Toast.makeText(MainActivity.this.getApplicationContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show();
                    return false;
                }
                return true;
            }

            //For Android 4.1 only
            protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
            {
                mUploadMessage = uploadMsg;
                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                intent.setType("image/*");
                startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE);
            }

            protected void openFileChooser(ValueCallback<Uri> uploadMsg)
            {
                mUploadMessage = uploadMsg;
                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                i.addCategory(Intent.CATEGORY_OPENABLE);
                i.setType("image/*");
                startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
            }
        });



    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent)
    {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        {
            if (requestCode == REQUEST_SELECT_FILE)
            {
                if (uploadMessage == null)
                    return;
                uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
                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
            Uri result = intent == null || resultCode != MainActivity.RESULT_OK ? null : intent.getData();
            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;
        }
        else
            Toast.makeText(MainActivity.this.getApplicationContext(), "Failed to Upload Image", Toast.LENGTH_LONG).show();
    }

请确保在AndroidManifest.xml中启用权限,并更新以下行

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

这段代码对我有用,你可以试试看。


我需要通过Webview将图片和视频上传到网站。如果用户决定使用他们的相机拍摄照片进行上传,或者选择图像/视频进行上传到Webview,那就更好了。 - TheLearner
另一个重要的问题是,图像在代码中的哪个部分被返回?我很难找到那一部分。谢谢。 - TheLearner
用户可以通过此代码上传照片、视频和其他文件,它能正常工作。请问您的应用程序的APK版本是多少?因为这会影响我提供的代码是否适用于目标Marshmallow版本。在运行应用程序时,它会要求用户允许使用相机和存储,所以我已经上传了文件@TheLearner。 - Pronab Roy
访问以下链接,您将了解为什么需要编写代码以授予权限:https://developer.android.com/training/permissions/requesting.html - Pronab Roy
我理解代码为什么需要权限,但是我在你的代码中没有看到如何处理被拒绝的权限。在这种情况下,应用程序会崩溃吗? - TheLearner
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接