0

有没有办法让 Phonegap 的 File API 像浏览器中的相同 API 一样工作?我需要与浏览器中相同的行为:点击“打开文件”按钮->“选择文件”对话框->...->使用 FileReader 对象打开文件。

谢谢!!!

更新:我找到了一些解决方案。它打开文件对话框并返回 JS 文件对象的名称。但是,当我从 PhoneGap 将该 JS 文件对象传递给 FileReader 时 - 它没有打开(onload 侦听器永远不会触发)。我做错了什么?

这是我的Java:

package org.apache.cordova.example;

import android.os.Bundle;
import org.apache.cordova.api.CordovaInterface;
import android.content.Intent;
import android.net.Uri;
import android.webkit.ValueCallback;
import org.apache.cordova.CordovaChromeClient;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.DroidGap;


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

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    super.loadUrl("file:///android_asset/www/index.html");

    this.appView.setWebChromeClient(new FileAttachmentChromeClient(this,     this.appView));
}



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

// openFileChooser is an overridable method in WebChromeClient which isn't
// included in the SDK's Android stub code
public class FileAttachmentChromeClient extends CordovaChromeClient {

    public FileAttachmentChromeClient(CordovaInterface ctx, CordovaWebView app) {
        super(ctx, app);

    }
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
        mUploadMessage = uploadMsg;
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("*/*");
        cordovaExample.this.startActivityForResult(Intent.createChooser(i, "Choose type of attachment"), FILECHOOSER_RESULTCODE);
    }

}
}

我用 JavaScript 做什么:

 var SelectedFile;
 var FReader; 

 document.getElementById('fileBox').addEventListener('change', fileChosen);     


 function fileChosen(evnt) {
SelectedFile = evnt.target.files[0];      
     FReader = new FileReader(); 
     FReader.readAsDataURL(SelectedFile);
     FReader.onload = function(loadevnt){   ....   }  //never happens
}
4

1 回答 1

1

那么问题是大多数移动浏览器不支持文件对话框。IIRC iOS 6 是第一个支持此功能的移动浏览器。我确实在我的Corinthian项目中编写了一些代码,而不是在积极开发中,这些代码确实修补了这个功能。

首先,您需要编写一个 Android 插件来启动提供文件对话框的意图。我正在使用 OI 文件管理器。

package org.apache.cordova;

import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;

/**
 * This class exposes methods in DroidGap that can be called from JavaScript.
 */
public class App extends CordovaPlugin {

    /**
     * Executes the request and returns PluginResult.
     *
     * @param action            The action to execute.
     * @param args              JSONArry of arguments for the plugin.
     * @param callbackContext   The callback context from which we were invoked.
     * @return                  A PluginResult object with a status and message.
     */
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        PluginResult.Status status = PluginResult.Status.OK;
        String result = "";

