参考文档:
geckoview版本
引入文档(有坑 下面会给出正确引入方式)
官方示例代码1
官方示例代码2
参考了两位大神的博客和demo:
GeckoView js交互实现
geckoview-jsdemo
引入方式:
- maven {
- url "https://maven.mozilla.org/maven2/"
- }
复制代码- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
复制代码- implementation 'org.mozilla.geckoview:geckoview-arm64-v8a:111.0.20230309232128'
复制代码 使用方式:
控件:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <org.mozilla.geckoview.GeckoView
- android:id="@+id/web_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- <ProgressBar
- android:id="@+id/web_progress"
- style="@style/Web.ProgressBar.Horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="visible"
- tools:progress="50" />
- </RelativeLayout>
复制代码 初始化及设置
- import androidx.annotation.NonNull;
- import androidx.annotation.Nullable;
- import androidx.appcompat.app.AlertDialog;
- import androidx.appcompat.app.AppCompatActivity;
- import androidx.core.app.ActivityCompat;
- import androidx.core.content.ContextCompat;
- import android.Manifest;
- import android.content.Context;
- import android.content.DialogInterface;
- import android.content.pm.PackageManager;
- import android.content.res.TypedArray;
- import android.net.Uri;
- import android.os.Bundle;
- import android.text.TextUtils;
- import android.util.Log;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.ArrayAdapter;
- import android.widget.LinearLayout;
- import android.widget.ProgressBar;
- import android.widget.ScrollView;
- import android.widget.Spinner;
- import android.widget.TextView;
- import org.json.JSONException;
- import org.json.JSONObject;
- import org.mozilla.geckoview.GeckoResult;
- 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 java.util.Locale;
- public class MainActivity extends AppCompatActivity {
- private static final String TAG = "MainActivityTag";
- // 权限回调码
- private static final int CAMERA_PERMISSION_REQUEST_CODE = 1000;
- // web - 测试环境
- private static final String WEB_URL = "https://xxx.xxx.com/";
- private static final String EXTENSION_LOCATION = "resource://android/assets/messaging/";
- private static final String EXTENSION_ID = "messaging@example.com";
- private static GeckoRuntime sRuntime = null;
- private GeckoSession session;
- private static WebExtension.Port mPort;
- private GeckoSession.PermissionDelegate.Callback mCallback;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- setupGeckoView();
- }
- private void setupGeckoView() {
- // 初始化控件
- GeckoView geckoView = findViewById(R.id.gecko_view);
- ProgressBar web_progress = findViewById(R.id.web_progress);
- if (sRuntime == 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(BuildConfig.DEBUG)
- .debugLogging(BuildConfig.DEBUG);
- sRuntime = GeckoRuntime.create(this, builder.build());
- }
- // 建立交互
- 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) {
- // 网页开始加载时的操作
- if (web_progress != null) {
- web_progress.setVisibility(View.VISIBLE);
- }
- }
- @Override
- public void onPageStop(GeckoSession session, boolean success) {
- // 网页加载完成时的操作
- if (web_progress != null) {
- web_progress.setVisibility(View.GONE);
- }
- }
- @Override
- public void onProgressChange(GeckoSession session, int progress) {
- // 网页加载进度变化时的操作
- if (web_progress != null) {
- web_progress.setProgress(progress);
- }
- }
- });
- // 权限
- session.setPermissionDelegate(new GeckoSession.PermissionDelegate() {
- @Override
- public void onAndroidPermissionsRequest(@NonNull final GeckoSession session,
- final String[] permissions,
- @NonNull final Callback callback) {
- mCallback = callback;
- if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
- || ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(MainActivity.this, permissions, CAMERA_PERMISSION_REQUEST_CODE);
- } else {
- callback.grant();
- }
- }
- @Nullable
- @Override
- public GeckoResult<Integer> onContentPermissionRequest(@NonNull GeckoSession session, @NonNull ContentPermission perm) {
- return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW);
- }
- @Override
- public void onMediaPermissionRequest(@NonNull final GeckoSession session,
- @NonNull final String uri,
- final MediaSource[] video,
- final MediaSource[] audio,
- @NonNull final MediaCallback callback) {
- final String host = Uri.parse(uri).getAuthority();
- final String title;
- if (audio == null) {
- title = getString(R.string.request_video, host);
- } else if (video == null) {
- title = getString(R.string.request_audio, host);
- } else {
- title = getString(R.string.request_media, host);
- }
- String[] videoNames = normalizeMediaName(video);
- String[] audioNames = normalizeMediaName(audio);
- final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
- final LinearLayout container = addStandardLayout(builder, title, null);
- final Spinner videoSpinner;
- if (video != null) {
- videoSpinner = addMediaSpinner(builder.getContext(), container, video, videoNames); // create spinner and add to alert UI
- } else {
- videoSpinner = null;
- }
- final Spinner audioSpinner;
- if (audio != null) {
- audioSpinner = addMediaSpinner(builder.getContext(), container, audio, audioNames); // create spinner and add to alert UI
- } else {
- audioSpinner = null;
- }
- // 手动同意权限
- builder.setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- // gather selected media devices and grant access
- final MediaSource video = (videoSpinner != null)
- ? (MediaSource) videoSpinner.getSelectedItem() : null;
- final MediaSource audio = (audioSpinner != null)
- ? (MediaSource) audioSpinner.getSelectedItem() : null;
- callback.grant(video, audio);
- }
- });
- final AlertDialog dialog = builder.create();
- dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(final DialogInterface dialog) {
- callback.reject();
- }
- });
- dialog.show();
- // 自动同意权限
- /*
- final MediaSource videoMediaSource = (videoSpinner != null)
- ? (MediaSource) videoSpinner.getSelectedItem() : null;
- final MediaSource audioMediaSource = (audioSpinner != null)
- ? (MediaSource) audioSpinner.getSelectedItem() : null;
- callback.grant(videoMediaSource, audioMediaSource);
- */
- }
- });
- session.open(sRuntime);
- geckoView.setSession(session);
- // 打开web地址
- session.loadUri(WEB_URL);
- }
- /**
- * 建立交互
- */
- private void installExtension() {
- sRuntime.getWebExtensionController()
- .ensureBuiltIn(EXTENSION_LOCATION, EXTENSION_ID)
- .accept(
- extension -> {
- Log.i(TAG, "Extension installed: " + extension);
- runOnUiThread(() -> {
- assert extension != null;
- extension.setMessageDelegate(mMessagingDelegate, "Android");
- });
- },
- e -> Log.e(TAG, "Error registering WebExtension", e)
- );
- }
- private final WebExtension.MessageDelegate mMessagingDelegate = new WebExtension.MessageDelegate() {
- @Nullable
- @Override
- public void onConnect(@NonNull WebExtension.Port port) {
- Log.e(TAG, "MessageDelegate onConnect");
- mPort = port;
- mPort.setDelegate(mPortDelegate);
- }
- };
- /**
- * 接收 JS 发送的消息
- */
- private final WebExtension.PortDelegate mPortDelegate = new WebExtension.PortDelegate() {
- @Override
- public void onPortMessage(final @NonNull Object message,
- final @NonNull WebExtension.Port port) {
- Log.e(TAG, "from extension: " + message);
- try {
- // ToastUtils.showLong("收到js调用: " + message);
- if (message instanceof JSONObject) {
- JSONObject jsonobject = (JSONObject) message;
- /*
- * jsonobject 格式
- *
- * {
- * "action": "JSBridge",
- * "data": {
- * "args":"字符串",
- * "function":"方法名"
- * }
- * }
- */
- String action = jsonobject.getString("action");
- if ("JSBridge".equals(action)) {
- JSONObject data = jsonobject.getJSONObject("data");
- String function = data.getString("function");
- if (!TextUtils.isEmpty(function)) {
- String args = data.getString("args");
- switch (function) {
- // 与前端定义的方法名 示例:callSetToken
- case "callSetToken": {
- break;
- }
- }
- }
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onDisconnect(final @NonNull WebExtension.Port port) {
- Log.e(TAG, "MessageDelegate:onDisconnect");
- if (port == mPort) {
- mPort = null;
- }
- }
- };
- /**
- * 向 js 发送数据 示例:evaluateJavascript("callStartUpload", "startUpload");
- *
- * @param methodName 定义的方法名
- * @param data 发送的数据
- */
- private void evaluateJavascript(String methodName, String data) {
- try {
- long id = System.currentTimeMillis();
- JSONObject message = new JSONObject();
- message.put("action", "evalJavascript");
- message.put("data", "window." + methodName + "('" + data + "')");
- message.put("id", id);
- mPort.postMessage(message);
- Log.e(TAG,"mPort.postMessage:" + message);
- } catch (JSONException ex) {
- throw new RuntimeException(ex);
- }
- }
-
- /**
- * web 端:
- *
- * 接收消息示例:window.callStartUpload = function(data){console.log(data)}
- *
- * 发送消息示例:
- * if(typeof window.JSBridge !== 'undefined'){
- * window.JSBridge.postMessage({function:name, args})
- * }
- *
- */
- private int getViewPadding(final AlertDialog.Builder builder) {
- final TypedArray attr =
- builder
- .getContext()
- .obtainStyledAttributes(new int[]{android.R.attr.listPreferredItemPaddingLeft});
- final int padding = attr.getDimensionPixelSize(0, 1);
- attr.recycle();
- return padding;
- }
- private LinearLayout addStandardLayout(
- final AlertDialog.Builder builder, final String title, final String msg) {
- final ScrollView scrollView = new ScrollView(builder.getContext());
- final LinearLayout container = new LinearLayout(builder.getContext());
- final int horizontalPadding = getViewPadding(builder);
- final int verticalPadding = (msg == null || msg.isEmpty()) ? horizontalPadding : 0;
- container.setOrientation(LinearLayout.VERTICAL);
- container.setPadding(
- /* left */ horizontalPadding, /* top */ verticalPadding,
- /* right */ horizontalPadding, /* bottom */ verticalPadding);
- scrollView.addView(container);
- builder.setTitle(title).setMessage(msg).setView(scrollView);
- return container;
- }
- private Spinner addMediaSpinner(
- final Context context,
- final ViewGroup container,
- final GeckoSession.PermissionDelegate.MediaSource[] sources,
- final String[] sourceNames) {
- final ArrayAdapter<GeckoSession.PermissionDelegate.MediaSource> adapter =
- new ArrayAdapter<GeckoSession.PermissionDelegate.MediaSource>(context, android.R.layout.simple_spinner_item) {
- private View convertView(final int position, final View view) {
- if (view != null) {
- final GeckoSession.PermissionDelegate.MediaSource item = getItem(position);
- ((TextView) view).setText(sourceNames != null ? sourceNames[position] : item.name);
- }
- return view;
- }
- @Override
- public View getView(final int position, View view, final ViewGroup parent) {
- return convertView(position, super.getView(position, view, parent));
- }
- @Override
- public View getDropDownView(final int position, final View view, final ViewGroup parent) {
- return convertView(position, super.getDropDownView(position, view, parent));
- }
- };
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- adapter.addAll(sources);
- final Spinner spinner = new Spinner(context);
- spinner.setAdapter(adapter);
- spinner.setSelection(0);
- container.addView(spinner);
- return spinner;
- }
- private String[] normalizeMediaName(final GeckoSession.PermissionDelegate.MediaSource[] sources) {
- if (sources == null) {
- return null;
- }
- String[] res = new String[sources.length];
- for (int i = 0; i < sources.length; i++) {
- final int mediaSource = sources[i].source;
- final String name = sources[i].name;
- if (GeckoSession.PermissionDelegate.MediaSource.SOURCE_CAMERA == mediaSource) {
- if (name.toLowerCase(Locale.ROOT).contains("front")) {
- res[i] = getString(R.string.media_front_camera);
- } else {
- res[i] = getString(R.string.media_back_camera);
- }
- } else if (!name.isEmpty()) {
- res[i] = name;
- } else if (GeckoSession.PermissionDelegate.MediaSource.SOURCE_MICROPHONE == mediaSource) {
- res[i] = getString(R.string.media_microphone);
- } else {
- res[i] = getString(R.string.media_other);
- }
- }
- return res;
- }
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // 授予权限
- mCallback.grant();
- } else {
- // 拒绝权限
- mCallback.reject();
- }
- }
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (session != null) {
- session.close();
- }
- }
- }
复制代码 资源文件设置:
在assets下新建:messaging 文件夹
.eslintrc.js
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- module.exports = {
- env: {
- webextensions: true,
- },
- };
复制代码 background.js
- // Establish connection with app
- 'use strict';
- const port = browser.runtime.connectNative("Android");
- 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');
- })
复制代码 content.js
- console.log(`content:start`);
- 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);
- }
- });
复制代码 manifest.json
- {
- "manifest_version": 2,
- "name": "messaging",
- "description": "Uses the proxy API to block requests to specific hosts.",
- "version": "3.0",
- "browser_specific_settings": {
- "gecko": {
- "strict_min_version": "65.0",
- "id": "messaging@example.com"
- }
- },
- "content_scripts": [
- {
- "matches": [
- "<all_urls>"
- ],
- "js": [
- "content.js"
- ],
- "run_at": "document_start"
- }
- ],
- "background": {
- "scripts": [
- "background.js"
- ]
- },
- "permissions": [
- "nativeMessaging",
- "nativeMessagingFromContent",
- "geckoViewAddons",
- "webNavigation",
- "geckoview",
- "tabs",
- "<all_urls>"
- ],
- "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
- }
复制代码 其他资源文件:
themes.xml
- <!-- WebView进度条 -->
- <style name="Web.ProgressBar.Horizontal" parent="@android:style/Widget.ProgressBar.Horizontal">
- <item name="android:progressDrawable">@drawable/web_view_progress</item>
- <item name="android:minHeight">2dp</item>
- <item name="android:maxHeight">2dp</item>
- </style>
复制代码 web_view_progress
- <?xml version="1.0" encoding="utf-8"?>
- <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@android:id/background">
- <shape>
- <corners android:radius="0dp" />
- <gradient
- android:angle="270"
- android:centerY="0.75"
- android:endColor="#A0B3CF"
- android:startColor="#A0B3CF" />
- </shape>
- </item>
- <item android:id="@android:id/progress">
- <clip>
- <shape>
- <corners android:radius="0dp" />
- <gradient
- android:angle="270"
- android:endColor="@color/colorPrimary"
- android:startColor="@color/colorPrimary" />
- </shape>
- </clip>
- </item>
- </layer-list>
复制代码 colors.xml
- <color name="colorPrimary">#FF2673FF</color>
复制代码 strings.xml
- <string name="device_sharing_microphone">麦克风打开</string>
- <string name="device_sharing_camera">摄像头打开</string>
- <string name="device_sharing_camera_and_mic">摄像头和麦克风打开</string>
- <string name="media_back_camera">背面摄像头</string>
- <string name="media_front_camera">前置摄像头</string>
- <string name="media_microphone">麦克风</string>
- <string name="media_other">未知来源</string>
- <string name="request_video">与共享视频 "%1$s"</string>
- <string name="request_audio">与共享音频 "%1$s"</string>
- <string name="request_media">与共享视频和音频 "%1$s"</string>
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |