官方集成说明
https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/geckoview-quick-start.html
官方JS交互说明
https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/web-extensions.html
Geckoview 和 JS 交互实现 主要分为2个大部分
第一部分,实现 Geckoview 的 WebExtension 扩展
新建文件夹 app / src / main / assets / messaging
再上述文件夹下,新建3个必要文件 background.js content.js manifest.json
1.1:background.js 代码
javascript
'use strict';
console.log("background script loaded!");
const port = browser.runtime.connectNative("browser");
async function sendMessageToTab(message) {
try {
let tabs = await browser.tabs.query({})
console.log(`background:tabs:${tabs}`)
return await browser.tabs.sendMessage(
tabs[tabs.length - 1].id,
message
)
} catch (e) {
console.log(`background:sendMessageToTab:req:error:${e}`)
return e.toString();
}
}
//监听 app message
port.onMessage.addListener(request => {
let action = request.action;
if(action === "evalJavascript") {
sendMessageToTab(request).then((resp) => {
port.postMessage(resp);
}).catch((e) => {
console.log(`background:sendMessageToTab:resp:error:${e}`)
});
}
})
//接收 content.js message
browser.runtime.onMessage.addListener((data, sender) => {
let action = data.action;
console.log("background:content:onMessage:" + action);
if (action === 'JSBridge') {
port.postMessage(data);
}
return Promise.resolve('done');
})
console.log("background script initialization complete !!! ");
1.2: content.js 代码
javascript
console.log("Content script loaded!");
let JSBridge = {
postMessage: function (message) {
browser.runtime.sendMessage({
action: "JSBridge",
data: message
});
}
}
window.wrappedJSObject.JSBridge = cloneInto(
JSBridge,
window,
{ cloneFunctions: true });
browser.runtime.onMessage.addListener((data, sender) => {
console.log("content:eval:" + data);
if (data.action === 'evalJavascript') {
let evalCallBack = {
id: data.id,
action: "evalJavascript",
}
try {
let result = window.eval(data.data);
console.log("content:eval:result" + result);
if (result) {
evalCallBack.data = result;
} else {
evalCallBack.data = "";
}
} catch (e) {
evalCallBack.data = e.toString();
return Promise.resolve(evalCallBack);
}
return Promise.resolve(evalCallBack);
}
});
console.log("Content script initialization complete");
1.3: manifest.json 代码
vbscript
{
"manifest_version": 2,
"name": "Messaging",
"description": "Uses the proxy API to block requests to specific hosts.",
"version": "4.0",
"browser_specific_settings": {
"gecko": {
"id": "messaging@example.com"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"background": {
"scripts": [
"background.js"
],
"persistent": false
},
"permissions": [
"nativeMessaging",
"nativeMessagingFromContent",
"geckoViewAddons",
"tabs",
"activeTab",
"<all_urls>"
]
}
上述步骤完成后,即完成了 Geckoview 的 WebExtension 扩展 的配置。
第二部分,实现Activity 中 Geckoview 的 配置
Android工程根目录下,settings.gradle 添加
repositories {
maven {
url "https://maven.mozilla.org/maven2/"
}
}
App目录下的build.gradle添加依赖
implementation 'org.mozilla.geckoview:geckoview-arm64-v8a:129.0.20240801122119'
依赖版本查询
https://maven.mozilla.org/?prefix=maven2/org/mozilla/geckoview/geckoview-arm64-v8a/
注意:高版本要求
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
2.1 Activity 核心代码
java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import org.json.JSONObject;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoRuntimeSettings;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
import org.mozilla.geckoview.WebExtension;
import org.mozilla.geckoview.WebExtensionController;
public class MainGeckoViewActivityBack extends Activity {
private GeckoView view_gecko = null;
private static GeckoRuntime runtime = null;
private GeckoSession session = null;
private static WebExtension.Port mPort;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_gecko);
view_gecko = findViewById(R.id.activity_main_gecko_gecko);
iniGeckoView();
}
private void iniGeckoView() {
if (runtime == null) {
GeckoRuntimeSettings.Builder builder = new GeckoRuntimeSettings.Builder()
.allowInsecureConnections(GeckoRuntimeSettings.ALLOW_ALL)
.javaScriptEnabled(true)
.doubleTapZoomingEnabled(true)
.inputAutoZoomEnabled(true)
.forceUserScalableEnabled(true)
.aboutConfigEnabled(true)
.loginAutofillEnabled(true)
.webManifest(true)
.consoleOutput(true)
.remoteDebuggingEnabled(true)
.debugLogging(true);
runtime = GeckoRuntime.create(this, builder.build());
}
// 安装 WebExtension 扩展
installExtension();
session = new GeckoSession();
GeckoSessionSettings settings = session.getSettings();
settings.setAllowJavascript(true);
settings.setUserAgentMode(GeckoSessionSettings.USER_AGENT_MODE_MOBILE);
session.getPanZoomController().setIsLongpressEnabled(false);
// 监听加载进度
session.setProgressDelegate(new GeckoSession.ProgressDelegate() {
@Override
public void onPageStart(GeckoSession session, String url) {
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
}
@Override
public void onProgressChange(GeckoSession session, int progress) {
}
});
session.open(runtime);
view_gecko.setSession(session);
session.loadUri("http://xxx.xxx.xxx.xxx/index.html");
// session.loadUri("resource://android/assets/web/index.html");
// 切记,测试不要在 assets目录下去加载 index.html
// 会导致 WebExtension 扩展 content.js 无法加载
// 即 window.JSBridge.postMessage("test") 无法调用;
// 记录踩坑半天
}
private final WebExtension.MessageDelegate messageDelegate = new WebExtension.MessageDelegate() {
@Override
public void onConnect(WebExtension.Port port) {
Log.w("Message Delegate", "浏览器扩展 Port 链接成功! ");
mPort = port;
mPort.setDelegate(mPortDelegate);
}
};
private final WebExtension.PortDelegate mPortDelegate = new WebExtension.PortDelegate() {
@Override
public void onPortMessage(Object message, WebExtension.Port port) {
Log.e("MessageDelegate", "收到消息");
try {
if (message instanceof JSONObject) {
Log.e("MessageDelegate", "Received JSONObject");
JSONObject jsonObject = (JSONObject) message;
String action = jsonObject.getString("action");
if ("JSBridge".equals(action)) {
String data = jsonObject.getString("data");
Toast.makeText(MainGeckoViewActivityBack.this, data, Toast.LENGTH_LONG).show();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onDisconnect(WebExtension.Port port) {
Log.e("MessageDelegate:", "onDisconnect");
if (port == mPort) {
mPort = null;
}
}
};
void installExtension() {
WebExtensionController controller = runtime.getWebExtensionController();
//仅在扩展尚未安装时才会安装
controller.ensureBuiltIn("resource://android/assets/messaging/", "messaging@example.com")
.accept(
extension -> {
Log.i("GeckoViewActivity", "浏览器 WebExtension installed 安装成功: " + extension.toString());
runOnUiThread(new Runnable() {
@Override
public void run() {
assert extension != null;
setAppMessageDelegate(extension);
}
});
},
e -> Log.e("GeckoViewActivity", "浏览器 注册 WebExtension 错误:", e)
);
}
private void setAppMessageDelegate(WebExtension extension){
extension.setMessageDelegate(messageDelegate, "browser");
}
/**
* 向H5页面发送消息
* */
public void evaluateJavascript(String javascriptString) {
try {
long id = System.currentTimeMillis();
Log.e("evalJavascript:id:", id + "");
JSONObject jsonObject = new JSONObject();
jsonObject.put("action", "evalJavascript");
jsonObject.put("data", javascriptString);
jsonObject.put("id", id);
Log.e("evalJavascript:", jsonObject.toString());
mPort.postMessage(jsonObject);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 html文件中调用核心代码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS2Android通信演示</title>
<style>
/* 页面样式,使按钮居中显示 */
body {
margin: 0;
padding: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #f0f2f5;
}
/* 按钮样式美化 */
#popupBtn {
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
color: white;
background-color: #1677ff;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s;
}
#popupBtn:hover {
background-color: #0f52ba;
}
</style>
</head>
<body>
<!-- 中间的按钮 -->
<button id="popupBtn">点击测试</button>
<script>
// 获取按钮元素
const btn = document.getElementById('popupBtn');
// 给按钮添加点击事件
btn.addEventListener('click', function() {
try {
// 调用 js向android 原生发送消息
window.JSBridge.postMessage("run");
console.log('成功: postMessage 调用成功 !!! ');
} catch (error) {
console.log(`错误: ${error.message}`);
}
});
</script>
</body>
</html>
感谢 :