        try {
            if (action.equals("clearCache")) {
                this.clearCache();
            }
            else if (action.equals("show")) {
                // This gets called from JavaScript onCordovaReady to show the webview.
                // I recommend we change the name of the Message as spinner/stop is not
                // indicative of what this actually does (shows the webview).
                cordova.getActivity().runOnUiThread(new Runnable() {
                    public void run() {
                        webView.postMessage("spinner", "stop");
                    }
                });
            }
            else if (action.equals("loadUrl")) {
                this.loadUrl(args.getString(0), args.optJSONObject(1));
            }
            else if (action.equals("cancelLoadUrl")) {
                this.cancelLoadUrl();
            }
            else if (action.equals("clearHistory")) {
                this.clearHistory();
            }
            else if (action.equals("backHistory")) {
                this.backHistory();
            }
            else if (action.equals("overrideButton")) {
                this.overrideButton(args.getString(0), args.getBoolean(1));
            }
            else if (action.equals("overrideBackbutton")) {
                this.overrideBackbutton(args.getBoolean(0));
            }
            else if (action.equals("exitApp")) {
                this.exitApp();
            }
            callbackContext.sendPluginResult(new PluginResult(status, result));
            return true;
        } catch (JSONException e) {
            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
            return false;
        }
    }

    //--------------------------------------------------------------------------
    // LOCAL METHODS
    //--------------------------------------------------------------------------

    /**
     * Clear the resource cache.
     */
    public void clearCache() {
        this.webView.clearCache(true);
    }

    /**
     * Load the url into the webview.
     *
     * @param url
     * @param props         Properties that can be passed in to the DroidGap activity (i.e. loadingDialog, wait, ...)
     * @throws JSONException
     */
    public void loadUrl(String url, JSONObject props) throws JSONException {
        LOG.d("App", "App.loadUrl("+url+","+props+")");
        int wait = 0;
        boolean openExternal = false;
        boolean clearHistory = false;

        // If there are properties, then set them on the Activity
        HashMap<String, Object> params = new HashMap<String, Object>();
        if (props != null) {
            JSONArray keys = props.names();
            for (int i = 0; i < keys.length(); i++) {
                String key = keys.getString(i);
                if (key.equals("wait")) {
                    wait = props.getInt(key);
                }
                else if (key.equalsIgnoreCase("openexternal")) {
                    openExternal = props.getBoolean(key);
                }
                else if (key.equalsIgnoreCase("clearhistory")) {
                    clearHistory = props.getBoolean(key);
                }
                else {
                    Object value = props.get(key);
                    if (value == null) {

                    }
                    else if (value.getClass().equals(String.class)) {
                        params.put(key, (String)value);
                    }
                    else if (value.getClass().equals(Boolean.class)) {
                        params.put(key, (Boolean)value);
                    }
                    else if (value.getClass().equals(Integer.class)) {
                        params.put(key, (Integer)value);
                    }
                }
            }
        }

        // If wait property, then delay loading

        if (wait > 0) {
            try {
                synchronized(this) {
                    this.wait(wait);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.webView.showWebPage(url, openExternal, clearHistory, params);
    }

    /**
     * Cancel loadUrl before it has been loaded (Only works on a CordovaInterface class)
     */
    @Deprecated
    public void cancelLoadUrl() {
        this.cordova.cancelLoadUrl();
    }

    /**
     * Clear page history for the app.
     */
    public void clearHistory() {
        this.webView.clearHistory();
    }

    /**
     * Go to previous page displayed.
     * This is the same as pressing the backbutton on Android device.
     */
    public void backHistory() {
        cordova.getActivity().runOnUiThread(new Runnable() {
            public void run() {
                webView.backHistory();
            }
        });
    }

    /**
     * Override the default behavior of the Android back button.
     * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
     *
     * @param override      T=override, F=cancel override
     */
    public void overrideBackbutton(boolean override) {
        LOG.i("App", "WARNING: Back Button Default Behaviour will be overridden.  The backbutton event will be fired!");
        webView.bindButton(override);
    }

    /**
     * Override the default behavior of the Android volume buttons.
     * If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
     *
     * @param button        volumeup, volumedown
     * @param override      T=override, F=cancel override
     */
    public void overrideButton(String button, boolean override) {
        LOG.i("DroidGap", "WARNING: Volume Button Default Behaviour will be overridden.  The volume event will be fired!");
        webView.bindButton(button, override);
    }

    /**
     * Return whether the Android back button is overridden by the user.
     *
     * @return boolean
     */
    public boolean isBackbuttonOverridden() {
        return webView.isBackButtonBound();
    }

    /**
     * Exit the Android application.
     */
    public void exitApp() {
        this.webView.postMessage("exit", null);
    }

}

package com.simonmacdonald.corinthian;

import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;

public class FileDialog extends Plugin {
    private static final int PICK_FILE_RESULT_CODE = 8974;
    private static final int PICK_DIRECTORY_RESULT_CODE = 8975;
    private static final String LOG_TAG = "FileDialog";
    public String callbackId;

    /**
     * Executes the request and returns PluginResult.
     *
     * @param action        The action to execute.
     * @param args          JSONArry of arguments for the plugin.
     * @param callbackId    The callback id used when calling back into JavaScript.
     * @return              A PluginResult object with a status and message.
     */
    @Override
    public PluginResult execute(String action, JSONArray args, String callbackId) {
        this.callbackId = callbackId;

        JSONObject options = args.optJSONObject(0);

        if (action.equals("pickFile")) {
            showDialog(options, PICK_FILE_RESULT_CODE);
        } else if (action.equals("pickFolder")) {
            showDialog(options, PICK_DIRECTORY_RESULT_CODE);
        }
        else {
            return new PluginResult(PluginResult.Status.INVALID_ACTION);
        }
        PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
        r.setKeepCallback(true);
        return r;
    }

    private void showDialog(JSONObject options, int type) {
        Intent intent;
        if (type == PICK_FILE_RESULT_CODE) {
            intent = new Intent("org.openintents.action.PICK_FILE");
        } else {
            intent = new Intent("org.openintents.action.PICK_DIRECTORY");
        }
        if (options != null) {
            String title = options.optString("title");
            if (title != null) {
                intent.putExtra("org.openintents.extra.TITLE", title);
            }
            String button = options.optString("button");
            if (button != null) {
                intent.putExtra("org.openintents.extra.BUTTON_TEXT", button);
            }
        }
        //intent.setData(Uri.fromFile(new File("/")));
        try {
            this.cordova.startActivityForResult((Plugin)this,intent,PICK_FILE_RESULT_CODE);
        } catch (ActivityNotFoundException e) {
            showDownloadDialog();
        }
    }

    private void showDownloadDialog() {
        final Context context = this.cordova.getContext();
        Runnable runnable = new Runnable() {
            public void run() {

                AlertDialog.Builder dialog = new AlertDialog.Builder(context);
                dialog.setTitle("Install File Manager?");
                dialog.setMessage("This requires the free OI File Manager app. Would you like to install it now?");
                dialog.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dlg, int i) {
                        dlg.dismiss();
                        Intent intent = new Intent(Intent.ACTION_VIEW,
                            Uri.parse("market://search?q=pname:org.openintents.filemanager")
                        );
                        try {
                            context.startActivity(intent);
                        } catch (ActivityNotFoundException e) {
//                          We don't have the market app installed, so download it directly.
                            Intent in = new Intent(Intent.ACTION_VIEW);
                            in.setData(Uri.parse("http://openintents.googlecode.com/files/FileManager-1.2.apk"));
                            context.startActivity(in);

                        }

                    }
                });
                dialog.setNegativeButton("No", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dlg, int i) {
                        dlg.dismiss();
                    }
                });
                dialog.create();
                dialog.show();
            }
        };
        this.cordova.getActivity().runOnUiThread(runnable);
    }

    @Override
    public void onActivityResult(int reqCode, int resultCode, Intent data) {
      //super.onActivityResult(reqCode, resultCode, data);
      //Log.d(LOG_TAG, "Data is " + data.getData().toString());
      Log.d(LOG_TAG, "we are in on activity result");
      switch (reqCode) {
      case PICK_FILE_RESULT_CODE:
      case PICK_DIRECTORY_RESULT_CODE: {
          if (resultCode==Activity.RESULT_OK && data!=null && data.getData()!=null) {
              String filePath = "file://" + data.getData().getPath();
              Log.d(LOG_TAG, "The data is = " + filePath);
              Log.d(LOG_TAG, "Calling succes with callback id = " + this.callbackId);
              this.success(new PluginResult(PluginResult.Status.OK, filePath), this.callbackId);
          }
          break;
      }
  }
    }
}

