Android 4.4 WebView文件选择器无法打开?

28
我们正在创建一个应用程序,该应用程序使用Webview,并访问用户需要上传文件的页面。我们在Android 4.4上遇到了问题,其中文件选择器不会打开,并且单击上传按钮时不会发生任何事情。此功能在早期版本中使用openFileChooser方法可以正常工作:
 webview.setWebChromeClient(new 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<Uri> uploadMsg) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

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

        //For Android 4.1
        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("image/*");
            MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), MainActivity.FILECHOOSER_RESULTCODE);
        }

    });

我已经花费了相当长时间来寻找在4.4上实现它的方法,但是没有成功。有人成功实现了吗?


你贴出的代码中看不到与 WebView 相关的连接,所以我想知道它与 WebView 有什么关系。上传按钮在哪里?请展示调用上述函数的代码。 - greenapps
我已经添加了一个部分,显示代码所在的位置。希望这能帮助澄清。 - MikeCon94
请问您能否在HTML页面中发布一个最简单的< form >,尝试调用选择器?(我之前也问过这个问题)。并且告诉我这个页面是来自互联网还是资源文件。 - greenapps
嘿@Reshma,我还没有找到解决方案,但我正在考虑实现我看到人们谈论的phonegap解决方法。我现在找不到链接,但有人在SO上发布了一个完整的实施指南。看起来可能需要一些时间才能使其运行起来。当我找到链接时,我会在这里发布它。 - MikeCon94
将setTargetSdkVersion设置为小于18。 - Muhammad Babar
显示剩余2条评论
9个回答

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 i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("*/*");
        MainActivity1.this.startActivityForResult(Intent.createChooser(i, "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 i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("*/*");
        startActivityForResult(Intent.createChooser(i, "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;
    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

        Log.d("LogTag", message);
        result.confirm();
        return true;
    }
}

  @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_SELECT_FILE) {
        if (uploadMessage == null) return;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
        }
        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 = data == null || resultCode != MainActivity.RESULT_OK ? null : data.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    }
}

我们如何使用相机拍摄的文件呢?它会显示为损坏。 - Vasudev Vyas
1
@Vasudev 你试过这个吗?https://stackoverflow.com/a/30322290/4201378 - Salman Nazir
已经解决了。但这是一个很好的例子。感谢@SalmanNazir的帮助。 - Vasudev Vyas

9

WebView按预期工作

如果我理解上述链接的内容正确,你(我和可能还有数百名开发人员)正在寻找一种hack。


@MikeCon94 看看这里 steveN 在评论中说的话:[https://dev59.com/G2025IYBdhLWcg3wnXSf#7041918] - magmike
简单的解决方案是将setTargetSdkVersion设置为小于18的值 :) - Muhammad Babar
在Android < 4.4中有效的两种方法都被设为final。通过对WebChromeClient进行一些反射,我得到了以下内容: public final void WebChromeClient.openFileChooser(android.webkit.ValueCallback,java.lang.String,java.lang.String) 和 public final void WebChromeClient.openFileChooser(android.webkit.ValueCallback,java.lang.String)。 换句话说:Android 4.4没有解决方案。 - Emanuel Moecklin

2

我找到了一个对我有用的解决方案。我在proguard-android.txt中添加了一条规则:

-keepclassmembers class * extends android.webkit.WebChromeClient {
     public void openFileChooser(...);
}

2

我也在解决这个问题,以下是我的解决方案:

private class MyWebChromeClient extends WebChromeClient {


    /**
     * This is the method used by Android 5.0+ to upload files towards a web form in a Webview
     *
     * @param webView
     * @param filePathCallback
     * @param fileChooserParams
     * @return
     */
    @Override
    public boolean onShowFileChooser(
            WebView webView, ValueCallback<Uri[]> filePathCallback,
            WebChromeClient.FileChooserParams fileChooserParams) {

        if (mFilePathCallback != null) {
            mFilePathCallback.onReceiveValue(null);
        }
        mFilePathCallback = filePathCallback;

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

        Intent[] intentArray = getCameraIntent();

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

        startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);

        return true;
    }

    @Override
    public void onProgressChanged(WebView view, int newProgress) {

        mProgressBar.setVisibility(View.VISIBLE);
        WebActivity.this.setValue(newProgress);
        super.onProgressChanged(view, newProgress);
    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

        Log.d("LogTag", message);
        result.confirm();
        return true;
    }

    /**
     * Despite that there is not a Override annotation, this method overrides the open file
     * chooser function present in Android 3.0+
     *
     * @param uploadMsg
     * @author Tito_Leiva
     */
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {

        mUploadMessage = uploadMsg;
        Intent i = getChooserIntent(getCameraIntent(), getGalleryIntent("image/*"));
        i.addCategory(Intent.CATEGORY_OPENABLE);
        WebActivity.this.startActivityForResult(Intent.createChooser(i, "Selecciona la imagen"), FILECHOOSER_RESULTCODE);

    }

    public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
        mUploadMessage = uploadMsg;
        Intent i = getChooserIntent(getCameraIntent(), getGalleryIntent("*/*"));
        i.addCategory(Intent.CATEGORY_OPENABLE);
        WebActivity.this.startActivityForResult(
                Intent.createChooser(i, "Selecciona la imagen"),
                FILECHOOSER_RESULTCODE);
    }

    /**
     * Despite that there is not a Override annotation, this method overrides the open file
     * chooser function present in Android 4.1+
     *
     * @param uploadMsg
     * @author Tito_Leiva
     */
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        mUploadMessage = uploadMsg;
        Intent i = getChooserIntent(getCameraIntent(), getGalleryIntent("image/*"));
        WebActivity.this.startActivityForResult(Intent.createChooser(i, "Selecciona la imagen"), FILECHOOSER_RESULTCODE);

    }

    private Intent[] getCameraIntent() {

        // Determine Uri of camera image to save.
        Intent takePictureIntent = new Intent(WebActivity.this, CameraActivity.class);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            // Create the File where the photo should go
            File photoFile = null;
            try {
                photoFile = createImageFile();
                takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
            } catch (IOException ex) {
                // Error occurred while creating the File
                Log.e(TAG, "Unable to create Image File", ex);
            }

            // Continue only if the File was successfully created
            if (photoFile != null) {
                mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                        Uri.fromFile(photoFile));
            } else {
                takePictureIntent = null;
            }
        }

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

        return intentArray;

    }

    private Intent getGalleryIntent(String type) {

        // Filesystem.
        final Intent galleryIntent = new Intent();
        galleryIntent.setType(type);
        galleryIntent.addCategory(Intent.CATEGORY_OPENABLE);
        galleryIntent.setAction(Intent.ACTION_GET_CONTENT);

        return galleryIntent;
    }

    private Intent getChooserIntent(Intent[] cameraIntents, Intent galleryIntent) {

        // Chooser of filesystem options.
        final Intent chooserIntent = Intent.createChooser(galleryIntent, "Seleccionar Fuente");

        // Add the camera options.
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents);

        return chooserIntent;
    }
}

