Android 使用 GeckoView 并实现 js 交互、权限交互

news/2024/10/18 13:07:42/

参考文档:

geckoview版本
引入文档(有坑 下面会给出正确引入方式)
官方示例代码1
官方示例代码2

参考了两位大神的博客和demo:

GeckoView js交互实现
geckoview-jsdemo

引入方式:

        maven {url "https://maven.mozilla.org/maven2/"}
    compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility 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.GeckoViewandroid:id="@+id/web_view"android:layout_width="match_parent"android:layout_height="match_parent" /><ProgressBarandroid: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;@Overrideprotected 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() {@Overridepublic void onPageStart(GeckoSession session, String url) {// 网页开始加载时的操作if (web_progress != null) {web_progress.setVisibility(View.VISIBLE);}}@Overridepublic void onPageStop(GeckoSession session, boolean success) {// 网页加载完成时的操作if (web_progress != null) {web_progress.setVisibility(View.GONE);}}@Overridepublic void onProgressChange(GeckoSession session, int progress) {// 网页加载进度变化时的操作if (web_progress != null) {web_progress.setProgress(progress);}}});// 权限session.setPermissionDelegate(new GeckoSession.PermissionDelegate() {@Overridepublic 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@Overridepublic GeckoResult<Integer> onContentPermissionRequest(@NonNull GeckoSession session, @NonNull ContentPermission perm) {return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW);}@Overridepublic 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() {@Overridepublic void onClick(final DialogInterface dialog, final int which) {// gather selected media devices and grant accessfinal 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() {@Overridepublic void onDismiss(final DialogInterface dialog) {callback.reject();}});dialog.show();}});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@Overridepublic 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() {@Overridepublic 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) {// 与前端定义的方法名 示例:callSetTokencase "callSetToken": {break;}}}}}} catch (Exception e) {e.printStackTrace();}}@Overridepublic 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;}@Overridepublic View getView(final int position, View view, final ViewGroup parent) {return convertView(position, super.getView(position, view, parent));}@Overridepublic 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;}@Overridepublic 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();}}}@Overrideprotected 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" /><gradientandroid: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" /><gradientandroid: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>

http://www.ppmy.cn/news/1438179.html

相关文章

关于豆瓣电影数据抓取以及可视化

首先我们可以先了解以下网络爬虫的定义&#xff1a; 爬虫是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。它可以在互联网上自动抓取网页内容&#xff0c;将这些信息存储起来。爬虫可以抓取网站的所有网页&#xff0c;从而获取对于我们有价值的信…

解决MobaXtermSSH连接超时,无法连linux IP地址的问题

一、错误展示&#xff1a; 二、解决历程 1.先是重启了下虚拟机&#xff08;Ubuntu&#xff09;&#xff0c;再次连接&#xff0c;失败 2.查看有无连接网络&#xff0c;发现没连&#xff0c;连了一下步骤如下&#xff1a;&#xff08;跟着图做就行&#xff09; 连了之后还是不…

Unity 实现原神中的元素反应

一、元素反应 原神中共有七种元素&#xff0c;分别是水、火、冰、岩、风、雷、草。这七种元素能互相作用 Demo下载&#xff1a;Download 元素反应表格图示&#xff0c;可能不够精准 /火水雷冰草岩风绽放原激化火/蒸发超载融化燃烧结晶扩散烈绽放/水蒸发/感电冻结/碎冰绽放结晶…

苹果电脑装虚拟机好用吗 苹果电脑装虚拟机要钱吗 Parallels对mac的损害 Parallels占用多大空间 PD19

在当今数字化的时代&#xff0c;人们对电脑系统跨设备互联的需求越来越高。作为拥有广泛用户群体的苹果电脑&#xff0c;许多用户会有在Mac系统中运行其他操作系统的需求。在这种情况下&#xff0c;安装虚拟机是一个较好的解决方案。那么接下来就给大家介绍苹果电脑装虚拟机好用…

重发布的原理及其应用

重发布的作用&#xff1a; 在一个网络中&#xff0c;若运行多种路由协议或者相同协议的不同进程&#xff1b;因为协议之间不能直接沟通计算&#xff0c;进程之间也是独立进行转发和运算的&#xff0c;所以&#xff0c;需要使用重发布来实现路由的共享。 条件 &#xff1a; 1&am…

数据挖掘实验(Apriori,fpgrowth)

Apriori&#xff1a;这里做了个小优化&#xff0c;比如abcde和adcef自连接出的新项集abcdef&#xff0c;可以用abcde的位置和f的位置取交集&#xff0c;这样第n项集的计算可以用n-1项集的信息和数字本身的位置信息计算出来&#xff0c;只需要保存第n-1项集的位置信息就可以提速…

Scala 04 —— Scala Puzzle 拓展

Scala 04 —— Scala Puzzle 拓展 文章目录 Scala 04 —— Scala Puzzle 拓展一、占位符二、模式匹配的变量和常量模式三、继承 成员声明的位置结果初始化顺序分析BMember 类BConstructor 类 四、缺省初始值与重载五、Scala的集合操作和集合类型保持一致性第一部分代码解释第二…

Scrapy爬虫框架入门(豆瓣电影Top 250)

文章目录 Scrapy 官网Scrapy 文档GithubScrapy 简介项目结构爬虫实现XPath 教程创建 Scrapy 项目配置用户代理网页 dom 元素 IP 代理池IP代理池作用配置IP代理池申请IP代理池 Scrapy 官网 https://scrapy.org/ Scrapy 文档 https://docs.scrapy.org/en/latest/ Github htt…