После практики в ByteDance в течение некоторого времени, JSBridge используется чаще.Я только что прочитал несколько простых концепций JSBridge, и у меня никогда не было времени понять принцип связи (JSBridge) от клиента к JavaScript. Недавно провел некоторое время, изучая связь со стороны Android на JavaScript -JSBridge(в основном слишком занят), у меня есть более глубокое понимание JSBridge, и я написал эту статью, чтобы поделиться ею.
Что такое JSBridge
Как следует из названия:Это мост между JavaScript (H5) и нативной коммуникацией., В разработке H5 часто возникает потребность в работе с клиентом, например, получение информации о приложении, открытие/закрытие WebView, подъем панели оплаты и т. д., но эти функции могут быть реализованы только в Native, поэтому JSBridge родился, и взаимодействует с Native через JSBridge. Это дает JavaScript возможность управлять Native, а также дает Native возможность вызывать JavaScript.
Принцип связи между JSBridge и Native
В основном есть два способа вызвать Native в JavaScript в H5.
- внедрить API, внедряйте собственные объекты или методы в объект окна JavaScript (что может быть аналогично вызовам RPC).
- Схема URL-адреса перехвата, клиент перехватывает запросы WebView и выполняет соответствующие операции (аналог JSONP).
Далее в качестве примера будет рассмотрено взаимодействие JSBridge на стороне Android для объяснения принципов реализации этих двух методов (я относительно хорош, только Java, но не Swift и OC😭).
внедрить API
Внедрять объекты или методы в окно JavaScript через интерфейс, предоставляемый WebView (Android используетaddJavascriptInterface()
метод), так что когда вызывается JavaScript, он эквивалентен выполнению соответствующей логики Native, чтобы достичь эффекта вызова JavaScript Native.
Для Android реализация выглядит следующим образом, основной код
webView.addJavascriptInterface(new InjectNativeObject(this), "NativeBridge");
Примеры следующие
Поместите Webview на главную страницу Android
Тогда соответствующий код на стороне Android выглядит следующим образомpublic class MainActivity extends AppCompatActivity {
private WebView webView;
// 不要用localhost或127.0.0.1
private final String host = "192.168.199.231";
public class InjectNativeObject { // 注入到JavaScript的对象
private Context context;
public InjectNativeObject(Context context) {
this.context = context;
}
@JavascriptInterface
public void openNewPage(String msg) { // 打开新页面,接受前端传来的参数
if (msg.equals("")) {
Toast.makeText(context, "please type!", Toast.LENGTH_LONG).show();
return;
}
startActivity(new Intent(context, SecondActivity.class));
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface // 存在兼容性问题
public void quit() { // 退出app
finish();
}
}
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.loginWebView);
webView.getSettings().setJavaScriptEnabled(true);
// JS注入
webView.addJavascriptInterface(new InjectNativeObject(this), "NativeBridge");
webView.loadUrl(String.format("http://%s:3000/login_webview", host)); // 加载Webview
}
}
Для сторон JavaScript вы можете напрямую вызвать метод объекта INJECTNATYOBJECT, введенный на родной стороной
window.NativeBridge = window.NativeBridge || {}; // 注入的对象
// 登录按钮点击,调用注入的openNewPage方法,并传入相应的值
loginButton.addEventListener("click", function (e) {
window.NativeBridge.openNewPage(accountInput.value + passwordInput.value);
}, false);
// 退出按钮点击,调用quit方法
quitButton.addEventListener("click", function (e) {
window.NativeBridge.quit();
}, false)
Фактический эффект заключается в следующем: кнопка входа в систему щелкает, чтобы вызвать метод openNewPage собственной стороны и передать соответствующие параметры клиенту. дефект:Android 4.2 и ниже используют метод addJavascriptInterface для уязвимости.
Уязвимость связана с тем, что программа не ограничивает должным образом использование метода WebView.addJavascriptInterface. Удаленные злоумышленники могут использовать Java Reflection API, чтобы использовать эту уязвимость для выполнения любого метода объекта Java. Проще говоря, добавьте интерфейс моста JavaScript к WebView через addJavascriptInterface. Этот интерфейс может напрямую манипулировать родным интерфейсом Java.
Доступно на Android 4.2 и выше@JavascriptInterface
Аннотация, чтобы обойти эту уязвимость, но нет возможности для версий ниже 4.2. Таким образом, использование этого метода сопряжено с определенными рисками и проблемами совместимости.
Схема URL-адреса перехвата
Терминал H5 проходит черезiframe.srcилиlocaltion.hrefОтправьте запрос схемы URL, а затем Native (сторона Android передаетshouldOverrideUrlLoading()
метод) для перехвата запрошенной схемы URL (включая параметры и т. д.) и выполнения соответствующих операций.
С точки зрения непрофессионала, обычный https-запрос, отправленный H5, может быть:Daydream.com/?ah=1&no=1,Схема URL-адреса JSBridge, согласованная с клиентом, может быть: Daydream://jsBridgeTest/?data={a:1,b:2}, клиент может использовать схему, чтобы различать, является ли это вызовом JSBridge или обычным https-запросом для делать разные сделки с.
Принцип его реализации аналогичен JSONP.
- Во-первых, введите метод обратного вызова в H5 и поместите его в объект окна.
function callback_1(data) { console.log(data); delete window.callback_1 };
window.callback_1 = callback_1;
Затем передайте имя обратного вызова в Native через схему URL.
- НативПасс
shouldOverrideUrlLoading()
, перехватите запрос к WebView и оцените, является ли это вызовом JSBridge через схему URL, согласованную с внешним интерфейсом. - Native анализирует обратный вызов на внешнем канале и использует следующий метод для вызова обратного вызова.
webView.loadUrl(String.format("javascript:callback_1(%s)", isChecked)); // 可以带上相应的参数
или
webView.evaluateJavascript(String.format("callback_1(%s)", isChecked), value -> {
// value callback_1执行是返回值
Toast.makeText(this, value, Toast.LENGTH_LONG).show();
});
С помощью описанных выше шагов вы можете реализовать связь между JavaScript и Native. Давайте посмотрим на перехват, который обрабатывает схему URL.shouldOverrideUrlLoadingСоответствующие примеры методов
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String schema = request.getUrl().getScheme(); // https or Daydream
if (this.schema.equals(schema)) { // 如果和约定好的Schema一致,则处理JSBridge调用
String callback = request.getUrl().getQueryParameter("callback");
String comment = request.getUrl().getQueryParameter("comment");
assert comment != null;
if (comment.equals("")) {
Toast.makeText(context, "please type some comment!", Toast.LENGTH_LONG).show();
return false;
}
// 使用loadUrl的方式来调用window上的方法
view.loadUrl(String.format("javascript:%s('%s')", callback, comment));
}
return super.shouldOverrideUrlLoading(view, request);
}
Пример
Макет страницы Android
Код на стороне Android выглядит следующим образомclass MyWebViewClient extends WebViewClient {
private final String schema = "sundial-dreams";
private Context context;
public MyWebViewClient(Context context) {
this.context = context;
}
// 拦截Schema
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String schema = request.getUrl().getScheme(); // 获取Schema https or Daydream
if (this.schema.equals(schema)) {
String callback = request.getUrl().getQueryParameter("callback");
String comment = request.getUrl().getQueryParameter("comment");
assert comment != null;
if (comment.equals("")) {
Toast.makeText(context, "please type some comment!", Toast.LENGTH_LONG).show();
return false;
}
// 使用loadUrl的方式来调用window上的方法
view.loadUrl(String.format("javascript:%s('%s')", callback, comment));
}
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
}
public class SecondActivity extends AppCompatActivity {
private WebView webView;
private SearchView searchView;
private Switch aSwitch;
private VideoView videoView;
private final String host = "192.168.199.231";
@SuppressLint({"SetJavaScriptEnabled"})
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_second);
webView = findViewById(R.id.commentWebView);
searchView = findViewById(R.id.searchView);
aSwitch = findViewById(R.id.comment_switch);
videoView = findViewById(R.id.videoView);
videoView.setMediaController(new MediaController(this));
// videoView.setVideoPath(Uri.parse("url").toString());
// videoView.start();
// videoView.requestFocus();
aSwitch.setChecked(true);
aSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
// Native调用JS,loadUrl 或者 evaluateJavascript
webView.evaluateJavascript(String.format("DisplayCommentCard(%s)", isChecked), value -> {
Toast.makeText(this, value, Toast.LENGTH_LONG).show();
});
// webView.loadUrl(String.format("javascript:DisplayCommentCard(%s)", isChecked));
});
webView.setWebViewClient(new MyWebViewClient(this));
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(String.format("http://%s:3000/page_webview", host));
}
}
Сторона JavaScript
Метод, смонтированный на окне, можно использовать в NativewebView.loadUrl() / webView.evaluateJavascript()
передача
/**
* @return {string}
*/
function DisplayCommentCard (display) {
commentList.style.opacity = +display;
return "JavaScriptFunction";
}
window.DisplayCommentCard = DisplayCommentCard;
Класс JSBridge может быть определен на основе принципа JSONP.
class JSBridge {
constructor () {
this.schema = 'sundial-dreams'; // 与客户端约定的schema
this.iframe = this.createIFrameElement();
this.id = 0; // callback id
}
createIFrameElement () { // 基于iframe.src发请求
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
return iframe;
}
call (params = {}, callback) {
params = Object.keys(params).reduce((acc, curKey) => acc + `${ curKey }=${ params[curKey] }&`, '');
const name = `__callback__${ this.id++ }`;
const src = `${ this.schema }://JSBridge?${ params }callback=${ name }`;
window[name] = function (value) {
delete window[name];
typeof callback === 'function' && callback(value);
};
this.iframe.src = src;
}
}
Пример использования JavaScript
const jsBridge = new JSBridge();
// 评论按钮点击,调用JSBridge
commentButton.addEventListener("click", function (e) {
jsBridge.call({ comment: commentInput.value }, value => {
commentInput.value = "";
AddComment(value);
});
}, false);
Эффект следующий дефект:При использовании схемы URL возникает проблема с длиной URL: если URL слишком длинный, он может быть утерян, вызов JSBridge может занять много времени, и для создания запроса потребуется определенное время.
Суммировать
Эта статья кратко знакомит читателей с JSBridge и коммуникацией на стороне Android. Благодаря соответствующему анализу и реализации кода можно обнаружить, что принцип JSBridge не так уж и сложен, поэтому я надеюсь, что эта статья может дать читателям определенное представление о JSBridge. В конце концов, жизнь состоит не только из внешнего интерфейса, но и из клиентов. Конец нас ждет o(T^T)o (Внешний интерфейс слишком сложен...)
полный код
GitHub: JSBridgeTest