我解决的一个问题是,在onActivityResult()方法中传送的uri没有扩展名。 为了解决这个问题,我使用了以下方法:
public static Uri savePicture(Context context, Bitmap bitmap, int maxSize) {

    int cropWidth = bitmap.getWidth();
    int cropHeight = bitmap.getHeight();

    if (cropWidth > maxSize) {
        cropHeight = cropHeight * maxSize / cropWidth;
        cropWidth = maxSize;

    }

    if (cropHeight > maxSize) {
        cropWidth = cropWidth * maxSize / cropHeight;
        cropHeight = maxSize;

    }

    bitmap = ThumbnailUtils.extractThumbnail(bitmap, cropWidth, cropHeight, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);

    File mediaStorageDir = new File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            context.getString(R.string.app_name)
    );

    if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
            return null;
        }
    }

    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile = new File(
            mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"
    );

    // Saving the bitmap
    try {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);

        FileOutputStream stream = new FileOutputStream(mediaFile);
        stream.write(out.toByteArray());
        stream.close();

    } catch (IOException exception) {
        exception.printStackTrace();
    }

    // Mediascanner need to scan for the image saved
    Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    Uri fileContentUri = Uri.fromFile(mediaFile);
    mediaScannerIntent.setData(fileContentUri);
    context.sendBroadcast(mediaScannerIntent);

    return fileContentUri;
} 

