最近看了一本书《Building Android Apps with HTML CSS and JavaScript》,顾名思义就是用开发web的方式来开发Android的APP,其中倒不是web的开发技术最吸我,而是这样的一种解决方案。像我们现在的手持设备种类这么多,主流的不外乎Android,Iphone,Ipad等等,如果要对每一种平台都开发一个相应的APP版本,代价太大。而基于浏览器的web app就容易解决这个问题,只需开发一次,便可以通用部署。
上面即是使用web技术来开发本地iphone app的一幅运行图,关于web的开发技术,这里就不多说了,这里重点提及Phonegap,利用它我们就可以把web app转换成各种移动平台的APP。
PhoneGap is an HTML5 app platform that allows you to author native applications with web technologies and get access to APIs and app stores. PhoneGap leverages web technologies developers already know best... HTML and JavaScript
这里简单介绍一下PhoneGap,它对各个移动平台的API进行了一次封装,屏蔽了每种移动平台的具体细节,而设计了一个公共的API层,对开发者来说,只要调用统一的API来进行开发就可以了。
把PhoneGap的源代码下载来通读了一下,了解了一下背后的实现原理,下面以Camera的功能为例来进行说明。
API 开发文档请参照 http://docs.phonegap.com/en/1.1.0/index.html,其中有关Camera的开发API请参照http://docs.phonegap.com/en/1.1.0/phonegap_camera_camera.md.html#Camera,我们的代码就可以写成
1 2 3 4 5 6 7 8 | navigator.camera.getPicture(onSuccess, onFail, { quality: 50 }); function onSuccess(imageData) { var image = document.getElementById('myImage'); image.src = "data:image/jpeg;base64," + imageData; } function onFail(message) { alert('Failed because: ' + message); } |
navigator.camera.getPicture(onSuccess, onFail, { quality: 50 }); function onSuccess(imageData) { var image = document.getElementById('myImage'); image.src = "data:image/jpeg;base64," + imageData; } function onFail(message) { alert('Failed because: ' + message); }
是不是很简单,具体后台平台的差异我们根本不需要来进行区别,全部由PhoneGap来给处理了,那么它是怎么处理的呢,继续往下看。
先来看看JS端的API实现,它主要是定义了web层开发的接口,也即我们上面例子中的camera.getPicture 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | /** * Gets a picture from source defined by "options.sourceType", and returns the * image as defined by the "options.destinationType" option. * The defaults are sourceType=CAMERA and destinationType=DATA_URL. * * @param {Function} successCallback * @param {Function} errorCallback * @param {Object} options */ Camera.prototype.getPicture = function(successCallback, errorCallback, options) { // successCallback required if (typeof successCallback !== "function") { console.log("Camera Error: successCallback is not a function"); return; } // errorCallback optional if (errorCallback && (typeof errorCallback !== "function")) { console.log("Camera Error: errorCallback is not a function"); return; } if (options === null || typeof options === "undefined") { options = {}; } if (options.quality === null || typeof options.quality === "undefined") { options.quality = 80; } if (options.maxResolution === null || typeof options.maxResolution === "undefined") { options.maxResolution = 0; } if (options.destinationType === null || typeof options.destinationType === "undefined") { options.destinationType = Camera.DestinationType.DATA_URL; } if (options.sourceType === null || typeof options.sourceType === "undefined") { options.sourceType = Camera.PictureSourceType.CAMERA; } if (options.encodingType === null || typeof options.encodingType === "undefined") { options.encodingType = Camera.EncodingType.JPEG; } if (options.mediaType === null || typeof options.mediaType === "undefined") { options.mediaType = Camera.MediaType.PICTURE; } if (options.targetWidth === null || typeof options.targetWidth === "undefined") { options.targetWidth = -1; } else if (typeof options.targetWidth == "string") { var width = new Number(options.targetWidth); if (isNaN(width) === false) { options.targetWidth = width.valueOf(); } } if (options.targetHeight === null || typeof options.targetHeight === "undefined") { options.targetHeight = -1; } else if (typeof options.targetHeight == "string") { var height = new Number(options.targetHeight); if (isNaN(height) === false) { options.targetHeight = height.valueOf(); } } PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [options]); }; |
/** * Gets a picture from source defined by "options.sourceType", and returns the * image as defined by the "options.destinationType" option. * The defaults are sourceType=CAMERA and destinationType=DATA_URL. * * @param {Function} successCallback * @param {Function} errorCallback * @param {Object} options */ Camera.prototype.getPicture = function(successCallback, errorCallback, options) { // successCallback required if (typeof successCallback !== "function") { console.log("Camera Error: successCallback is not a function"); return; } // errorCallback optional if (errorCallback && (typeof errorCallback !== "function")) { console.log("Camera Error: errorCallback is not a function"); return; } if (options === null || typeof options === "undefined") { options = {}; } if (options.quality === null || typeof options.quality === "undefined") { options.quality = 80; } if (options.maxResolution === null || typeof options.maxResolution === "undefined") { options.maxResolution = 0; } if (options.destinationType === null || typeof options.destinationType === "undefined") { options.destinationType = Camera.DestinationType.DATA_URL; } if (options.sourceType === null || typeof options.sourceType === "undefined") { options.sourceType = Camera.PictureSourceType.CAMERA; } if (options.encodingType === null || typeof options.encodingType === "undefined") { options.encodingType = Camera.EncodingType.JPEG; } if (options.mediaType === null || typeof options.mediaType === "undefined") { options.mediaType = Camera.MediaType.PICTURE; } if (options.targetWidth === null || typeof options.targetWidth === "undefined") { options.targetWidth = -1; } else if (typeof options.targetWidth == "string") { var width = new Number(options.targetWidth); if (isNaN(width) === false) { options.targetWidth = width.valueOf(); } } if (options.targetHeight === null || typeof options.targetHeight === "undefined") { options.targetHeight = -1; } else if (typeof options.targetHeight == "string") { var height = new Number(options.targetHeight); if (isNaN(height) === false) { options.targetHeight = height.valueOf(); } } PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [options]); };
上段代码中的最后一句就是呼叫本地的Camera模块的takePicture函数,具体的PhoneGap.exec实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | /** * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. * The native side can return: * Synchronous: PluginResult object as a JSON string * Asynchrounous: Empty string "" * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, * depending upon the result of the action. * * @param {Function} success The success callback * @param {Function} fail The fail callback * @param {String} service The name of the service to use * @param {String} action Action to be run in PhoneGap * @param {Array.<String>} [args] Zero or more arguments to pass to the method */ PhoneGap.exec = function(success, fail, service, action, args) { try { var callbackId = service + PhoneGap.callbackId++; if (success || fail) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true])); // If a result was returned if (r.length > 0) { eval("var v="+r+";"); // If status is OK, then return value back to caller if (v.status === PhoneGap.callbackStatus.OK) { // If there is a success callback, then call it now with // returned value if (success) { try { success(v.message); } catch (e) { console.log("Error in success callback: " + callbackId + " = " + e); } // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } return v.message; } // If no result else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } // If error, then display error else { console.log("Error: Status="+v.status+" Message="+v.message); // If there is a fail callback, then call it now with returned value if (fail) { try { fail(v.message); } catch (e1) { console.log("Error in error callback: "+callbackId+" = "+e1); } // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } return null; } } } catch (e2) { console.log("Error: "+e2); } }; |
/** * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. * The native side can return: * Synchronous: PluginResult object as a JSON string * Asynchrounous: Empty string "" * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, * depending upon the result of the action. * * @param {Function} success The success callback * @param {Function} fail The fail callback * @param {String} service The name of the service to use * @param {String} action Action to be run in PhoneGap * @param {Array.<String>} [args] Zero or more arguments to pass to the method */ PhoneGap.exec = function(success, fail, service, action, args) { try { var callbackId = service + PhoneGap.callbackId++; if (success || fail) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true])); // If a result was returned if (r.length > 0) { eval("var v="+r+";"); // If status is OK, then return value back to caller if (v.status === PhoneGap.callbackStatus.OK) { // If there is a success callback, then call it now with // returned value if (success) { try { success(v.message); } catch (e) { console.log("Error in success callback: " + callbackId + " = " + e); } // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } return v.message; } // If no result else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } // If error, then display error else { console.log("Error: Status="+v.status+" Message="+v.message); // If there is a fail callback, then call it now with returned value if (fail) { try { fail(v.message); } catch (e1) { console.log("Error in error callback: "+callbackId+" = "+e1); } // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } return null; } } } catch (e2) { console.log("Error: "+e2); } };
至此为止,web端js的代码工作就完毕了,程序流程转移到了本地代码中,我们以Android的本地代码为例,看看是怎么具体实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 | package com.phonegap; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.net.Uri; import android.provider.MediaStore.Images.Media; import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; import com.phonegap.api.PluginResult.Status; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import org.apache.commons.codec.binary.Base64; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class CameraLauncher extends Plugin { private static final int DATA_URL = 0; private static final int FILE_URI = 1; private static final int PHOTOLIBRARY = 0; private static final int CAMERA = 1; private static final int SAVEDPHOTOALBUM = 2; private static final int PICTURE = 0; private static final int VIDEO = 1; private static final int ALLMEDIA = 2; private static final int JPEG = 0; private static final int PNG = 1; private static final String GET_PICTURE = "Get Picture"; private static final String GET_VIDEO = "Get Video"; private static final String GET_All = "Get All"; private static final String LOG_TAG = "CameraLauncher"; private int mQuality; private int targetWidth; private int targetHeight; private Uri imageUri; private int encodingType; private int mediaType; public String callbackId; private int numPics; public PluginResult execute(String action, JSONArray args, String callbackId) { PluginResult.Status status = PluginResult.Status.OK; String result = ""; this.callbackId = callbackId; try { if (action.equals("takePicture")) { int srcType = 1; int destType = 0; this.targetHeight = 0; this.targetWidth = 0; this.encodingType = 0; this.mediaType = 0; this.mQuality = 80; JSONObject options = args.optJSONObject(0); if (options != null) { srcType = options.getInt("sourceType"); destType = options.getInt("destinationType"); this.targetHeight = options.getInt("targetHeight"); this.targetWidth = options.getInt("targetWidth"); this.encodingType = options.getInt("encodingType"); this.mediaType = options.getInt("mediaType"); this.mQuality = options.getInt("quality"); } if (srcType == 1) { takePicture(destType, this.encodingType); } else if ((srcType == 0) || (srcType == 2)) { getImage(srcType, destType); } PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); r.setKeepCallback(true); return r; } return new PluginResult(status, result); } catch (JSONException e) { e.printStackTrace(); }return new PluginResult(PluginResult.Status.JSON_EXCEPTION); } public void takePicture(int returnType, int encodingType) { this.numPics = queryImgDB().getCount(); Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); File photo = createCaptureFile(encodingType); intent.putExtra("output", Uri.fromFile(photo)); this.imageUri = Uri.fromFile(photo); this.ctx.startActivityForResult(this, intent, 32 + returnType + 1); } private File createCaptureFile(int encodingType) { File photo = null; if (encodingType == 0) photo = new File(DirectoryManager.getTempDirectoryPath(this.ctx), "Pic.jpg"); else { photo = new File(DirectoryManager.getTempDirectoryPath(this.ctx), "Pic.png"); } return photo; } public void getImage(int srcType, int returnType) { Intent intent = new Intent(); String title = "Get Picture"; if (this.mediaType == 0) { intent.setType("image/*"); } else if (this.mediaType == 1) { intent.setType("video/*"); title = "Get Video"; } else if (this.mediaType == 2) { intent.setType("*/*"); title = "Get All"; } intent.setAction("android.intent.action.GET_CONTENT"); intent.addCategory("android.intent.category.OPENABLE"); this.ctx.startActivityForResult(this, Intent.createChooser(intent, new String(title)), (srcType + 1) * 16 + returnType + 1); } public Bitmap scaleBitmap(Bitmap bitmap) { int newWidth = this.targetWidth; int newHeight = this.targetHeight; int origWidth = bitmap.getWidth(); int origHeight = bitmap.getHeight(); if ((newWidth <= 0) && (newHeight <= 0)) { return bitmap; } if ((newWidth > 0) && (newHeight <= 0)) { newHeight = newWidth * origHeight / origWidth; } else if ((newWidth <= 0) && (newHeight > 0)) { newWidth = newHeight * origWidth / origHeight; } else { double newRatio = newWidth / newHeight; double origRatio = origWidth / origHeight; if (origRatio > newRatio) newHeight = newWidth * origHeight / origWidth; else if (origRatio < newRatio) { newWidth = newHeight * origWidth / origHeight; } } return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); } public void onActivityResult(int requestCode, int resultCode, Intent intent) { int srcType = requestCode / 16 - 1; int destType = requestCode % 16 - 1; if (srcType == 1) { if (resultCode == -1) { try { ExifHelper exif = new ExifHelper(); if (this.encodingType == 0) { exif.createInFile(DirectoryManager.getTempDirectoryPath(this.ctx) + "/Pic.jpg"); exif.readExifData(); } try { bitmap = MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), this.imageUri); } catch (FileNotFoundException e) { Uri uri = intent.getData(); ContentResolver resolver = this.ctx.getContentResolver(); bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)); } Bitmap bitmap = scaleBitmap(bitmap); if (destType == 0) { processPicture(bitmap); checkForDuplicateImage(0); } else if (destType == 1) { ContentValues values = new ContentValues(); values.put("mime_type", "image/jpeg"); Uri uri = null; try { uri = this.ctx.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException e) { System.out.println("Can't write to external media storage."); try { uri = this.ctx.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException ex) { System.out.println("Can't write to internal media storage."); failPicture("Error capturing image - no media storage found."); return; } } OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); os.close(); if (this.encodingType == 0) { exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); exif.writeExifData(); } success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); } bitmap.recycle(); bitmap = null; System.gc(); checkForDuplicateImage(1); } catch (IOException e) { e.printStackTrace(); failPicture("Error capturing image."); } } else if (resultCode == 0) { failPicture("Camera cancelled."); } else { failPicture("Did not complete!"); } } else if ((srcType == 0) || (srcType == 2)) if (resultCode == -1) { Uri uri = intent.getData(); ContentResolver resolver = this.ctx.getContentResolver(); if (this.mediaType != 0) { success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); } else if (destType == 0) { try { Bitmap bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)); bitmap = scaleBitmap(bitmap); processPicture(bitmap); bitmap.recycle(); bitmap = null; System.gc(); } catch (FileNotFoundException e) { e.printStackTrace(); failPicture("Error retrieving image."); } } else if (destType == 1) { if ((this.targetHeight > 0) && (this.targetWidth > 0)) { try { Bitmap bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)); bitmap = scaleBitmap(bitmap); String fileName = DirectoryManager.getTempDirectoryPath(this.ctx) + "/resize.jpg"; OutputStream os = new FileOutputStream(fileName); bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); os.close(); bitmap.recycle(); bitmap = null; success(new PluginResult(PluginResult.Status.OK, "file://" + fileName), this.callbackId); System.gc(); } catch (Exception e) { e.printStackTrace(); failPicture("Error retrieving image."); } } else { success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); } } } else if (resultCode == 0) { failPicture("Selection cancelled."); } else { failPicture("Selection did not complete!"); } } private Cursor queryImgDB() { return this.ctx.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { "_id" }, null, null, null); } private void checkForDuplicateImage(int type) { int diff = 1; Cursor cursor = queryImgDB(); int currentNumOfImages = cursor.getCount(); if (type == 1) { diff = 2; } if (currentNumOfImages - this.numPics == diff) { cursor.moveToLast(); int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex("_id"))).intValue() - 1; Uri uri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + id); this.ctx.getContentResolver().delete(uri, null, null); } } public void processPicture(Bitmap bitmap) { ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); try { if (bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, jpeg_data)) { byte[] code = jpeg_data.toByteArray(); byte[] output = Base64.encodeBase64(code); String js_out = new String(output); success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); js_out = null; output = null; code = null; } } catch (Exception e) { failPicture("Error compressing image."); } jpeg_data = null; } public void failPicture(String err) { error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); } } |
package com.phonegap; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.net.Uri; import android.provider.MediaStore.Images.Media; import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; import com.phonegap.api.PluginResult.Status; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import org.apache.commons.codec.binary.Base64; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class CameraLauncher extends Plugin { private static final int DATA_URL = 0; private static final int FILE_URI = 1; private static final int PHOTOLIBRARY = 0; private static final int CAMERA = 1; private static final int SAVEDPHOTOALBUM = 2; private static final int PICTURE = 0; private static final int VIDEO = 1; private static final int ALLMEDIA = 2; private static final int JPEG = 0; private static final int PNG = 1; private static final String GET_PICTURE = "Get Picture"; private static final String GET_VIDEO = "Get Video"; private static final String GET_All = "Get All"; private static final String LOG_TAG = "CameraLauncher"; private int mQuality; private int targetWidth; private int targetHeight; private Uri imageUri; private int encodingType; private int mediaType; public String callbackId; private int numPics; public PluginResult execute(String action, JSONArray args, String callbackId) { PluginResult.Status status = PluginResult.Status.OK; String result = ""; this.callbackId = callbackId; try { if (action.equals("takePicture")) { int srcType = 1; int destType = 0; this.targetHeight = 0; this.targetWidth = 0; this.encodingType = 0; this.mediaType = 0; this.mQuality = 80; JSONObject options = args.optJSONObject(0); if (options != null) { srcType = options.getInt("sourceType"); destType = options.getInt("destinationType"); this.targetHeight = options.getInt("targetHeight"); this.targetWidth = options.getInt("targetWidth"); this.encodingType = options.getInt("encodingType"); this.mediaType = options.getInt("mediaType"); this.mQuality = options.getInt("quality"); } if (srcType == 1) { takePicture(destType, this.encodingType); } else if ((srcType == 0) || (srcType == 2)) { getImage(srcType, destType); } PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); r.setKeepCallback(true); return r; } return new PluginResult(status, result); } catch (JSONException e) { e.printStackTrace(); }return new PluginResult(PluginResult.Status.JSON_EXCEPTION); } public void takePicture(int returnType, int encodingType) { this.numPics = queryImgDB().getCount(); Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); File photo = createCaptureFile(encodingType); intent.putExtra("output", Uri.fromFile(photo)); this.imageUri = Uri.fromFile(photo); this.ctx.startActivityForResult(this, intent, 32 + returnType + 1); } private File createCaptureFile(int encodingType) { File photo = null; if (encodingType == 0) photo = new File(DirectoryManager.getTempDirectoryPath(this.ctx), "Pic.jpg"); else { photo = new File(DirectoryManager.getTempDirectoryPath(this.ctx), "Pic.png"); } return photo; } public void getImage(int srcType, int returnType) { Intent intent = new Intent(); String title = "Get Picture"; if (this.mediaType == 0) { intent.setType("image/*"); } else if (this.mediaType == 1) { intent.setType("video/*"); title = "Get Video"; } else if (this.mediaType == 2) { intent.setType("*/*"); title = "Get All"; } intent.setAction("android.intent.action.GET_CONTENT"); intent.addCategory("android.intent.category.OPENABLE"); this.ctx.startActivityForResult(this, Intent.createChooser(intent, new String(title)), (srcType + 1) * 16 + returnType + 1); } public Bitmap scaleBitmap(Bitmap bitmap) { int newWidth = this.targetWidth; int newHeight = this.targetHeight; int origWidth = bitmap.getWidth(); int origHeight = bitmap.getHeight(); if ((newWidth <= 0) && (newHeight <= 0)) { return bitmap; } if ((newWidth > 0) && (newHeight <= 0)) { newHeight = newWidth * origHeight / origWidth; } else if ((newWidth <= 0) && (newHeight > 0)) { newWidth = newHeight * origWidth / origHeight; } else { double newRatio = newWidth / newHeight; double origRatio = origWidth / origHeight; if (origRatio > newRatio) newHeight = newWidth * origHeight / origWidth; else if (origRatio < newRatio) { newWidth = newHeight * origWidth / origHeight; } } return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); } public void onActivityResult(int requestCode, int resultCode, Intent intent) { int srcType = requestCode / 16 - 1; int destType = requestCode % 16 - 1; if (srcType == 1) { if (resultCode == -1) { try { ExifHelper exif = new ExifHelper(); if (this.encodingType == 0) { exif.createInFile(DirectoryManager.getTempDirectoryPath(this.ctx) + "/Pic.jpg"); exif.readExifData(); } try { bitmap = MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), this.imageUri); } catch (FileNotFoundException e) { Uri uri = intent.getData(); ContentResolver resolver = this.ctx.getContentResolver(); bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)); } Bitmap bitmap = scaleBitmap(bitmap); if (destType == 0) { processPicture(bitmap); checkForDuplicateImage(0); } else if (destType == 1) { ContentValues values = new ContentValues(); values.put("mime_type", "image/jpeg"); Uri uri = null; try { uri = this.ctx.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException e) { System.out.println("Can't write to external media storage."); try { uri = this.ctx.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); } catch (UnsupportedOperationException ex) { System.out.println("Can't write to internal media storage."); failPicture("Error capturing image - no media storage found."); return; } } OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); os.close(); if (this.encodingType == 0) { exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); exif.writeExifData(); } success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); } bitmap.recycle(); bitmap = null; System.gc(); checkForDuplicateImage(1); } catch (IOException e) { e.printStackTrace(); failPicture("Error capturing image."); } } else if (resultCode == 0) { failPicture("Camera cancelled."); } else { failPicture("Did not complete!"); } } else if ((srcType == 0) || (srcType == 2)) if (resultCode == -1) { Uri uri = intent.getData(); ContentResolver resolver = this.ctx.getContentResolver(); if (this.mediaType != 0) { success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); } else if (destType == 0) { try { Bitmap bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)); bitmap = scaleBitmap(bitmap); processPicture(bitmap); bitmap.recycle(); bitmap = null; System.gc(); } catch (FileNotFoundException e) { e.printStackTrace(); failPicture("Error retrieving image."); } } else if (destType == 1) { if ((this.targetHeight > 0) && (this.targetWidth > 0)) { try { Bitmap bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)); bitmap = scaleBitmap(bitmap); String fileName = DirectoryManager.getTempDirectoryPath(this.ctx) + "/resize.jpg"; OutputStream os = new FileOutputStream(fileName); bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); os.close(); bitmap.recycle(); bitmap = null; success(new PluginResult(PluginResult.Status.OK, "file://" + fileName), this.callbackId); System.gc(); } catch (Exception e) { e.printStackTrace(); failPicture("Error retrieving image."); } } else { success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); } } } else if (resultCode == 0) { failPicture("Selection cancelled."); } else { failPicture("Selection did not complete!"); } } private Cursor queryImgDB() { return this.ctx.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { "_id" }, null, null, null); } private void checkForDuplicateImage(int type) { int diff = 1; Cursor cursor = queryImgDB(); int currentNumOfImages = cursor.getCount(); if (type == 1) { diff = 2; } if (currentNumOfImages - this.numPics == diff) { cursor.moveToLast(); int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex("_id"))).intValue() - 1; Uri uri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + id); this.ctx.getContentResolver().delete(uri, null, null); } } public void processPicture(Bitmap bitmap) { ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); try { if (bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, jpeg_data)) { byte[] code = jpeg_data.toByteArray(); byte[] output = Base64.encodeBase64(code); String js_out = new String(output); success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); js_out = null; output = null; code = null; } } catch (Exception e) { failPicture("Error compressing image."); } jpeg_data = null; } public void failPicture(String err) { error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); } }
可以看到Camera本身被设计一个插件的形式,实现了具体的takePicture函数,至于具体的js端代码和Android本地端代码的通信是怎么样建立起来的,也即js代码怎么和Android的Activity的代码进行数据的交互,那么下面的代码会给你答案了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | public class GapViewClient extends WebViewClient { DroidGap ctx; public GapViewClient(DroidGap ctx) { this.ctx = ctx; } public boolean shouldOverrideUrlLoading(WebView view, String url) { if (!this.ctx.pluginManager.onOverrideUrlLoading(url)) { if (url.startsWith("tel:")) { try { Intent intent = new Intent("android.intent.action.DIAL"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error dialing " + url + ": " + e.toString()); } } else if (url.startsWith("geo:")) { try { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error showing map " + url + ": " + e.toString()); } } else if (url.startsWith("mailto:")) { try { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error sending email " + url + ": " + e.toString()); } } else if (url.startsWith("sms:")) { try { Intent intent = new Intent("android.intent.action.VIEW"); String address = null; int parmIndex = url.indexOf('?'); if (parmIndex == -1) { address = url.substring(4); } else { address = url.substring(4, parmIndex); Uri uri = Uri.parse(url); String query = uri.getQuery(); if ((query != null) && (query.startsWith("body="))) { intent.putExtra("sms_body", query.substring(5)); } } intent.setData(Uri.parse("sms:" + address)); intent.putExtra("address", address); intent.setType("vnd.android-dir/mms-sms"); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error sending sms " + url + ":" + e.toString()); } } else if ((this.ctx.loadInWebView) || (url.startsWith("file://")) || (url.indexOf(this.ctx.baseUrl) == 0) || (DroidGap.this.isUrlWhiteListed(url) != 0)) { try { HashMap params = new HashMap(); this.ctx.showWebPage(url, true, false, params); } catch (ActivityNotFoundException e) { System.out.println("Error loading url into DroidGap - " + url + ":" + e.toString()); } } else { try { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error loading url " + url + ":" + e.toString()); } } } return true; } public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); DroidGap.access$208(this.ctx); if (!url.equals("about:blank")) { DroidGap.this.appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); } Thread t = new Thread(new Runnable() { public void run() { try { Thread.sleep(2000L); DroidGap.GapViewClient.this.ctx.runOnUiThread(new Runnable() { public void run() { DroidGap.this.appView.setVisibility(0); DroidGap.GapViewClient.this.ctx.spinnerStop(); } }); } catch (InterruptedException e) { } } }); t.start(); if (this.ctx.clearHistory) { this.ctx.clearHistory = false; this.ctx.appView.clearHistory(); } if (url.equals("about:blank")) { if (this.ctx.callbackServer != null) { this.ctx.callbackServer.destroy(); } this.ctx.finish(); } } public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { System.out.println("onReceivedError: Error code=" + errorCode + " Description=" + description + " URL=" + failingUrl); DroidGap.access$208(this.ctx); this.ctx.spinnerStop(); this.ctx.onReceivedError(errorCode, description, failingUrl); } public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { String packageName = this.ctx.getPackageName(); PackageManager pm = this.ctx.getPackageManager(); try { ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 128); if ((appInfo.flags & 0x2) != 0) { handler.proceed(); return; } super.onReceivedSslError(view, handler, error); } catch (PackageManager.NameNotFoundException e) { super.onReceivedSslError(view, handler, error); } } } public class GapClient extends WebChromeClient { private String TAG = "PhoneGapLog"; private long MAX_QUOTA = 104857600L; private DroidGap ctx; public GapClient(Context ctx) { this.ctx = ((DroidGap)ctx); } public boolean onJsAlert(WebView view, String url, String message, JsResult result) { AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); dlg.setMessage(message); dlg.setTitle("Alert"); dlg.setCancelable(false); dlg.setPositiveButton(17039370, new DialogInterface.OnClickListener(result) { public void onClick(DialogInterface dialog, int which) { this.val$result.confirm(); } }); dlg.create(); dlg.show(); return true; } public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); dlg.setMessage(message); dlg.setTitle("Confirm"); dlg.setCancelable(false); dlg.setPositiveButton(17039370, new DialogInterface.OnClickListener(result) { public void onClick(DialogInterface dialog, int which) { this.val$result.confirm(); } }); dlg.setNegativeButton(17039360, new DialogInterface.OnClickListener(result) { public void onClick(DialogInterface dialog, int which) { this.val$result.cancel(); } }); dlg.create(); dlg.show(); return true; } public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { boolean reqOk = false; if ((url.indexOf(this.ctx.baseUrl) == 0) || (DroidGap.this.isUrlWhiteListed(url) != 0)) { reqOk = true; } if ((reqOk) && (defaultValue != null) && (defaultValue.length() > 3) && (defaultValue.substring(0, 4).equals("gap:"))) { try { JSONArray array = new JSONArray(defaultValue.substring(4)); String service = array.getString(0); String action = array.getString(1); String callbackId = array.getString(2); boolean async = array.getBoolean(3); String r = DroidGap.this.pluginManager.exec(service, action, callbackId, message, async); result.confirm(r); } catch (JSONException e) { e.printStackTrace(); } } else if ((reqOk) && (defaultValue != null) && (defaultValue.equals("gap_poll:"))) { String r = DroidGap.this.callbackServer.getJavascript(); result.confirm(r); } else if ((reqOk) && (defaultValue != null) && (defaultValue.equals("gap_callbackServer:"))) { String r = ""; if (message.equals("usePolling")) { r = "" + DroidGap.this.callbackServer.usePolling(); } else if (message.equals("restartServer")) { DroidGap.this.callbackServer.restartServer(); } else if (message.equals("getPort")) { r = Integer.toString(DroidGap.this.callbackServer.getPort()); } else if (message.equals("getToken")) { r = DroidGap.this.callbackServer.getToken(); } result.confirm(r); } else if ((reqOk) && (defaultValue != null) && (defaultValue.equals("gap_init:"))) { DroidGap.this.appView.setVisibility(0); this.ctx.spinnerStop(); result.confirm("OK"); } else { JsPromptResult res = result; AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); dlg.setMessage(message); EditText input = new EditText(this.ctx); if (defaultValue != null) { input.setText(defaultValue); } dlg.setView(input); dlg.setCancelable(false); dlg.setPositiveButton(17039370, new DialogInterface.OnClickListener(input, res) { public void onClick(DialogInterface dialog, int which) { String usertext = this.val$input.getText().toString(); this.val$res.confirm(usertext); } }); dlg.setNegativeButton(17039360, new DialogInterface.OnClickListener(res) { public void onClick(DialogInterface dialog, int which) { this.val$res.cancel(); } }); dlg.create(); dlg.show(); } return true; } public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { Log.d(this.TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); if (estimatedSize < this.MAX_QUOTA) { long newQuota = estimatedSize; Log.d(this.TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota)); quotaUpdater.updateQuota(newQuota); } else { quotaUpdater.updateQuota(currentQuota); } } public void onConsoleMessage(String message, int lineNumber, String sourceID) { Log.d(this.TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); } public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { super.onGeolocationPermissionsShowPrompt(origin, callback); callback.invoke(origin, true, false); } } |
public class GapViewClient extends WebViewClient { DroidGap ctx; public GapViewClient(DroidGap ctx) { this.ctx = ctx; } public boolean shouldOverrideUrlLoading(WebView view, String url) { if (!this.ctx.pluginManager.onOverrideUrlLoading(url)) { if (url.startsWith("tel:")) { try { Intent intent = new Intent("android.intent.action.DIAL"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error dialing " + url + ": " + e.toString()); } } else if (url.startsWith("geo:")) { try { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error showing map " + url + ": " + e.toString()); } } else if (url.startsWith("mailto:")) { try { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error sending email " + url + ": " + e.toString()); } } else if (url.startsWith("sms:")) { try { Intent intent = new Intent("android.intent.action.VIEW"); String address = null; int parmIndex = url.indexOf('?'); if (parmIndex == -1) { address = url.substring(4); } else { address = url.substring(4, parmIndex); Uri uri = Uri.parse(url); String query = uri.getQuery(); if ((query != null) && (query.startsWith("body="))) { intent.putExtra("sms_body", query.substring(5)); } } intent.setData(Uri.parse("sms:" + address)); intent.putExtra("address", address); intent.setType("vnd.android-dir/mms-sms"); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error sending sms " + url + ":" + e.toString()); } } else if ((this.ctx.loadInWebView) || (url.startsWith("file://")) || (url.indexOf(this.ctx.baseUrl) == 0) || (DroidGap.this.isUrlWhiteListed(url) != 0)) { try { HashMap params = new HashMap(); this.ctx.showWebPage(url, true, false, params); } catch (ActivityNotFoundException e) { System.out.println("Error loading url into DroidGap - " + url + ":" + e.toString()); } } else { try { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(url)); DroidGap.this.startActivity(intent); } catch (ActivityNotFoundException e) { System.out.println("Error loading url " + url + ":" + e.toString()); } } } return true; } public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); DroidGap.access$208(this.ctx); if (!url.equals("about:blank")) { DroidGap.this.appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); } Thread t = new Thread(new Runnable() { public void run() { try { Thread.sleep(2000L); DroidGap.GapViewClient.this.ctx.runOnUiThread(new Runnable() { public void run() { DroidGap.this.appView.setVisibility(0); DroidGap.GapViewClient.this.ctx.spinnerStop(); } }); } catch (InterruptedException e) { } } }); t.start(); if (this.ctx.clearHistory) { this.ctx.clearHistory = false; this.ctx.appView.clearHistory(); } if (url.equals("about:blank")) { if (this.ctx.callbackServer != null) { this.ctx.callbackServer.destroy(); } this.ctx.finish(); } } public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { System.out.println("onReceivedError: Error code=" + errorCode + " Description=" + description + " URL=" + failingUrl); DroidGap.access$208(this.ctx); this.ctx.spinnerStop(); this.ctx.onReceivedError(errorCode, description, failingUrl); } public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { String packageName = this.ctx.getPackageName(); PackageManager pm = this.ctx.getPackageManager(); try { ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 128); if ((appInfo.flags & 0x2) != 0) { handler.proceed(); return; } super.onReceivedSslError(view, handler, error); } catch (PackageManager.NameNotFoundException e) { super.onReceivedSslError(view, handler, error); } } } public class GapClient extends WebChromeClient { private String TAG = "PhoneGapLog"; private long MAX_QUOTA = 104857600L; private DroidGap ctx; public GapClient(Context ctx) { this.ctx = ((DroidGap)ctx); } public boolean onJsAlert(WebView view, String url, String message, JsResult result) { AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); dlg.setMessage(message); dlg.setTitle("Alert"); dlg.setCancelable(false); dlg.setPositiveButton(17039370, new DialogInterface.OnClickListener(result) { public void onClick(DialogInterface dialog, int which) { this.val$result.confirm(); } }); dlg.create(); dlg.show(); return true; } public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); dlg.setMessage(message); dlg.setTitle("Confirm"); dlg.setCancelable(false); dlg.setPositiveButton(17039370, new DialogInterface.OnClickListener(result) { public void onClick(DialogInterface dialog, int which) { this.val$result.confirm(); } }); dlg.setNegativeButton(17039360, new DialogInterface.OnClickListener(result) { public void onClick(DialogInterface dialog, int which) { this.val$result.cancel(); } }); dlg.create(); dlg.show(); return true; } public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { boolean reqOk = false; if ((url.indexOf(this.ctx.baseUrl) == 0) || (DroidGap.this.isUrlWhiteListed(url) != 0)) { reqOk = true; } if ((reqOk) && (defaultValue != null) && (defaultValue.length() > 3) && (defaultValue.substring(0, 4).equals("gap:"))) { try { JSONArray array = new JSONArray(defaultValue.substring(4)); String service = array.getString(0); String action = array.getString(1); String callbackId = array.getString(2); boolean async = array.getBoolean(3); String r = DroidGap.this.pluginManager.exec(service, action, callbackId, message, async); result.confirm(r); } catch (JSONException e) { e.printStackTrace(); } } else if ((reqOk) && (defaultValue != null) && (defaultValue.equals("gap_poll:"))) { String r = DroidGap.this.callbackServer.getJavascript(); result.confirm(r); } else if ((reqOk) && (defaultValue != null) && (defaultValue.equals("gap_callbackServer:"))) { String r = ""; if (message.equals("usePolling")) { r = "" + DroidGap.this.callbackServer.usePolling(); } else if (message.equals("restartServer")) { DroidGap.this.callbackServer.restartServer(); } else if (message.equals("getPort")) { r = Integer.toString(DroidGap.this.callbackServer.getPort()); } else if (message.equals("getToken")) { r = DroidGap.this.callbackServer.getToken(); } result.confirm(r); } else if ((reqOk) && (defaultValue != null) && (defaultValue.equals("gap_init:"))) { DroidGap.this.appView.setVisibility(0); this.ctx.spinnerStop(); result.confirm("OK"); } else { JsPromptResult res = result; AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); dlg.setMessage(message); EditText input = new EditText(this.ctx); if (defaultValue != null) { input.setText(defaultValue); } dlg.setView(input); dlg.setCancelable(false); dlg.setPositiveButton(17039370, new DialogInterface.OnClickListener(input, res) { public void onClick(DialogInterface dialog, int which) { String usertext = this.val$input.getText().toString(); this.val$res.confirm(usertext); } }); dlg.setNegativeButton(17039360, new DialogInterface.OnClickListener(res) { public void onClick(DialogInterface dialog, int which) { this.val$res.cancel(); } }); dlg.create(); dlg.show(); } return true; } public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { Log.d(this.TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); if (estimatedSize < this.MAX_QUOTA) { long newQuota = estimatedSize; Log.d(this.TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota)); quotaUpdater.updateQuota(newQuota); } else { quotaUpdater.updateQuota(currentQuota); } } public void onConsoleMessage(String message, int lineNumber, String sourceID) { Log.d(this.TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); } public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { super.onGeolocationPermissionsShowPrompt(origin, callback); callback.invoke(origin, true, false); } }
相信开发过webview的同学都知道其中的奥秘了,通过重载webview的WebChromeClient和WebViewClient接口,实现其中的js代码的处理部分,即可和phoneGap中js端的代码进行交互了。
相信现在大家都熟悉了一种利用web方式开发移动APP的解决方式了吧,期待更好的解决方案能够出来。
最后再向大家推荐一个js开发库,来方便的定制APP的样式,怎么样让你的APP看起来更本地化,它就是http://jqtouch.com/,运行结果见开始的图片,如果我把浏览器的部分隐藏掉,谁能轻易分辨出来是一个本地化的APP,还是web的APP(⊙o⊙)?
Hi! Great blog!