开发环境:

Android Studio,Cordova5.3.3

问题描述:

页面1:

<html>
<script type="text/javascript" src="cordova.js"></script>
<script>
	document.addEventListener('deviceready', function(){
		document.addEventListener("backbutton", onBackKeyDown, false);
	},false);

	function onBackKeyDown(){
		//
	}
</script>
</html>

页面2:

<html>
<script type="text/javascript" src="cordova.js"></script>
<script>
	document.addEventListener('deviceready', function(){
		document.addEventListener("backbutton", onBackKeyDown, false);
	},false);

	function onBackKeyDown(){
		//
	}
</script>
</html>

当我在第一个界面点击返回键,程序正常响应,跳转到第二个页面时,再次点击返回键无效果,甚至连音量加减键都不能使用。然而第一个界面不触发返回事件,跳转第二个界面正常响应。网上也有朋友遇到类似的问题,但是均没有得到解决。

问题分析:

还是相信那句话,在源码面前没有秘密,所以我从源码着手查找问题原因及解决办法。

首先我从cordova.js进行分析,因为我们是通过这个文件注册backbutton事件的。cordova.js 1532行:

    var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';

    // Inject a listener for the backbutton on the document.
    var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
    backButtonChannel.onHasSubscribersChange = function() {
        // If we just attached the first handler or detached the last handler,
        // let native know we need to override the back button.
        exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
    };

可以看出4.x版本以上,cordova.js是采用CoreAndroid进行返回键注册的,调用的方法是overrideBackbutton方法。

下面看CoreAndroid.java,231行:

    public void overrideBackbutton(boolean override) {
        LOG.i("App", "WARNING: Back Button Default Behavior will be overridden.  The backbutton event will be fired!");
        webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
    }

可以看出,已将backbutton事件注册了,具体注册源码可自行查看。

下面看下返回事件是如何从native传到webview的。

public Boolean onDispatchKeyEvent(KeyEvent event) - CordovaWebViewImpl.java

private void sendJavascriptEvent(String event) - CordovaWebViewImpl.java

public void fireJavascriptEvent(String action) - CoreAndroid.java

private void sendEventMessage(String action) - CoreAndroid.java

public void sendPluginResult(PluginResult pluginResult) - CallbackContext.java

webView.sendPluginResult(pluginResult, callbackId);//最终就是调用这句代码向webview发送backbutton事件的。

当我跟踪调试代码的时候时,我发现第二次未响应的时候,上面的代码确实执行了,但callbackId还是之前的值。callbackId是每次cordova.js初始化的时候生成的,并且我还发现,每次切换界面的时候,CoreAndroid等所有插件都是重新初始化的,一会儿拿出证据来。下面看代码:

private void sendJavascriptEvent(String event) {
    if (appPlugin == null) {
        appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
    }    if (appPlugin == null) {
        LOG.w(TAG, "Unable to fire event without existing plugin");
        return;
    }
    appPlugin.fireJavascriptEvent(event);
}

这里可以看出,只要调用过一次sendJavascriptEvent()方法后,appPlugin对象就已经存在了CordovaWebViewImpl对象里面了,那么上面的出现问题的原因也就解释清楚了。

解决办法:

在CordovaWebViewImpl.java的public void loadUrlIntoView(final String url, boolean recreatePlugins)中,

public void loadUrlIntoView(final String url, boolean recreatePlugins) {
    LOG.d(TAG, ">>> loadUrl(" + url + ")");
    if (url.equals("about:blank") || url.startsWith("javascript:")) {
        engine.loadUrl(url, false);
        return;
    }

    recreatePlugins = recreatePlugins || (loadedUrl == null);
    if (recreatePlugins) {
        // Don't re-initialize on first load.
        if (loadedUrl != null) {
            resetCoreAndroidPlugin();
            pluginManager.init();
        }
        loadedUrl = url;
    }

此处可以看出每次加载页面时,使用pluginManager.init();方法来初始化插件。

这里我是用我自己添加的resetCoreAndroidPlugin()方法,来重置CordovaWebViewImpl里面的appPlugin变量,resetCoreAndroidPlugin()方法实现如下

    public void resetCoreAndroidPlugin(){
        appPlugin = null;
    }

我在github上fork了cordova-android源码,已经pull request, 点击查看

文笔不好,如有不明白之处可以共同探讨。才疏学浅,如有不对的地方,请指正!