最后,onActivityResult() 方法是。
@Override
protected void onActivityResult(int requestCode, int resultCode,
                                Intent intent) {

    if (resultCode == RESULT_OK) {

        // This is for Android 4.4.4- (JellyBean & KitKat)
        if (requestCode == FILECHOOSER_RESULTCODE) {

            if (null == mUploadMessage) {
                super.onActivityResult(requestCode, resultCode, intent);
                return;
            }

            final boolean isCamera;

            if (intent == null) {
                isCamera = true;
            } else {
                final String action = intent.getAction();
                if (action == null) {
                    isCamera = false;
                } else {
                    isCamera = action.equals(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
                }
            }

            Uri selectedImageUri;

            if (isCamera) {

                selectedImageUri = mOutputFileUri;
                mUploadMessage.onReceiveValue(selectedImageUri);
                mUploadMessage = null;

                return;

            } else {

                try {

                    Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), intent.getData());
                    selectedImageUri = intent == null ? null : ImageUtility.savePicture(this, bitmap, 1400);

                    mUploadMessage.onReceiveValue(selectedImageUri);
                    mUploadMessage = null;

                    return;

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            // And this is for Android 5.0+ (Lollipop)
        } else if (requestCode == INPUT_FILE_REQUEST_CODE) {

            Uri[] results = null;

            // Check that the response is a good one
            if (resultCode == Activity.RESULT_OK) {
                if (intent == null) {
                    // If there is not data, then we may have taken a photo
                    if (mCameraPhotoPath != null) {
                        results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                    }
                } else {

                    Bitmap bitmap = null;

                    try {
                        bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), intent.getData());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    Uri dataUri = ImageUtility.savePicture(this, bitmap, 1400);

                    if (dataUri != null) {
                        results = new Uri[]{dataUri};
                    }
                }
            }

            mFilePathCallback.onReceiveValue(results);
            mFilePathCallback = null;

            return;
        }
    } else {

        super.onActivityResult(requestCode, resultCode, intent);
        return;
    }
} 

问候,我尝试了你的解决方案,但在Android 4.4.x上无法正常工作。在这个操作系统版本上,我没有收到任何方法调用。请问在你的设备上是否可以正常运行? - Rubin

2

请查看这里。因为API 19 4.4,WebView已经迁移到使用Chromium,不再使用WebChromeClient。


2

Android Kotlin

MyWebViewChromeClient类:

class MyWebViewChromeClient(private val mContext: MyMainActivity): WebChromeClient() {

    override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
        mContext.mUploadMessageArray?.onReceiveValue(null)
        mContext.mUploadMessageArray = filePathCallback

        val contentSelectionIntent = Intent(Intent.ACTION_GET_CONTENT)
        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
        contentSelectionIntent.type = "*/*"
        val intentArray: Array<Intent?> = arrayOfNulls(0)

        val chooserIntent = Intent(Intent.ACTION_CHOOSER)
        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
        chooserIntent.putExtra(Intent.EXTRA_TITLE, "File Chooser")
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
        mContext.startActivityForResult(chooserIntent, mContext.FILECHOOSER_RESULTCODE)
        return true
    }

MyMainActivity类:

class MyMainActivity : MyBaseActivity() {

    val FILECHOOSER_RESULTCODE = 1001
    var mUploadMessageArray: ValueCallback<Array<Uri>>? = null

    private lateinit var mWebView: MyWebView
    private lateinit var mContext: Context

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (mUploadMessageArray == null) {
                return
            }
            val result = if (intent == null || resultCode != Activity.RESULT_OK) null else data?.data
            result?.let {
                val uriArray: Array<Uri> = arrayOf(it)
                mUploadMessageArray?.onReceiveValue(uriArray)
                mUploadMessageArray = null
            } ?: kotlin.run {
                mUploadMessageArray?.onReceiveValue(null)
                mUploadMessageArray = null
            }
        }
    }
}

你是否有使用chooserIntent的版本,以便通过文件浏览器或相机上传图像?如果有,请分享。 - Vegeta ZA

0
你需要实现一个 WebviewClient 类。你可以查看这些 示例
webview.setWebViewClient(new myWebClient());

The web.setWebChromeClient(new WebChromeClient() {
//
}

创建名为mywebClient的类,并添加以下代码以实现onPageStarted()函数、shouldOvverideLoading()函数和onActivityresult()函数,如下所示。
public class myWebClient extends WebViewClient { 


     @Override
     public void onPageStarted(WebView view, String url, Bitmap favicon) { 
             super.onPageStarted(view, url, favicon);
     }

     @Override

     public boolean shouldOverrideUrlLoading(WebView view, String url) {       

        view.loadUrl(url);
       return true; 
     }

     @Override 
    public void onPageFinished(WebView view, String url) {   
        super.onPageFinished(view, url); 
    } 
 }

 //flipscreen not loading again

 @Override

 public void onConfigurationChanged(Configuration newConfig){  
       super.onConfigurationChanged(newConfig);

 } 

@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; 

  } 

}

下载演示版


0

-2

我已经在使用最新的Android版本4.4.3中尝试了Webview中的文件选择器,它运行良好。我猜测Google已经修复了这个问题。


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