PhoneGap插件调用Java流程源码分析(二)

来源:互联网 发布:python import路径 编辑:程序博客网 时间:2024/06/04 19:56

                PhoneGap插件调用Java流程源码分析

   回顾一下上一篇  PhoneGap插件调用Java流程源码分析(一)最后的话题,当调用webview.loadUrl(...)会需要判断是否初始化,如果没有就需要调用一下init方法:

private void initIfNecessary() {        if (pluginManager == null) {            Log.w(TAG, "CordovaWebView.init() was not called. This will soon be required.");            // Before the refactor to a two-phase init, the Context needed to implement CordovaInterface.             CordovaInterface cdv = (CordovaInterface)getContext();            if (!Config.isInitialized()) {                Config.init(cdv.getActivity());            }            init(cdv, makeWebViewClient(cdv), makeWebChromeClient(cdv), Config.getPluginEntries(), Config.getWhitelist(), Config.getExternalWhitelist(), Config.getPreferences());        }    }

   然后调用很多参数的init方法:

public void init(CordovaInterface cordova, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient,            List<PluginEntry> pluginEntries, Whitelist internalWhitelist, Whitelist externalWhitelist,            CordovaPreferences preferences) {        if (this.cordova != null) {            throw new IllegalStateException();        }        this.cordova = cordova;        this.viewClient = webViewClient;        this.chromeClient = webChromeClient;        this.internalWhitelist = internalWhitelist;        this.externalWhitelist = externalWhitelist;        this.preferences = preferences;        super.setWebChromeClient(webChromeClient);        super.setWebViewClient(webViewClient);        pluginManager = new PluginManager(this, this.cordova, pluginEntries);        bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));        resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);        pluginManager.addService("App", "org.apache.cordova.App");        initWebViewSettings();        exposeJsInterface();    }
      初始化了webViewClient,webChromeClient;

      初始化了PluginManager(那么这个是做什么呢?后面分析),并且注册了配置的Plugin;

      初始化了一个CordovaBridge(bridge变量);

      初始化了webview的设置;

      最终和JS接口关联起来,exposeJsInterface();

CordovaChromeClient分析

      第一步,上关键源码:

public class CordovaChromeClient extends WebChromeClient {    public static final int FILECHOOSER_RESULTCODE = 5173;    private String TAG = "CordovaLog";    private long MAX_QUOTA = 100 * 1024 * 1024;    protected CordovaInterface cordova;    protected CordovaWebView appView;    // the video progress view    private View mVideoProgressView;        // File Chooser    public ValueCallback<Uri> mUploadMessage;        @Deprecated    public CordovaChromeClient(CordovaInterface cordova) {        this.cordova = cordova;    }    public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {        this.cordova = ctx;        this.appView = app;    }    @Deprecated    public void setWebView(CordovaWebView view) {        this.appView = view;    }    /**     * Tell the client to display a javascript alert dialog.     *     * @param view     * @param url     * @param message     * @param result     * @see Other implementation in the Dialogs plugin.     */    @Override    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {        AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());        dlg.setMessage(message);        dlg.setTitle("Alert");        //Don't let alerts break the back button        dlg.setCancelable(true);        dlg.setPositiveButton(android.R.string.ok,                new AlertDialog.OnClickListener() {                    public void onClick(DialogInterface dialog, int which) {                        result.confirm();                    }                });        dlg.setOnCancelListener(                new DialogInterface.OnCancelListener() {                    public void onCancel(DialogInterface dialog) {                        result.cancel();                    }                });        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {            //DO NOTHING            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {                if (keyCode == KeyEvent.KEYCODE_BACK)                {                    result.confirm();                    return false;                }                else                    return true;            }        });        dlg.show();        return true;    }    /**     * Tell the client to display a confirm dialog to the user.     *     * @param view     * @param url     * @param message     * @param result     * @see Other implementation in the Dialogs plugin.     */    @Override    public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {        AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());        dlg.setMessage(message);        dlg.setTitle("Confirm");        dlg.setCancelable(true);        dlg.setPositiveButton(android.R.string.ok,                new DialogInterface.OnClickListener() {                    public void onClick(DialogInterface dialog, int which) {                        result.confirm();                    }                });        dlg.setNegativeButton(android.R.string.cancel,                new DialogInterface.OnClickListener() {                    public void onClick(DialogInterface dialog, int which) {                        result.cancel();                    }                });        dlg.setOnCancelListener(                new DialogInterface.OnCancelListener() {                    public void onCancel(DialogInterface dialog) {                        result.cancel();                    }                });        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {            //DO NOTHING            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {                if (keyCode == KeyEvent.KEYCODE_BACK)                {                    result.cancel();                    return false;                }                else                    return true;            }        });        dlg.show();        return true;    }    /**     * Tell the client to display a prompt dialog to the user.     * If the client returns true, WebView will assume that the client will     * handle the prompt dialog and call the appropriate JsPromptResult method.     *     * Since we are hacking prompts for our own purposes, we should not be using them for     * this purpose, perhaps we should hack console.log to do this instead!     *     * @see Other implementation in the Dialogs plugin.     */    @Override    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.        String handledRet = appView.bridge.promptOnJsPrompt(origin, message, defaultValue);        if (handledRet != null) {            result.confirm(handledRet);        } else {            // Returning false would also show a dialog, but the default one shows the origin (ugly).            final JsPromptResult res = result;            AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());            dlg.setMessage(message);            final EditText input = new EditText(this.cordova.getActivity());            if (defaultValue != null) {                input.setText(defaultValue);            }            dlg.setView(input);            dlg.setCancelable(false);            dlg.setPositiveButton(android.R.string.ok,                    new DialogInterface.OnClickListener() {                        public void onClick(DialogInterface dialog, int which) {                            String usertext = input.getText().toString();                            res.confirm(usertext);                        }                    });            dlg.setNegativeButton(android.R.string.cancel,                    new DialogInterface.OnClickListener() {                        public void onClick(DialogInterface dialog, int which) {                            res.cancel();                        }                    });            dlg.show();        }        return true;    }   ...............}
     继承自 WebChromClient 的类,重写了其中的 onJsAlert,onJsConfirm,onJsPrompt 等方法。

WebChromClient 原来这三个onJsXX方法的作用是:实现JavaScript中的警示对话框、确认对话框和提示对话框。

查看onJsAlert,onJsConfirm的代码块,其实就是把对话框用android代码实现,而不是使用js的对话框。

而在 onJsPrompt() 方法中,在把对话框实现之前,做了一堆操作。因此,值得我们专门的关注其代码

/**     * Tell the client to display a prompt dialog to the user.     * If the client returns true, WebView will assume that the client will     * handle the prompt dialog and call the appropriate JsPromptResult method.     *     * Since we are hacking prompts for our own purposes, we should not be using them for     * this purpose, perhaps we should hack console.log to do this instead!     *     * @see Other implementation in the Dialogs plugin.     */    @Override    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.        String handledRet = appView.bridge.promptOnJsPrompt(origin, message, defaultValue);        if (handledRet != null) {            result.confirm(handledRet);        } else {            // Returning false would also show a dialog, but the default one shows the origin (ugly).            final JsPromptResult res = result;            AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());            dlg.setMessage(message);            final EditText input = new EditText(this.cordova.getActivity());            if (defaultValue != null) {                input.setText(defaultValue);            }            dlg.setView(input);            dlg.setCancelable(false);            dlg.setPositiveButton(android.R.string.ok,                    new DialogInterface.OnClickListener() {                        public void onClick(DialogInterface dialog, int which) {                            String usertext = input.getText().toString();                            res.confirm(usertext);                        }                    });            dlg.setNegativeButton(android.R.string.cancel,                    new DialogInterface.OnClickListener() {                        public void onClick(DialogInterface dialog, int which) {                            res.cancel();                        }                    });            dlg.show();        }        return true;    }
    看来,这里实现了 PhoneGap 中 Java 端和浏览器端通讯的关键一步:实现 JavaScript 与 Java 端通讯的原理是 JavaScript 利用 prompt 来传递调用信息的数据,在 onJsPrompt 中,重写的方法截获了这些数据,调用
String handledRet = appView.bridge.promptOnJsPrompt(origin, message, defaultValue);
    这里调用了appview(cordovawebview)中初始化的CordovaBridge的promptOnJsPrompt方法,进去看看做了什么事情:

public String promptOnJsPrompt(String origin, String message, String defaultValue) {        if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {            JSONArray array;            try {                array = new JSONArray(defaultValue.substring(4));                int bridgeSecret = array.getInt(0);                String service = array.getString(1);                String action = array.getString(2);                String callbackId = array.getString(3);                String r = jsExec(bridgeSecret, service, action, callbackId, message);                return r == null ? "" : r;            } catch (JSONException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }            return "";        }        // Sets the native->JS bridge mode.         else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {            try {                int bridgeSecret = Integer.parseInt(defaultValue.substring(16));                jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));            } catch (NumberFormatException e){                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }            return "";        }        // Polling for JavaScript messages         else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {            int bridgeSecret = Integer.parseInt(defaultValue.substring(9));            try {                String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));                return r == null ? "" : r;            } catch (IllegalAccessException e) {                e.printStackTrace();            }            return "";        }        else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {            // Protect against random iframes being able to talk through the bridge.            // Trust only file URLs and the start URL's domain.            // The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.            if (origin.startsWith("file:") || (origin.startsWith("http") && loadedUrl.startsWith(origin))) {                // Enable the bridge                int bridgeMode = Integer.parseInt(defaultValue.substring(9));                jsMessageQueue.setBridgeMode(bridgeMode);                // Tell JS the bridge secret.                int secret = generateBridgeSecret();                return ""+secret;            } else {                Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);            }            return "";        }        return null;    }
   完成了对数据格式等等分析后,按照要求进行具体的调用
String r = jsExec(bridgeSecret, service, action, callbackId, message);
      
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {        if (!verifySecret("exec()", bridgeSecret)) {            return null;        }        // If the arguments weren't received, send a message back to JS.  It will switch bridge modes and try again.  See CB-2666.        // We send a message meant specifically for this case.  It starts with "@" so no other message can be encoded into the same string.        if (arguments == null) {            return "@Null arguments.";        }        jsMessageQueue.setPaused(true);        try {            // Tell the resourceApi what thread the JS is running on.            CordovaResourceApi.jsThread = Thread.currentThread();            pluginManager.exec(service, action, callbackId, arguments);            String ret = null;            if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {                ret = jsMessageQueue.popAndEncode(false);            }            return ret;        } catch (Throwable e) {            e.printStackTrace();            return "";        } finally {            jsMessageQueue.setPaused(false);        }    }
     看关键代码:

pluginManager.exec(service, action, callbackId, arguments);
   这里就调用了pluginManager去调用具体的plugin(记得初始化的时候会注册配置文件里面的plugin,然后根据名字来查找调用).

CordovaWebView 分析

   再次回到CordovaWebView里面看看,到底JS是如何和我们Java端关联起来的?

    public void init(CordovaInterface cordova, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient,            List<PluginEntry> pluginEntries, Whitelist internalWhitelist, Whitelist externalWhitelist,            CordovaPreferences preferences) {        if (this.cordova != null) {            throw new IllegalStateException();        }        this.cordova = cordova;        this.viewClient = webViewClient;        this.chromeClient = webChromeClient;        this.internalWhitelist = internalWhitelist;        this.externalWhitelist = externalWhitelist;        this.preferences = preferences;        super.setWebChromeClient(webChromeClient);        super.setWebViewClient(webViewClient);        pluginManager = new PluginManager(this, this.cordova, pluginEntries);        bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));        resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);        pluginManager.addService("App", "org.apache.cordova.App");        initWebViewSettings();        exposeJsInterface();    }
     WebSettings做一些设置,包括:启用js,设置数据库,设置缓存。
private void initWebViewSettings() {        this.setInitialScale(0);        this.setVerticalScrollBarEnabled(false);        // TODO: The Activity is the one that should call requestFocus().        if (shouldRequestFocusOnInit()) {this.requestFocusFromTouch();}// Enable JavaScript        WebSettings settings = this.getSettings();        settings.setJavaScriptEnabled(true);//关键点,使能启动JS,能够执行JS脚本        settings.setJavaScriptCanOpenWindowsAutomatically(true);//使能Js自动打开窗口        settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);                // Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)        try {            Method gingerbread_getMethod =  WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });                        String manufacturer = Build.MANUFACTURER;            Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);            if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&                    Build.MANUFACTURER.contains("HTC"))            {                gingerbread_getMethod.invoke(settings, true);            }        } catch (NoSuchMethodException e) {            Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");        } catch (IllegalArgumentException e) {            Log.d(TAG, "Doing the NavDump failed with bad arguments");        } catch (IllegalAccessException e) {            Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");        } catch (InvocationTargetException e) {            Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");        }        //We don't save any form data in the application        settings.setSaveFormData(false);        settings.setSavePassword(false);                // Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist        // while we do this        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)            Level16Apis.enableUniversalAccess(settings);        // Enable database        // We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16        String databasePath = getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();        settings.setDatabaseEnabled(true);        settings.setDatabasePath(databasePath);                        //Determine whether we're in debug or release mode, and turn on Debugging!        ApplicationInfo appInfo = getContext().getApplicationContext().getApplicationInfo();        if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&            Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {            enableRemoteDebugging();        }                settings.setGeolocationDatabasePath(databasePath);        // Enable DOM storage        settings.setDomStorageEnabled(true);        // Enable built-in geolocation        settings.setGeolocationEnabled(true);                // Enable AppCache        // Fix for CB-2282        settings.setAppCacheMaxSize(5 * 1048576);        settings.setAppCachePath(databasePath);        settings.setAppCacheEnabled(true);                // Fix for CB-1405        // Google issue 4641        settings.getUserAgentString();                IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);        if (this.receiver == null) {            this.receiver = new BroadcastReceiver() {                @Override                public void onReceive(Context context, Intent intent) {                    getSettings().getUserAgentString();                }            };            getContext().registerReceiver(this.receiver, intentFilter);        }        // end CB-1405    }
      再来看看exposeJsInterface()函数:

private void exposeJsInterface() {        if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {            Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");            // Bug being that Java Strings do not get converted to JS strings automatically.            // This isn't hard to work-around on the JS side, but it's easier to just            // use the prompt bridge instead.            return;                    }         this.addJavascriptInterface(new ExposedJsApi(bridge), "_cordovaNative");    }
      把exposedJsApi作为JS的接口添加到webView中,名字为“_cordovaNative”。JS端就是通过这个名字来调用Java端函数。

   之前在CordovaChromeClient中调用onJsPrompt,之后就会调用promtOnJsPrompt,然后就调用CordovaBridge的jsExec方法,然后调用到pluginManger.exec(service, action, callbackId, arguments),那么去看看这里面具体做了什么?

   

    /**     * Receives a request for execution and fulfills it by finding the appropriate     * Java class and calling it's execute method.     *     * PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded     * string is returned that will indicate if any errors have occurred when trying to find     * or execute the class denoted by the clazz argument.     *     * @param service       String containing the service to run     * @param action        String containing the action that the class is supposed to perform. This is     *                      passed to the plugin execute method and it is up to the plugin developer     *                      how to deal with it.     * @param callbackId    String containing the id of the callback that is execute in JavaScript if     *                      this is an async plugin call.     * @param rawArgs       An Array literal string containing any arguments needed in the     *                      plugin execute method.     */    public void exec(final String service, final String action, final String callbackId, final String rawArgs) {        CordovaPlugin plugin = getPlugin(service);        if (plugin == null) {            Log.d(TAG, "exec() call to unknown plugin: " + service);            PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);            app.sendPluginResult(cr, callbackId);            return;        }        CallbackContext callbackContext = new CallbackContext(callbackId, app);        try {            long pluginStartTime = System.currentTimeMillis();            boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);            long duration = System.currentTimeMillis() - pluginStartTime;            if (duration > SLOW_EXEC_WARNING_THRESHOLD) {                Log.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");            }            if (!wasValidAction) {                PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);                callbackContext.sendPluginResult(cr);            }        } catch (JSONException e) {            PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);            callbackContext.sendPluginResult(cr);        } catch (Exception e) {            Log.e(TAG, "Uncaught exception from plugin", e);            callbackContext.error(e.getMessage());        }    }
    从代码看,通过service名字获取一个plugin(里面怎么获取到的先不管),创建一个CallbackContext对象,然后调用具体的plugin.execute(其实我们利用cordova开发更多的是通过继承plugin来开发我们自己的插件).根据执行的返回结果设置PluginResult.

    第一步:获得plugin

CordovaPlugin plugin = getPlugin(service);
/**     * Get the plugin object that implements the service.     * If the plugin object does not already exist, then create it.     * If the service doesn't exist, then return null.     *     * @param service       The name of the service.     * @return              CordovaPlugin or null     */    public CordovaPlugin getPlugin(String service) {        CordovaPlugin ret = pluginMap.get(service);        if (ret == null) {            PluginEntry pe = entryMap.get(service);            if (pe == null) {                return null;            }            if (pe.plugin != null) {                ret = pe.plugin;            } else {                ret = instantiatePlugin(pe.pluginClass);            }            ret.privateInitialize(ctx, app, app.getPreferences());            pluginMap.put(service, ret);        }        return ret;    }
   首先从pluginMap中取,如果不存在就从entryMap中去取,取不到就返回,取到就实例化一下,并且初始化一下,并放入pluginMap中。那么我们就一个一个来分析:

   1)pluginMap,entryMap在哪里初始化?关键点:看看cordovaWebview init时对pluginManager的初始化

private final HashMap<String, PluginEntry> entryMap = new HashMap<String, PluginEntry>();

private final HashMap<String, CordovaPlugin> pluginMap = new HashMap<String, CordovaPlugin>();
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, List<PluginEntry> pluginEntries) {        this.ctx = cordova;        this.app = cordovaWebView;        if (pluginEntries == null) {            ConfigXmlParser parser = new ConfigXmlParser();            parser.parse(ctx.getActivity());            pluginEntries = parser.getPluginEntries();        }        setPluginEntries(pluginEntries);    }
public void setPluginEntries(List<PluginEntry> pluginEntries) {        this.onPause(false);        this.onDestroy();        pluginMap.clear();        urlMap.clear();        for (PluginEntry entry : pluginEntries) {            addService(entry);        }    }
public void addService(PluginEntry entry) {        this.entryMap.put(entry.service, entry);        List<String> urlFilters = entry.getUrlFilters();        if (urlFilters != null) {            urlMap.put(entry.service, urlFilters);        }        if (entry.plugin != null) {            entry.plugin.privateInitialize(ctx, app, app.getPreferences());            pluginMap.put(entry.service, entry.plugin);        }    }
从这里就可以看到,所有plugin信息都会存入entryMap,只有经过了初始化的plugin才会放入到pluginMap。

那到底是怎么初始化plugin的呢?

public final void privateInitialize(CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {        assert this.cordova == null;        this.cordova = cordova;        this.webView = webView;        this.preferences = preferences;        initialize(cordova, webView);        pluginInitialize();    }
在cordovaPlugin中,initialize和pluginInitialize都是空实现,我们可以在我们自定义的plugin中覆写.

到此plugin初始化完毕.

   2)我们自定义的plugin一般写法?

public class xxxextends CordovaPlugin {    /**     * Constructor.     */    public xxx() {    }    /**     * Sets the context of the Command. This can then be used to do things like     * get file paths associated with the Activity.     *     * @param cordova The context of the main Activity.     * @param webView The CordovaWebView Cordova is running in.     */    public void initialize(CordovaInterface cordova, CordovaWebView webView) {        super.initialize(cordova, webView);        .......    }    /**     * Executes the request and returns PluginResult.     *     * @param action            The action to execute.     * @param args              JSONArry of arguments for the plugin.     * @param callbackContext   The callback id used when calling back into JavaScript.     * @return                  True if the action was valid, false if not.     */    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {        if (action.equals("getDeviceInfo")) {            JSONObject r = new JSONObject();            //省略实现            callbackContext.success(r);//回调结果,表示正常处理        }        else {            return false;        }        return true;    }}
     3)上面说到,当pluginMap中没有就需要调用entryMap去取然后实例化,那么怎么实例化plugin?

/**     * Create a plugin based on class name.     */    private CordovaPlugin instantiatePlugin(String className) {        CordovaPlugin ret = null;        try {            Class<?> c = null;            if ((className != null) && !("".equals(className))) {                c = Class.forName(className);            }            if (c != null & CordovaPlugin.class.isAssignableFrom(c)) {                ret = (CordovaPlugin) c.newInstance();            }        } catch (Exception e) {            e.printStackTrace();            System.out.println("Error adding plugin " + className + ".");        }        return ret;    }
    其实就是根据Java的反射机制,通过className来获得class(Class.forName),然后调用class.newInstance()方法。

    4)当我们plugin处理完就会返回一个成功的标志给webview.


至此:整个js调用本地的过程已经清楚了:

1. 在CordovaChromeClient的onJsPrompt方法调用ExposedJsApi的exec方法。

2. ExposedJsApi的exec,通过找到的plugin调用java端的execute方法并返回。

3. 返回结果通知给CordovaWebView。





0 0