Cordova原始碼深入分析-第二講
阿新 • • 發佈:2019-01-03
上一篇文章中介紹了cordova外掛初始化流程,本文開始介紹,任何呼叫到java程式碼
上文已經介紹到呼叫
navigator.camera.getPicture(onSuccess, onFail, {
quality: 50,
sourceType: Camera.PictureSourceType.CAMERA,
destinationType: Camera.DestinationType.FILE_URI
});
的時候,會呼叫Camera.js的類,本講繼續往下分析Camera.js
cameraExport.getPicture = function (successCallback, errorCallback, options) { console.log('takePicture getPicture ') argscheck.checkArgs('fFO', 'Camera.getPicture', arguments); options = options || {}; var getValue = argscheck.getValue; var quality = getValue(options.quality, 50); var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI); var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA); var targetWidth = getValue(options.targetWidth, -1); var targetHeight = getValue(options.targetHeight, -1); var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG); var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE); var allowEdit = !!options.allowEdit; var correctOrientation = !!options.correctOrientation; var saveToPhotoAlbum = !!options.saveToPhotoAlbum; var popoverOptions = getValue(options.popoverOptions, null); var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK); var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection]; console.log('takePicture click 3') exec(successCallback, errorCallback, 'Camera', 'takePicture', args);//關鍵程式碼 // XXX: commented out // return new CameraPopoverHandle(); };
上面引入了exec方法
var exec = require('cordova/exec');
定義實在cordova.js中定義的
function androidExec(success, fail, service, action, args) { if (bridgeSecret < 0) { // If we ever catch this firing, we'll need to queue up exec()s // and fire them once we get a secret. For now, I don't think // it's possible for exec() to be called since plugins are parsed but // not run until until after onNativeReady. throw new Error('exec() called without bridgeSecret'); } // Set default bridge modes if they have not already been set. // By default, we use the failsafe, since addJavascriptInterface breaks too often if (jsToNativeBridgeMode === undefined) { androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT); } // If args is not provided, default to an empty array args = args || []; // Process any ArrayBuffers in the args into a string. for (var i = 0; i < args.length; i++) { if (utils.typeName(args[i]) == 'ArrayBuffer') { args[i] = base64.fromArrayBuffer(args[i]); } } var callbackId = service + cordova.callbackId++, argsJson = JSON.stringify(args); if (success || fail) { cordova.callbacks[callbackId] = {success:success, fail:fail};//這個後面有用 } var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);//繼續深入程式碼分析 // If argsJson was received by Java as null, try again with the PROMPT bridge mode. // This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666. if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") { androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT); androidExec(success, fail, service, action, args); androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT); } else if (msgs) { messagesFromNative.push(msgs); // Always process async to avoid exceptions messing up stack. nextTick(processMessages); } }
下面我們看看nativeApiProvider是從哪裡來的
define("cordova/android/nativeapiprovider", function(require, exports, module) { /** * Exports the ExposedJsApi.java object if available, otherwise exports the PromptBasedNativeApi. */ var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');//這裡是關鍵 var currentApi = nativeApi; module.exports = { get: function() { return currentApi; }, setPreferPrompt: function(value) { currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi; }, // Used only by tests. set: function(value) { currentApi = value; } }; });
看到_cordovaNative這個是和native通訊的橋
在SystemWebViewEngine.java中(這裡在介紹java端程式碼的時候,會詳細介紹,目前只有一個印象即可)
@SuppressLint("AddJavascriptInterface")
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
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;
}
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");//與js建立通訊
}
到java端之後,就調入到SystemExposedJsApi.java中的exec方法 @JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
接著呼叫到CordovaBridge.java中
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.java中
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
LOG.d(TAG, "exec() call stack: " + Log.getStackTraceString(new Throwable()));
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());
}
}
下面以相機外掛為例:CameraLauncher.java,後面就是吊起照相機的邏輯
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
//Adding an API to CoreAndroid to get the BuildConfigValue
//This allows us to not make this a breaking change to embedding
this.applicationId = (String) BuildHelper.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID");
this.applicationId = preferences.getString("applicationId", this.applicationId);
if (action.equals("takePicture")) {
this.srcType = CAMERA;
this.destType = FILE_URI;
this.saveToPhotoAlbum = false;
this.targetHeight = 0;
this.targetWidth = 0;
this.encodingType = JPEG;
this.mediaType = PICTURE;
this.mQuality = 50;
//Take the values from the arguments if they're not already defined (this is tricky)
this.destType = args.getInt(1);
this.srcType = args.getInt(2);
this.mQuality = args.getInt(0);
this.targetWidth = args.getInt(3);
this.targetHeight = args.getInt(4);
this.encodingType = args.getInt(5);
this.mediaType = args.getInt(6);
this.allowEdit = args.getBoolean(7);
this.correctOrientation = args.getBoolean(8);
this.saveToPhotoAlbum = args.getBoolean(9);
// If the user specifies a 0 or smaller width/height
// make it -1 so later comparisons succeed
if (this.targetWidth < 1) {
this.targetWidth = -1;
}
if (this.targetHeight < 1) {
this.targetHeight = -1;
}
// We don't return full-quality PNG files. The camera outputs a JPEG
// so requesting it as a PNG provides no actual benefit
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
!this.correctOrientation && this.encodingType == PNG && this.srcType == CAMERA) {
this.encodingType = JPEG;
}
try {
if (this.srcType == CAMERA) {
this.callTakePicture(destType, encodingType);
}
else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
// FIXME: Stop always requesting the permission
if(!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
this.getImage(this.srcType, destType, encodingType);
}
}
}
catch (IllegalArgumentException e)
{
callbackContext.error("Illegal Argument Exception");
PluginResult r = new PluginResult(PluginResult.Status.ERROR);
callbackContext.sendPluginResult(r);
return true;
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
callbackContext.sendPluginResult(r);
return true;
}
return false;
}