前面我们已经完成了模块化的重构和优化,这一篇我们利用这点快速编写新的模块
- KVDB: 本地KV数据库
- Camera: 调用摄像头
- Image: 调用系统图片
KVDB模块Android篇
KVDB模块由Native提供比LocalStorage更稳定的本地存储,暂时提供三种数据类型存储的实现:
- Int
- Bool
- String
Native
Android将使用SharedPreferences作为底层,具体也可以根据App的情况替换成其他数据库等等
新建JSBridgeKVDB.kt文件,代码如下:
class JSBridgeKVDB(val activity: WebActivity, webView: WebView) : BridgeModuleBase(webView) {
val preferences = activity.getSharedPreferences("KVDB", Context.MODE_PRIVATE)
override fun callFunc(func: String, callbackId: String, arg: JSONObject) {
when (func) {
"getInt" -> {
callback(callbackId, getInt(arg.getString("key")))
}
"setInt" -> {
setInt(arg.getString("key"), arg.getInt("value"))
}
"getBool" -> {
callback(callbackId, getBool(arg.getString("key")))
}
"setBool" -> {
setBool(arg.getString("key"), arg.getBoolean("value"))
}
"getString" -> {
callback(callbackId, arg.getString(arg.getString("key")))
}
"setString" -> {
setString(arg.getString("key"), arg.getString("value"))
}
}
}
fun getInt(key: String): Int {
return preferences.getInt(key, 0)
}
fun setInt(key: String, value: Int) {
preferences.edit().putInt(key, value).apply()
}
fun getBool(key: String): Boolean {
return preferences.getBoolean(key, false)
}
fun setBool(key: String, value: Boolean) {
preferences.edit().putBoolean(key, value).apply()
}
fun getString(key: String): String? {
return preferences.getString(key, null)
}
fun setString(key: String, value: String?) {
preferences.edit().putString(key, value).apply()
}
}
BridgeObject里增加JSBridgeKVDB的初始化:
init {
bridgeModuleMap["UI"] = JSBridgeUI(activity, webView)
bridgeModuleMap["KVDB"] = JSBridgeKVDB(activity, webView)
}
KVDB模块iOS篇
KVDB模块由Native提供比LocalStorage更稳定的本地存储,暂时提供三种数据类型存储的实现:
- Int
- Bool
- String
Native
iOS将使用UserDefaults作为底层,具体也可以根据App的情况替换成其他数据库等等
新建JSBridgeKVDB.swift文件,代码如下:
class JSBridgeKVDB : BridgeModuelBase {
weak var viewController: WebViewController?
var userDefaults = UserDefaults(suiteName: "KVDB")!
init(viewController: WebViewController) {
self.viewController = viewController
}
override func callFunc(_ funcName: String, callbackId: String, arg: [String : Any?]) {
let key = arg["key"] as! String
switch funcName {
case "getInt":
callback(callbackId: callbackId, value: getInt(key: key))
case "setInt":
setInt(key: key, value: arg["value"] as? Int ?? 0)
case "getBool":
callback(callbackId: callbackId, value: getBool(key: key))
case "setBool":
setBool(key: key, value: arg["value"] as? Bool ?? false)
case "getString":
callback(callbackId: callbackId, value: getString(key: key))
case "setString":
setString(key: key, value: arg["value"] as? String)
default: break
}
}
func getInt(key: String) -> Int {
return userDefaults.integer(forKey: key)
}
func setInt(key: String, value: Int) {
userDefaults.set(value, forKey: key)
}
func getBool(key: String) -> Bool {
return userDefaults.bool(forKey: key)
}
func setBool(key: String, value: Bool) {
userDefaults.set(value, forKey: key)
}
func getString(key: String) -> String? {
return userDefaults.string(forKey: key)
}
func setString(key: String, value: String?) {
userDefaults.set(value, forKey: key)
}
}
BridgeHandler的initModules加上KVDB模块的初始化:
func initModules() {
moduleDict["UI"] = JSBridgeUI(viewController: viewController!)
moduleDict["KVDB"] = JSBridgeKVDB(viewController: viewController!)
}
KVDB模块JS篇
jsbridge.js
针对js的特性,我们可以利用JSON.stringify/JSON.parse
,提供比原生多一种数据类型json
JSBridge.KVDB = {}
JSBridge.KVDB.getInt = function(key, callback) {
callNative("KVDB.getInt", { key: key }, callback);
}
JSBridge.KVDB.setInt = function(key, value) {
callNative("KVDB.setInt", { key: key, value: value });
}
JSBridge.KVDB.getBool = function(key, callback) {
callNative("KVDB.getBool", { key: key }, callback);
}
JSBridge.KVDB.setBool = function(key, value) {
callNative("KVDB.setBool", { key: key, value: value });
}
JSBridge.KVDB.getString = function(key, callback) {
callNative("KVDB.getString", { key: key }, callback);
}
JSBridge.KVDB.setString = function(key, value) {
callNative("KVDB.setString", { key: key, value: value });
}
JSBridge.KVDB.getJSON = function(key, callback) {
callNative("KVDB.getString", { key: key }, function(result){
callback(JSON.parse(result))
})
}
JSBridge.KVDB.setJSON = function(key, value) {
callNative("KVDB.setString", { key: key, value: JSON.stringify(value) });
}
index.html
<button onclick="onClickButton(this)">KVDB.getInt</button>
<button onclick="onClickButton(this)">KVDB.setInt</button>
<button onclick="onClickButton(this)">KVDB.getBool</button>
<button onclick="onClickButton(this)">KVDB.setBool</button>
<button onclick="onClickButton(this)">KVDB.getString</button>
<button onclick="onClickButton(this)">KVDB.setString</button>
<button onclick="onClickButton(this)">KVDB.getJSON</button>
<button onclick="onClickButton(this)">KVDB.setJSON</button>
case "KVDB.getInt":
JSBridge.KVDB.getInt("int", (result) => {
JSBridge.UI.toast("getInt:" + result)
})
break
case "KVDB.setInt":
JSBridge.KVDB.setInt("int", parseInt(Date.now() / 1000))
JSBridge.UI.toast('setInt')
break
case "KVDB.getBool":
JSBridge.KVDB.getBool("bool", (result) => {
JSBridge.UI.toast("getBool: " + result)
})
break
case "KVDB.setBool":
JSBridge.KVDB.setBool("bool", Date.now() % 2 === 0)
JSBridge.UI.toast('setBool')
break
case "KVDB.getString":
JSBridge.KVDB.getString("string", (result) => {
JSBridge.UI.toast("getString: " + result)
})
break
case "KVDB.setString":
JSBridge.KVDB.setString("string", "string_value_" + Date.now())
JSBridge.UI.toast('setString')
break
case "KVDB.getJSON":
JSBridge.KVDB.getJSON("json", (result) => {
JSBridge.UI.toast("getJSON: " + JSON.stringify(result, null, ' '))
})
break
case "KVDB.setJSON":
JSBridge.KVDB.setJSON("json", { title: 'This is json', message: 'time is ' + new Date().toUTCString() })
JSBridge.UI.toast('setJSON')
break
结合Android和iOS,自己运行看看效果,这里就不截图了。
Camera模块Android篇
这篇使用到了Native的知识,来源:
- 系统相机拍照:developer.android.com/training/ca…
- 获取 Activity 的结果:developer.android.google.cn/training/ba…
AndroidManifest.xml
manifest配置里增加使用相机的请求项
<manifest ... >
<uses-feature android:name="android.hardware.camera"
android:required="true" />
...
</manifest>
WebActivity.kt
由于我们要调用系统相机,我们必须使用activity的Intent来唤起相机应用,在activity上 需要做如下的代码编写,代码设计了callback方式的调用,提供给bridge模块使用
abstract class WebActivity : AppCompatActivity() {
private var activityResultCallback: ActivityResultCallback? = null
...
fun startActivityForCallback(intent: Intent, requestCode: Int, callback: ActivityResultCallback) {
activityResultCallback = callback
startActivityForResult(intent, requestCode)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
activityResultCallback?.let {
if (it.invoke(requestCode, resultCode, data)) {
activityResultCallback = null
}
}
}
}
JSBridgeCamera.kt
class JSBridgeCamera(val activity: WebActivity, webView: WebView) : BridgeModuleBase(webView) {
override fun callFunc(func: String, callbackId: String, arg: JSONObject) {
when (func) {
"takePicture" -> takePicture(callbackId, arg)
}
}
private fun takePicture(callbackId: String, arg: JSONObject) {
...
}
}
WebViewBridge.kt
bridgeModuleMap["Camera"] = JSBridgeCamera(activity, webView)
takePicture代码
private val REQ_CODE = 200
private fun takePicture(callbackId: String, arg: JSONObject) {
// 创建去相机的意图(Intent)
val takePhotoIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// 检查系统是否支持相机
if (!takePhotoIntent.resolveActivity(activity.packageManager) != null) {
} else {
// 调用前面定义的callback方式打开摄像机
activity.startActivityForCallback(takePhotoIntent, REQ_CODE) { requestCode: Int, resultCode: Int, result: Intent? ->
// 不是对应的requestCode
if (REQ_CODE != requestCode) {
return@startActivityForCallback false
}
val json = JSONObject()
if (resultCode == RESULT_OK) {
json.put("success", true)
val bitmap = result?.extras?.get("data") as Bitmap
// TODO 存文件,返回路径(后面会讲到)
} else {
json.put("success", false)
json.put("canceled", resultCode == RESULT_CANCELED)
json.put("error", "Result code is $resultCode")
}
callback(callbackId, json)
true
}
}
}
扩展阅读:
Android Intent: developer.android.google.cn/guide/compo…
存文件
首先我们声明了两个成员变量:
- appCacheDir: 应用的缓存路径,使用了packageName防止名称冲突
- cameraCacheDir:appCacheDir的子目录,作为业务区分
private val appCacheDir = File(activity.cacheDir, activity.application.packageName)
private val cameraCacheDir = File(appCacheDir, "Camera")
然后将文件存到cameraCacheDir下:
// 获取拍照的bitmap
val bitmap = result?.extras?.get("data") as Bitmap
// 使用时间戳创建一个文件
val cacheFile = File(cameraCacheDir, "takePicture_${System.currentTimeMillis()}.jpg")
// 下面是将图片写入文件
var fos: FileOutputStream? = null
try {
fos = FileOutputStream(cacheFile)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
} catch (error: Throwable) {
// 出现了IO异常
json.put("success", false)
json.put("error", error.localizedMessage)
return@startActivityForCallback true
} finally {
fos?.close()
}
json.put("success", true)
// 增加文件路径返回给js端
json.put("file", cacheFile.absolutePath)
到这里就完成的takePicture部分
扩展阅读:
- Java读写文件:www.runoob.com/java/java-f…
- Android将文件保存在本地:developer.android.com/training/da…
takeVideo
takeVideo和takePicture非常类似,只是相机返回的数据是视频,并且更加简单
private fun takeVideo(callbackId: String, arg: JSONObject) {
// 创建去相机的意图(Intent),ACTION_VIDEO_CAPTURE(拍视频)
val takePhotoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
// 检查系统是否支持相机
if (takePhotoIntent.resolveActivity(activity.packageManager) == null) {
// 不支持返回不成功
val json = JSONObject()
json.put("success", false)
json.put("error", "This device is not support camera.")
callback(callbackId, json)
} else {
// 调用前面定义的callback方式打开摄像机
activity.startActivityForCallback(takePhotoIntent, REQ_CODE) { requestCode: Int, resultCode: Int, result: Intent? ->
// 不是对应的requestCode
if (REQ_CODE != requestCode) {
return@startActivityForCallback false
}
val json = JSONObject()
if (resultCode == RESULT_OK) {
// 获取视频地址,这里是和takePicture不同的地方
val videoUri = result?.data
json.put("success", true)
json.put("file", videoUri.toString())
} else {
json.put("success", false)
json.put("canceled", resultCode == RESULT_CANCELED)
json.put("error", "Result code is $resultCode")
}
callback(callbackId, json)
true
}
}
}
override fun callFunc(func: String, callbackId: String, arg: JSONObject) {
when (func) {
"takePicture" -> takePicture(callbackId, arg)
"takeVideo" -> takeVideo(callbackId, arg) // 注意加上这一行
}
}
到这里就完成了takeVideo
Camera模块iOS篇
JSBridgeCamera.swift
添加JSBridgeCamera.swift文件,初始代码:
class JSBridgeCamera : BridgeModuelBase {
weak var viewController: WebViewController?
init(viewController: WebViewController) {
self.viewController = viewController
}
override func callFunc(_ funcName: String, callbackId: String, arg: [String : Any?]) {
switch funcName {
case "takePicture": takePicture(callbackId: callbackId, arg: arg)
case "takeVideo": takeVideo(callbackId: callbackId, arg: arg)
default: break
}
}
private func takePicture(callbackId: String, arg: [String : Any?]) {
...
}
private func takeVideo(callbackId: String, arg: [String : Any?]) {
...
}
}
WebViewBridge.swift
func initModules() {
moduleDict["UI"] = JSBridgeUI(viewController: viewController!)
moduleDict["KVDB"] = JSBridgeKVDB(viewController: viewController!)
// 添加Camera模块
moduleDict["Camera"] = JSBridgeCamera(viewController: viewController!)
}
takePicture
拍照和录像都需要使用到系统提供的UIImagePickerController
,使用很简单,如下:
private var cameraDelegate: CameraDelegate?
...
private func takePicture(callbackId: String, arg: [String : Any?]) {
guard let vc = self.viewController else { return }
//
if !UIImagePickerController.isSourceTypeAvailable(.camera) {
callback(callbackId: callbackId, json: [
"success": false,
"error": "This device is not support camera"
])
return
}
let cameraPicker = UIImagePickerController()
// 配置为拍照图片
cameraPicker.allowsEditing = false
cameraPicker.sourceType = .camera
cameraPicker.cameraCaptureMode = .photo
// 创建delegate
cameraDelegate = CameraDelegate()
cameraDelegate?.jsbridgeCamera = self
cameraDelegate?.callbackId = callbackId
cameraPicker.delegate = cameraDelegate!
// 打开拍照
vc.present(cameraPicker, animated: true, completion: nil)
}
上面代码创建了delegate,这个delegate实现了UIImagePickerControllerDelegate
和UINavigationControllerDelegate
代码如下:
fileprivate class CameraDelegate : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// 定义这两个属性,从JSBridgeCamera里传过来的,用于调用callback
weak var jsbridgeCamera: JSBridgeCamera?
var callbackId: String!
// 实现这个protocol方法,用于拍照完成后的回调处理
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
// TODO 图片存入缓存文件
let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": true
])
}
// 实现这个protocol方法,用于用户取消时的操作
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": false,
"canceled": true
])
}
}
前面还没有完成全部代码,但可以现在先运行看看结果。
运行后会在XCode看到如下错误提示
This app has crashed because it attempted to access privacy-sensitive data without
a usage description. The app's Info.plist must contain an NSCameraUsageDescription
key with a string value explaining to the user how the app uses this data.
需要在Info.plist
添加一行信息,用于申请权限时系统显示的文案,这里不细说。
完成上面的操作再运行,拍照流程应该是通的,然后继续后面的步骤。
图片存入缓存
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
// 获取拍照的图片
let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
// 创建临时文件
let tempDir = NSTemporaryDirectory() as NSString
let appTempDir = tempDir.appendingPathComponent(Bundle.main.bundleIdentifier!) as NSString
let cacheDir = appTempDir.appendingPathComponent("Camera") as NSString
let filePath = cacheDir.appendingPathComponent("Camera_\(Int64(Date().timeIntervalSince1970)).jpg")
let fileMgr = FileManager.default
// 确定目录存在
var isDir: ObjCBool = false
if !fileMgr.fileExists(atPath: cacheDir as String, isDirectory: &isDir) {
try? fileMgr.createDirectory(atPath: cacheDir as String, withIntermediateDirectories: true, attributes: nil)
}
// 转换成jpeg data
guard let data = image.jpegData(compressionQuality: 1) else {
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": false,
"error": "Image compress fail"
])
return
}
// 写文件
do {
let fileURL = URL(fileURLWithPath: filePath)
try data.write(to: fileURL)
// callback给js
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": true,
"file": fileURL.absoluteString
])
} catch {
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": false,
"error": error.localizedDescription
])
return
}
}
到这里就完成的takePicture部分
takeVideo
takeVideo和takePicture非常类似,只是相机返回的数据是视频,并且更加简单
private func takeVideo(callbackId: String, arg: [String : Any?]) {
guard let vc = self.viewController else { return }
if !UIImagePickerController.isSourceTypeAvailable(.camera) {
callback(callbackId: callbackId, json: [
"success": false,
"error": "This device is not support camera"
])
return
}
let cameraPicker = UIImagePickerController()
cameraPicker.sourceType = .camera
cameraPicker.cameraDevice = .rear
cameraPicker.videoQuality = .typeHigh
cameraPicker.mediaTypes = [kUTTypeMovie as String]
cameraPicker.videoMaximumDuration = 60
cameraDelegate = CameraDelegate()
cameraDelegate?.jsbridgeCamera = self
cameraDelegate?.callbackId = callbackId
cameraPicker.delegate = cameraDelegate!
vc.present(cameraPicker, animated: true, completion: nil)
}
由于拍照和录视频都回调的统一的方法,因此CameraDelegate类里需要作出判断,代码如下:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
jsbridgeCamera?.cameraDelegate = nil
picker.dismiss(animated: true, completion: nil)
// 判断是视频资源
if let url = info[.mediaURL] as? URL {
// callback给js
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": true,
"file": url.absoluteString
])
}
// 判断是图片资源
else if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
// 创建临时文件
let tempDir = NSTemporaryDirectory() as NSString
let appTempDir = tempDir.appendingPathComponent(Bundle.main.bundleIdentifier!) as NSString
let cacheDir = appTempDir.appendingPathComponent("Camera") as NSString
let filePath = cacheDir.appendingPathComponent("Camera_\(Int64(Date().timeIntervalSince1970)).jpg")
let fileMgr = FileManager.default
// 确定目录存在
var isDir: ObjCBool = false
if !fileMgr.fileExists(atPath: cacheDir as String, isDirectory: &isDir) {
try? fileMgr.createDirectory(atPath: cacheDir as String, withIntermediateDirectories: true, attributes: nil)
}
// 转换成jpeg data
guard let data = image.jpegData(compressionQuality: 1) else {
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": false,
"error": "Image compress fail"
])
return
}
// 写文件
do {
let fileURL = URL(fileURLWithPath: filePath)
try data.write(to: fileURL)
// callback给js
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": true,
"file": fileURL.absoluteString.
])
} catch {
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": false,
"error": error.localizedDescription
])
return
}
} else {
jsbridgeCamera?.callback(callbackId: callbackId, json: [
"success": false,
"error": "Operation failed."
])
}
}
到这里就完成了takeVideo
Camera模块JS篇
jsbridge.js
JSBridge.Camera = {}
JSBridge.Camera.takePicture = function(callback) {
callNative("Camera.takePicture", {}, callback)
}
JSBridge.Camera.takeVideo = function(callback) {
callNative("Camera.takeVideo", {}, callback)
}
index.html
<button onclick="onClickButton(this)">Camera.takePicture</button>
<button onclick="onClickButton(this)">Camera.takeVideo</button>
case "Camera.takePicture":
JSBridge.Camera.takePicture((result) => {
JSBridge.UI.toast(JSON.stringify(result))
})
break
case "Camera.takeVideo":
JSBridge.Camera.takeVideo((result) => {
JSBridge.UI.toast(JSON.stringify(result))
})
break
结合Android和iOS,自己运行看看效果,这里就不截图了。
Image模块Android篇
Android去系统选择图片和视频资源依然使用Intent,只是参数设置不一样,这里不细讲,直接贴代码:
JSBridgeImage.swift
class JSBridgeImage(val activity: WebActivity, webView: WebView) : BridgeModuleBase(webView) {
private val REQ_CODE = 200
override fun callFunc(func: String, callbackId: String, arg: JSONObject) {
when (func) {
"pickPhotos" -> pickPhotos(callbackId, arg)
}
}
private fun pickPhotos(callbackId: String, arg: JSONObject) {
// 创建去系统选图片的意图
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activity.startActivityForCallback(intent, REQ_CODE) { requestCode: Int, resultCode: Int, result: Intent? ->
// 不是对应的requestCode
if (REQ_CODE != requestCode) {
return@startActivityForCallback false
}
val json = JSONObject()
if (resultCode == Activity.RESULT_OK) {
val selectedImage = result?.data as Uri
json.put("success", false)
json.put("file", selectedImage.toString())
} else {
json.put("success", false)
json.put("canceled", resultCode == Activity.RESULT_CANCELED)
json.put("error", "Result code is $resultCode")
}
callback(callbackId, json)
true
}
}
}
WebViewBridge.kt
bridgeModuleMap["Camera"] = JSBridgeCamera(activity, webView)
Image模块iOS篇
JSBridgeImage.swift
iOS去系统选择图片和视频资源和Camera拍照使用的都是UIImagePickerController
,只是参数
设置不一样,这里不细讲,直接贴代码:
class JSBridgeImage : BridgeModuelBase {
weak var viewController: WebViewController?
fileprivate var pickerDelegate: PickerDelegate?
init(viewController: WebViewController) {
self.viewController = viewController
}
override func callFunc(_ funcName: String, callbackId: String, arg: [String : Any?]) {
switch funcName {
case "pickPhotos": pickPhotos(callbackId: callbackId, arg: arg)
default: break
}
}
private func pickPhotos(callbackId: String, arg: [String : Any?]) {
guard let vc = self.viewController else { return }
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
pickerDelegate = PickerDelegate()
pickerDelegate?.jsbridgeImage = self
pickerDelegate?.callbackId = callbackId
picker.delegate = pickerDelegate!
vc.present(picker, animated: true, completion: nil)
}
}
fileprivate class PickerDelegate : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
weak var jsbridgeImage: JSBridgeImage?
var callbackId: String!
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
jsbridgeImage?.pickerDelegate = nil
picker.dismiss(animated: true, completion: nil)
let url = info[UIImagePickerController.InfoKey.referenceURL] as? URL
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String
let type = mediaType == "public.image" ? "image" : "video"
// callback给js
jsbridgeImage?.callback(callbackId: callbackId, json: [
"success": true,
"type": type,
"file": url?.absoluteString
])
}
// 出来用户取消的操作
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
jsbridgeImage?.pickerDelegate = nil
picker.dismiss(animated: true, completion: nil)
jsbridgeImage?.callback(callbackId: callbackId, json: [
"success": false,
"canceled": true
])
}
}
Image模块JS篇
jsbridge.js
JSBridge.Image = {}
JSBridge.Image.pickPhotos = function(callback) {
callNative("Image.pickPhotos", {}, callback)
}
index.html
<button onclick="onClickButton(this)">Image.pickPhotos</button>
case "Image.pickPhotos":
JSBridge.Image.pickPhotos((result) => {
JSBridge.UI.toast(JSON.stringify(result))
})
break
结合Android和iOS,自己运行看看效果,这里就不截图了。
结语
到这里我们完成了几个JSBridge模块:
- UI
- KVDB
- Camera
- Image
完整地了解了如何模块化
其中Camera、Image模块会返回一些本地路径,协议不是http的,WebView无法识别,下一章我们会讲解