混合开发的App(App)的技术问题是什么?
发表时间:2023-09-20 14:00:58
文章来源:炫佑科技
浏览次数:193
菏泽炫佑科技
混合开发的App(App)的技术问题是什么?
混合开发的App(App)是嵌入在App中的轻量级浏览器,并且使用HTML 5开发一些原生功能。这些功能不仅可以在不升级App的情况下动态更新,而且还可以同时运行在或iOS App提供的更好的用户体验并节省开发资源。
我们来谈谈App开发中的技术问题。 我对iOS不太了解,所以主要说一下正在开发的东西。 其中可能有很多错误,欢迎大家批评指正。
发展中存在哪些关键问题
如果要在App中显示Html 5网页的功能,其实很简单,就一个。 您可以点击链接跳转至网页。 这样的功能还能叫开发吗? 很明显不是。
我认为开发的App中必须包含的一个功能是Html 5页面和App之间如何交互。 例如,如果我点击Html 5页面上的按钮或链接,我可以跳转到App中的某个页面吗? 例如,如果我在Html 5页面上点击分享按钮,我可以调用App的分享功能吗? 例如,加载Html时能否获取App的用户信息?
看下面的图片。 当你进入网易云音乐的这个Html 5页面时,点击作者:空编辑,就会进入他的主页。 这个首页就是这个页面,当你点击上面的播放按钮时,云音乐就会开始播放。 界面播放音乐。 点击评论后,您将进入评论页面。
Html 5 和之间的交互
本来就支持js和Java互相调用。 只需要启用脚本执行,然后通过代码 .ce(new (), ""); 将 Java 对象注入到 Html 5 页面中。 然后就可以在Html 5页面中调用它了。 功能
微信怎么办
微信应该是开发得*好的应用程序之一。 它如何相互作用?
答案就是微信JS-SDK。 在微信开发者文档中可以看到,微信JS-SDK封装了微信的各种功能,比如分享到朋友圈、图片接口、音频接口、支付接口、地理位置接口等,开发者只需调用微信JS中的功能即可—— SDK,然后JS-SDK会调用微信中的功能。 这样做的好处是混合开发的App(App)的技术问题是什么?,我编写一个Html 5应用程序或网页,在微信和iOS中都可以正常运行。 知道了
这将在下面详细讨论
如何制作网易云音乐
那么网易云音乐是如何做到的呢? 我用黑科技发现上图中云音乐的接口是y(这个名字好奇怪,如果让我在代码中找肯定找不到,因为还有一个类叫y) ),于是我反编译了云音乐在代码中寻找对应的功能实现代码,但是没有找到。 但我得到了Html 5页面的地址:
打开后发现和App中显示的不一样。 然后我拦截了进入Html 5的请求,发现加载了云音乐的地址是,其中添加了移动系统类型。
然后在我自己的App中加载这个Html 5页面,你可以看到下图。 @小ibi说这样的文字点击可以跳转到个人,点击播放按钮可以播放音乐。
从Html源码中可以看到以下信息:
也就是说,当我点击某个用户名时,我请求跳转到://user/。 由于重定向的URL可以被拦截,因此App正在拦截每个URL。 如果主机为yes,则会启动用户主页。
反编译代码后发现this..(new cf(this)); 在云音乐的代码中。 我进入cf类,发现如下代码:
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
if (url.startsWith("orpheus://")) {
RedirectActivity.a(this.activity, url);
return true;
}
if ((url.toLowerCase().startsWith("http://")) || (url.toLowerCase().startsWith("https://"))) {
return false;
}
try {
this.activity.startActivity(new Intent("android.intent.action.VIEW", Uri.parse(url)));
return true;
} catch (ActivityNotFoundException localActivityNotFoundException) {
localActivityNotFoundException.printStackTrace();
}
return true;
}
果然,再次进入时,这是一个没有任何界面的界面,专门用来处理页面跳转信息的。 它会调用一个方法。 (this, ().().(), false) 处理url,方法名我自己写的,部分代码如下:
可以看到传入了://user/中的用户id,因此启动了用户主页并显示了用户信息。
然后我自己写了代码拦截Html 5的跳转,打印的Log如下:
可以看到Html 5页面可以跳转到各个页面,如用户主页、音乐播放、MV界面、评论页面、电台节目等。
总结
总的来说,目前我知道的两种主流方法是
js调用中的代码:拦截页面跳转
第二种方法实现起来非常简单,但一个致命的问题是这种交互方法是单向的,Html 5无法实现回调。 云音乐App中点击跳转到特定页面的功能确实很容易实现,也很合适。 如果需求变得复杂,Html 5如果需要获取App中的用户信息,*好使用js调用。
js 并与之交互
上面说了,支持js调用代码,但是这个功能在4.2(API 17)中存在高危漏洞。 该漏洞的原理是系统通过.ce(o,)方法注册了可以被js调用的Java对象,但是系统并没有限制注册的Java对象的方法调用。 因此,攻击者可以利用反射来调用任何其他未注册的 Java 对象,并且攻击者可以根据客户端的能力进行任何操作。本文详细介绍了此漏洞
出于安全考虑,4.2之后的系统规定允许js调用的Java方法必须使用@注解
解决方案
是一个广泛使用的开发框架,提供了一套js和交互规范
课堂上可以看到
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;
}
webView.addJavascriptInterface(new SystemExposedJsApi(bridge), "_cordovaNative");
}
因此,当系统高于4.2时,仍然使用ce方法,因为这种方法在更高版本上安全且简单。 当系统低于4.2时,应该使用什么方法?
答案是方法
可以设置一个对象可以处理js的3个方法
这三个方法分别对应js的alert、方法。 因为它们只接收返回值,所以js可以在调用方法后等待参数返回。 下面是 .js 中的一段代码:
/**
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
*/
module.exports = {
exec: function(bridgeSecret, service, action, callbackId, argsJson) {
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
},
setNativeToJsBridgeMode: function(bridgeSecret, value) {
prompt(value, 'gap_bridge_mode:' + bridgeSecret);
},
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
}
};
然后在处理js调用的方法中使用即可
/**
* Tell the client to display a prompt dialog to the user. If the client returns true, WebView will assume that the client will handle the prompt dialog and call the appropriate JsPromptResult method.
*
* Since we are hacking prompts for our own purposes, we should not be using them for this purpose, perhaps we should hack console.log to do this instead!
*/
@Override
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
if (handledRet != null) {
result.confirm(handledRet);
} else {
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirm(value);
} else {
result.cancel();
}
}
});
}
return true;
}
开源解决方案
是的,是开源的解决方案,但是需要xml配置信息,使用起来比较麻烦,而且框架很重。 具体请自行搜索教程。
下面这个开源项目是我个人认为比较合理、轻量级的解决方案。 下图是一个Demo。
这个项目的原理是使用.来交互。 本质上,js调用函数,传递一些参数,方法拦截,然后解析数据,*后调用对应的方法。
该类定义了所有js可以调用的方法。 这些方法必须是静态方法,并且所有方法的**个参数必须是
/**
* HostJsScope中需要被JS调用的函数,必须定义成public static,且必须包含WebView这个参数
*/
public class HostJsScope {
/**
* 短暂气泡提醒
* @param webView 浏览器
* @param message 提示信息
* */
public static void toast(WebView webView, String message) {
Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
}
/**
* 系统弹出提示框
* @param webView 浏览器
* @param message 提示信息
* */
public static void alert(WebView webView, String message) {
// 构建一个Builder来显示网页中的alert对话框
AlertDialog.Builder builder = new AlertDialog.Builder(webView.getContext());
builder.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setTitle("Hello world")
.setMessage(message)
.setCancelable(false)
.create()
.show();
}
// 其他代码
}
上面的代码列出了点击Html 5按钮弹出对话框的*基本的功能。
这个库中*关键的事情之一就是调用。 这个实现的是通过js调用Java方法的功能。 该类仅用于 ent 类。
public class InjectedChromeClient extends WebChromeClient {
private JsCallJava mJsCallJava;
private boolean mIsInjectedJS;
public InjectedChromeClient(String injectedName, Class injectedCls) {
this(new JsCallJava(injectedName, injectedCls));
}
public InjectedChromeClient(JsCallJava jsCallJava) {
mJsCallJava = jsCallJava;
}
// 处理Alert事件
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
result.confirm();
return true;
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
//为什么要在这里注入JS
//1 OnPageStarted中注入有可能全局注入不成功,导致页面脚本上所有接口任何时候都不可用
//2 OnPageFinished中注入,虽然*后都会全局注入成功,但是完成时间有可能太晚,当页面在初始化调用接口函数时会等待时间过长
//3 在进度变化时注入,刚好可以在上面两个问题中得到一个折中处理
//为什么是进度大于25%才进行注入,因为从测试看来只有进度大于这个数字页面才真正得到框架刷新加载,保证100%注入成功
if (newProgress <= 25) {
mIsInjectedJS = false;
} else if (!mIsInjectedJS) {
view.loadUrl(mJsCallJava.getPreloadInterfaceJS());
mIsInjectedJS = true;
StopWatch.log(" inject js interface completely on progress " + newProgress);
}
super.onProgressChanged(view, newProgress);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm(mJsCallJava.call(view, message));
StopWatch.log("onJsPrompt: " + view.toString() +", " + url +", " + message +", " + defaultValue + ", " + result) ;
return true;
}
}
此条目已设置。 这里需要注意一个非常重要的细节。 方法中,在方法中注入了一段js代码。 这段js代码如下:
javascript: (function(b) {
console.log("HostApp initialization begin");
var a = {
queue: [],
callback: function() {
var d = Array.prototype.slice.call(arguments, 0);
var c = d.shift();
var e = d.shift();
this.queue[c].apply(this, d);
if (!e) {
delete this.queue[c]
}
}
};
a.alert = a.alert = a.alert = a.delayJsCallBack = a.getIMSI = a.getOsSdk = a.goBack = a.overloadMethod = a.overloadMethod
= a.passJson2Java = a.passLongType = a.retBackPassJson = a.retJavaObject = a.testLossTime = a.toast = a.toast = function() {
var f = Array.prototype.slice.call(arguments, 0);
if (f.length < 1) {
throw "HostApp call error, message:miss method name"
}
var e = [];
for (var h = 1; h < f.length; h++) {
var c = f[h];
var j = typeof c;
e[e.length] = j;
if (j == "function") {
var d = a.queue.length;
a.queue[d] = c;
f[h] = d
}
}
var g = JSON.parse(prompt(JSON.stringify({
method: f.shift(),
types: e,
args: f
})));
if (g.code != 200) {
throw "HostApp call error, code:" + g.code + ", message:" + g.result
}
return g.result
};
//有时候,我们希望在该方法执行前插入一些其他的行为用来检查当前状态或是监测
//代码行为,这就要用到拦截(Interception)或者叫注入(Injection)技术了
/**
* Object.getOwnPropertyName 返回一个数组,内容是指定对象的所有属性
*
* 其后遍历这个数组,分别做以下处理:
* 1. 备份原始属性;
* 2. 检查属性是否为 function(即方法);
* 3. 若是重新定义该方法,做你需要做的事情,之后 apply 原来的方法体。
*/
Object.getOwnPropertyNames(a).forEach(function(d) {
var c = a[d];
if (typeof c === "function" && d !== "callback") {
a[d] = function() {
return c.apply(a, [d].concat(Array.prototype.slice.call(arguments, 0)))
}
}
});
b.HostApp = a;
console.log("HostApp initialization end")
})(window);
那么这段js代码是怎么生成的呢?答案就在类的构造函数方法中。 这个构造函数所做的就是解析类中的方法,并将每个方法的签名保存在Map中。 看上面的js代码
a.警报=a.警报=a.警报=a。 = a. = a. = a. = a. = a.
= a. = a. = a. = a. = a. = a.toast = a.toast = ()
这些是类中定义的方法名称
那么这个库的整个执行流程如下:
类解析类中的所有静态方法,放入一个Map中,生成一段js代码进行设置。 该方法中,将js代码注入到Html5页面中。 通俗地说,这个过程就是告诉Html 5 Page,我给你开了哪些功能,你可以调用我,以便js调用提供的方法。 那js代码也会把js执行的方法转成json字符串,通过js方法传递给方法。 其中,调用call(view, msg)解析json字符串,包括要执行的方法名、参数类型和方法参数。 还会验证json中的方法参数类型是否与json中的同名方法参数类型一致等。*后,如果方法执行正确,则调用方法会返回一个json字符串code=200,否则会密码=500。 这些信息会通过方法的返回值传递给js,这样Html 5代码就可以知道是否正确执行了。
以上就是这个开源库的整个原理。 我个人认为非常适合开发。 该方案中js可以接收到的返回值,并且没有使用ce方法,在低版本手机上不会出现安全问题。 该方法比以下方法更容易实现和配置
那么当我点击Html 5页面上的一个按钮时,比如弹出一个对话框,这个过程的整体流程是怎样的呢?
微信的解决办法?
什么? 你问我微信是怎么解决的?我也反编译了微信的代码,想看看他们是怎么解决的。 其实我很好奇微信的js调用以及返回的方法。
首先我去微信的js sdk官网看了一下js sdk提供的功能。 它提供了各种强大的功能。 你可以自己看一下。 那么问题来了,微信如何进行js调用并成功返回呢?
带着疑惑app开发,我反编译了微信客户端,看到/jsapi中有wxjs.js文件。 我认为这是微信js sdk的源代码。 。 。
首先说一下我对js代码不太了解。 我只能猜测微信的js代码。 如果有js高手也对这方面感兴趣的话,希望我们一起探讨(fei)(jian)。 藏)
我在wxjs.js中看到了代码。 我猜微信当时就是用这个数据结构来和js通信的吧?
var __WeixinJSBridge = {
// public
invoke:_call,
call:_call,
on:_onfor3rd,
env:_env,
log:_log,
// private
// _test_start:_test_start,
_fetchQueue: _fetchQueue,
_handleMessageFromWeixin: _handleMessageFromWeixin,
_hasInit: false,
_continueSetResult: _continueSetResult
};
然后我看到了下面的代码。 我觉得应该提供分享内容到朋友圈的功能。
// share timeline
_on('menu:share:timeline',function(argv){
_log('share timeline');
var data;
if (typeof argv.title === 'string') {
data = argv;
_call('shareTimeline',data);
}else{
data = {
// "img_url": "",
// "img_width": "",
// "img_height": "",
"link": document.documentURI || _session_data.init_url,
"desc": document.documentURI || _session_data.init_url,
"title": document.title
};
var shareFunc = function(_img){
if (_img) {
data['img_url'] = _img.src;
data['img_width'] = _img.width;
data['img_height'] = _img.height;
}
_call('shareTimeline',data);
};
getSharePreviewImage(shareFunc);
}
});
请注意*后一句:_call('',data);,查看call属性后,发现了_call方法。
function _call(func,params,callback) {
var curFuncIdentifier = __WeixinJSBridge.call;
if (curFuncIdentifier !== _callIdentifier) {
return;
}
if (!func || typeof func !== 'string') {
return;
};
if (typeof params !== 'object') {
params = {};
};
var callbackID = (_callback_count++).toString();
if (typeof callback === 'function') {
_callback_map[callbackID] = callback;
};
var msgObj = {'func':func,'params':params};
msgObj[_MESSAGE_TYPE] = 'call';
msgObj[_CALLBACK_ID] = callbackID;
_sendMessage(JSON.stringify(msgObj));
}
大致意思应该是:转换这个东西 _call('',data); 转换为 json 字符串。 从这里我们可以看出,微信的做法和上面的开源库非常相似,简单又安全。 _call方法*终调用发送消息的方法
//将消息添加到发送队列,iframe的准备队列为weixin://dispatch_message/
function _sendMessage(message) {
_sendMessageQueue.push(message);
_readyMessageIframe.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;
// var ifm = _WXJS('iframe#__WeixinJSBridgeIframe')[0];
// if (!ifm) {
// ifm = _createQueueReadyIframe(document);
// }
// ifm.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;
};
从上面的代码可以看到,微信的js sdk也将js方法调用替换为类似///的URL,也就是上面提到的json封装的数据。 那么我猜微信的做法和网易云音乐拦截URL类似? 如果真是这样的话,那是非常不安全的。 任何Html 5页面都可以伪造类似::///的URL来调用微信功能。 还好微信每次调用js都要带上它。 应用程序。
在反编译的微信代码中,我看到了如下代码:
我想这就是微信想要向Html 5开放的接口吧? 但是对比微信js sdk官网后发现,app提供的很多功能在js sdk中是找不到的。 嗯~
从上面可以看到有一个熟悉的ixin,就是js处理的回调接口。 我用这个字符串在微信代码中搜索,结果如下:
因此,我粗略猜测微信中的js调用函数是拦截URL,回调方法是使用
我也在js sdk中找到了对应的函数:
function _handleMessageFromWeixin(message) {
var curFuncIdentifier = __WeixinJSBridge._handleMessageFromWeixin;
if (curFuncIdentifier !== _handleMessageIdentifier) {
return '{}';
}
var ret;
var msgWrap
if (_isUseMd5 === 'yes') {
var realMessage = message[_JSON_MESSAGE];
var shaStr = message[_SHA_KEY];
var arr = new Array;
arr[0] = JSON.stringify(realMessage);
arr[1] = _xxyy;
var str = arr.join("");
var msgSha = '';
var shaObj = CryptoJS.SHA1(str);
msgSha = shaObj.toString();
if (msgSha !== shaStr) {
_log('_handleMessageFromWeixin , shaStr : ' + shaStr + ' , str : ' + str + ' , msgSha : ' + msgSha);
return '{}';
}
msgWrap = realMessage;
}
//省略很多代码
微信的做法应该说很基础,使用原生功能,但是安全。 由于微信客户端每次js调用都有验证(appid),这也增加了一定程度的安全性。
以上都是基于我分析的正确性。
一些个人想法
如今,各种新技术也在试图解决开发效率的问题。 他们想用技术来解决在iOS客户端上运行的一组代码。 相信随着技术的发展,这些问题都会得到解决。 我也很期待即将到来的React
开发适用于哪些功能?
本文讨论的开发是在客户端嵌入Html App的功能。 微信在这方面应该是*好的。 由于Html 5的效率和功耗,我个人感觉用户无法满足Web App的体验。 App只适用于特定场景。 一些基础的功能,比如调用手机的摄像头、获取地理位置、登录注册功能等,做成Html 5可以更好调用的功能,体验会更好。
如果把登录注册功能做成Html 5,在弱网络环境下体验会很差。 也许您需要等待很长时间才能加载页面。 你可能会说我可以预加载Html 5代码,打开App时直接加载。 那我说你这是给自己找麻烦。 如果是这样的话,发展可能会更快。
那么什么情况适合Html 5开发呢?一些活动页面,比如限时抢购、团购等,都适合Html 5,因为这些页面可能会非常眼花缭乱,而且很复杂。 Html 5开发可能更简单。 关键是这些页面的生命周期很短,更新也比较快,因为有一个活动说可能就一周了,下周活动就变了。 如果是这样的话,你就无法继续做下去了。
总结
有一句古老的格言:
如果你手里有锤子,那么一切看起来都像钉子
千万不要因为认为开发可以夸耀平台运行而使用任何开发功能。其实我早期也是这么认为的。 后来因为渲染效率低,整个应用转为开发。 请看这里
引用:
今天,我们是新的代码,以加快速度。 到 of ,我们从 / 到纯代码,再到 for 、新用户、磁盘缓存等等。
本文主要讲一下开发中js和交互的技术实现原理。 我写的可能有很多错误,希望技术高手指出。
*后,我认为开源库是一个非常好的解决方案。 该解决方案巧妙、简单且安全。 当时我调试了半天弄清楚原理后,我一拍大腿,这个方法真好! ! 网易云音乐的解决方案适合其场景。 它不需要回调。 它只需要处理相应的信息,然后实现页面跳转、音乐播放、MV播放等功能。 这种方法也简单易用。