然后你需要编写你的 JavaScript 接口:

FileDialog: {
    pickFile: function(successCallback, errorCallback, options) {
        var win = typeof successCallback !== 'function' ? null : function(f) {
            window.resolveLocalFileSystemURI(f, function(fileEntry) {
                successCallback(fileEntry);
            }, fail);
        };
        cordova.exec(win, errorCallback, "FileDialog", "pickFile", [options]);
    },
    pickFolder: function(successCallback, errorCallback, options) {
        var win = typeof successCallback !== 'function' ? null : function(d) {
            window.resolveLocalFileSystemURI(d, function(dirEntry) {
                successCallback(dirEntry);
            }, fail);
        };
        cordova.exec(win, errorCallback, "FileDialog", "pickFolder", [options]);
    },
    patch: function() {
        var inputs = document.getElementsByTagName("input");
        for (var i=0; i < inputs.length; i++) {
           if (inputs[i].getAttribute('type') == 'file'){
               var me = inputs[i];
               inputs[i].addEventListener("click", function() {
                   corinthian.FileDialog.pickFile(function(fileEntry) {
                       me.value = fileEntry.fullPath;
                   });
               });
           }
        }
    }
}

最后通过调用 FileDialog.patch() 进行猴子补丁;在 PhoneGap 中收到 deviceready 事件后。

希望这可以帮助...

于 2012-12-07T15:50:38.430 回答