Android geckoview 集成,JS交互,官方demo

官方集成说明

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 代码

'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 代码

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 代码

{
  "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 核心代码

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文件中调用核心代码

<!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>

感谢 :

https://jishuzhan.net/article/1781493646214828034

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值