手机音量键的监听原理
本篇涉及cordova/platform模块和CoreAdnroid.java插件的讲解,通过该模块和插件实现volumeup,volumedown,backbtn,searchbtn的监听。webview通过监听原生按键事件在将事件动作传递给js执行js注册的方法达到监听目的。
步骤1:webview注册原生按键监听
-----------------------------------------以下代码见SystemWebView.java------------------------------------------
...此处省略部分代码...
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
//CordovaWebViewImpl.java对具体的事件动作分发
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
if (ret != null) {
return ret.booleanValue();
}
return super.dispatchKeyEvent(event);
}
...此处省略部分代码...
------------------------------------------SystemWebView.java-------------------------------------------------
------------------------------------------CordovaWebViewImpl-------------------------------------------------
@Override
public Boolean onDispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK;
//按键摁下
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (isBackButton && mCustomView != null) {
return true;
} else if (boundKeyCodes.contains(keyCode)) {
return true;
} else if (isBackButton) {
return engine.canGoBack();
}
//按键松开
} else if (event.getAction() == KeyEvent.ACTION_UP) {
if (isBackButton && mCustomView != null) {
hideCustomView();
return true;
//boundKeyCodes为HashSet对象,当js注册了volumeup等监听后,会将对应keycode存在该变量内
} else if (boundKeyCodes.contains(keyCode)) {
String eventName = null;
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
eventName = "volumedownbutton";
break;
case KeyEvent.KEYCODE_VOLUME_UP:
eventName = "volumeupbutton";
break;
case KeyEvent.KEYCODE_SEARCH:
eventName = "searchbutton";
break;
case KeyEvent.KEYCODE_MENU:
eventName = "menubutton";
break;
case KeyEvent.KEYCODE_BACK:
eventName = "backbutton";
break;
}
if (eventName != null) {
sendJavascriptEvent(eventName);//发送事件给js
return true;
}
} else if (isBackButton) {
return engine.goBack();
}
}
return null;
}
------------------------------------------CordovaWebViewImpl.java---------------------------------------
--------------------------------------------------CoreAndroid.java--------------------------------
...此处省略部分代码...
/**
该方法作用是将对应button比如volumeup,volumedown灯添加到boundKeyCodes(CordovaWebViewImpl.java)当中
*/
public void overrideButton(String button, boolean override) {
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
if (button.equals("volumeup")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
}
else if (button.equals("volumedown")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
}
else if (button.equals("menubutton")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
}
}
...此处省略部分代码...
/**
* 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();
}
...此处省略部分代码...
//js在执行exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1])
//该动作时候将由该方法接收动作,对应button比如volumeup,volumedown等将添加到boundKeyCodes(CordovaWebViewImpl.java)当中
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();
}
else if (action.equals("messageChannel")) {
synchronized(messageChannelLock) {
messageChannel = callbackContext;
if (pendingResume != null) {
sendEventMessage(pendingResume);
pendingResume = null;
}
}
return true;
}
callbackContext.sendPluginResult(new PluginResult(status, result));
return true;
} catch (JSONException e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
return false;
}
}
...此处省略部分代码...
--------------------------------------------------CoreAndroid.java----------------------------------
步骤2:js注册volumeup等事件监听
document.addEventListener("volumeupbutton", listener, false);
当我们给volumeup绑定时候实际上触发了platform模块的以下方法
function bindButtonChannel(buttonName) {
//创建volumeupbutton隧道
var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
//给volumeupbutton绑定监听器时候触发了该法方法
volumeButtonChannel.onHasSubscribersChange = function() {
//该方法通知native将buttonName(volumeup,volumedown)变量值加入到native变量boundKeyCodes对象当中
//之后native监听按键的时候总会先判断当前的按键是否在于该对象当中,有才会继续做处理。
//APP_PLUGIN_NAME = 'CoreAndroid'即CoreAndroid.java插件
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
};
}
bindButtonChannel('volumeup');
bindButtonChannel('volumedown');
步骤3:建立接收按键事件的回调通知
在platform模块中可以找到如下代码:
//cordova就绪后触发了两个以下回调
channel.onCordovaReady.subscribe(function() {
//参数:成功回调方法,失败回调方法,插件名,执行动作,参数
//该方法实质作用是注册一个回调上下文即CallbackContext,并保存于CoreAndroid.java对象的messageChannel变量中,
//之后原生触发的按键动作都将同过该回调上下文messageChannel来反馈给js
exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
//通知native显示界面
exec(null, null, APP_PLUGIN_NAME, "show", []);
});
function onMessageFromNative(msg) {
var cordova = require('cordova');
var action = msg.action;
switch (action)
{
// Button events
case 'backbutton':
case 'menubutton':
case 'searchbutton':
// App life cycle events
case 'pause':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);//触发注册的volumeupbutton等监听器
break;
case 'resume':
if(arguments.length > 1 && msg.pendingResult) {
if(arguments.length === 2) {
msg.pendingResult.result = arguments[1];
} else {
// The plugin returned a multipart message
var res = [];
for(var i = 1; i < arguments.length; i++) {
res.push(arguments[i]);
}
msg.pendingResult.result = res;
}
// Save the plugin result so that it can be delivered to the js
// even if they miss the initial firing of the event
lastResumeEvent = msg;
}
cordova.fireDocumentEvent(action, msg);
break;
default:
throw new Error('Unknown event action ' + action);
}
}