简单封装网页app


只要做一个“空壳” APP,它唯一的目的就是打开您在网址(server.url),然后适应手机的手势。

步骤一

1.打开 cmd 命令行,进入这个新的、空的文件夹:

##新建文件夹
mkdir d:\Apps\dav
##进入文件夹目录
d:
cd D:\Apps\dav

2. 安装“打包工具”:

npm install @capacitor/core @capacitor/android @capacitor/cli

3. 初始化 Capacitor:

npx cap init
  • App Name? -> 我的导航 (您自己起名)

  • App ID? -> com.xxsky.nav (您自己起ID)

  • web asset directory? -> public

4. 创建“欺骗”用的文件夹和文件:

  • (npx cap init 不会创建 public 文件夹,我们必须手动创建)

  • 在 cmd 中运行:

mkdir public
  • (public 文件夹里必须有一个 index.html)

  • 在 cmd 中运行:

echo "<html></html>" > public/index.html
  • 修改配置文件:

    • 用记事本打开 D:\Tools\Nav\capacitor.config.json。

    • 确保它看起来是这样的(webDir: ‘public’ 并且有 server.url):

{
  "appId": "com.myname.nav",
  "appName": "我的导航",
  "webDir": "public",

  "server": {
    "url": "https://[您的导航站网址]",
    "cleartext": true
  }
}

5. 添加 Android 平台:

npx cap add android

6. 同步配置:

npx cap sync android

步骤二

1. 注入“链接”修复代码

  • 打开 Android Studio

  • 打开您的新项目:D:\Tools\MyNavApp\android。

  • 等待 Gradle 同步(这会很慢,因为它是一个新项目,需要下载“零件”)。

  • 【关键】 找到您的 MainActivity.java:

  • app -> java -> com.myname.nav (您在步骤1里起的新ID) -> MainActivity.java

  • 双击打开它。

  • 删除里面的所有内容。

  • 粘贴这个新的、专门为“导航站”准备的“魔法代码”:

// 【【【【【 这是您在 init 时的 ID 】】】】】
package com.xxsky.nav; 

import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;
// (注意:我们不再需要 "OnBackPressedCallback" 了)

public class MainActivity extends BridgeActivity {

    // 我们需要把 webView 变量放在这里,以便“返回键”能“看到”它
    private WebView webView; 

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); 

        // 1. 【链接修复】(和之前一样)
        //    我们把 webView 存到“全局”变量里
        this.webView = this.bridge.getWebView();

        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url); 
                return true;
            }
        });
    }

    // --- 2. 【【【这就是“返回键”的“终极修复”(安全版)】】】 ---

    // 我们“覆盖”那个“旧的、可靠的” onBackPressed 函数
    @Override
    public void onBackPressed() {
        // a. 当用户按下“返回”...

        // b. ...我们检查“浏览器”是否可以后退
        if (this.webView.canGoBack()) {
            // c. ...如果可以,我们就在“浏览器”里后退
            this.webView.goBack();
        } else {
            // d. ...如果不能(在主页),我们就调用“系统默认”的返回 (也就是“退出APP”)
            super.onBackPressed();
        }
    }
}

或者有沉浸式功能的

// 【【【 关键:这是您的 ID !】】】
package com.navgo.hxjx;

import android.graphics.Color; // <-- 【新】导入“颜色”
import android.os.Bundle;
import android.view.View; // <-- 【新】导入“View”
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.WebViewListener; // 导入“安全”工具

public class MainActivity extends BridgeActivity {

    // 我们需要把 webView 变量放在这里,以便“返回键”能“看到”它
    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // --- 【【【这就是“沉浸式”的“终极修复”】】】 ---
        // 这会“强行”让窗口画在状态栏下面 (导致“重叠”)
        // 并且把状态栏设为“透明”
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        );
        getWindow().setStatusBarColor(Color.TRANSPARENT);
        //

        // 1. 【链接修复】(和之前一样)
        //    我们把 webView 存到“全局”变量里
        this.webView = this.bridge.getWebView();

        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
    }

    // --- 2. 【【【这就是“返回键”的“终极修复”(安全版)】】】 ---

    // 我们“覆盖”那个“旧的、可靠的” onBackPressed 函数
    @Override
    public void onBackPressed() {
        // a. 当用户按下“返回”...

        // b. ...我们检查“浏览器”是否可以后退
        if (this.webView.canGoBack()) {
            // c. ...如果可以,我们就在“浏览器”里后退
            this.webView.goBack();
        } else {
            // d. ...如果不能(在主页),我们就调用“系统默认”的返回 (也就是“退出APP”)
            super.onBackPressed();
        }
    }
}

步骤三

更改 APP 图标

1:准备您的图标文件

您需要一张正方形的图片。

  • 格式: 最好是 .png 格式(支持透明背景)。

  • 尺寸: 尺寸越大越好,Android Studio 会自动帮您缩放。理想尺寸是 1024x1024 像素(512x512 也可以)。

  • 注意: 现代安卓图标有“安全区域”(通常是中间 66% 的圆形或圆角矩形)。请确保您的“核心”Logo 在这个安全区域内,否则它可能会被裁切。

  • 准备好这张 .png 文件,并把它放在您电脑上一个容易找到的位置(比如“桌面”)。

2:使用 Android Studio 的“Asset Studio”工具

  • 请回到您的 Android Studio(确保 D:\Tools\LunaTV\android 项目是打开的)。

  • 在左上角的“项目”文件浏览器中(您之前找 MainActivity.java 的地方)。

  • 右键点击最顶层的那个 app 文件夹(它有一个蓝色的“a”图标)。

  • 在弹出的菜单中,移动到 “New” (新建) -> “Image Asset” (图像资产)。

3. 配置您的新图标

  • 点击后,会弹出一个叫 “Asset Studio” 的新窗口。

  • Icon Type (图标类型): 保持默认的 Launcher Icons (Adaptive & Legacy)。

  • Name (名称): 保持默认的 ic_launcher。

  • Source Asset (源资产):

  • 在 Path (路径) 这一行,点击它最右边的“文件夹”图标。

  • 在弹出的文件选择器中,找到并选择您在步骤 1 中准备好的那张 .png 图标文件。

  • 点击 OK。

  • 调整 (Scaling):

  • 您会立刻在右侧的“预览”窗口看到您的图标。

  • 您可能会看到一个“安全区”的圆圈。

  • 在中间的 Resize (调整大小) 滑块上,您可以左右拖动,把您的图标缩放到您觉得合适的大小(通常是让它刚好填满那个“安全区”)。

  • (可选)Background Layer (背景层):

  • 如果您的 .png 是透明的,您可以在这里给它加一个纯色背景。

  • 在 Name: ic_launcher 的下方,您会看到三个标签页 (Tab):

    • Foreground Layer (前景层) <– 您现在在这里!(它是蓝色的)

    • Background Layer (背景层) <– 您要点击这里!

    • Options (选项)

  • 点击一下第二个标签页,也就是 Background Layer (背景层)

    • Source Asset -> Asset Type -> Color (颜色),
    • 然后选择您想要的背景色。

4. 生成图标

  • 当您对预览满意后,点击右下角的 “Next” (下一步) 按钮。

  • 它会弹出一个新窗口,显示它将要“覆盖”(Overwrite)一大堆旧的 ic_launcher.png 文件。

  • 这是正常的,这正是我们想要的。

  • 点击右下角的 “Finish” (完成) 按钮。

Android Studio 现在会自动生成所有不同尺寸(mdpi, hdpi, xhdpi 等)的图标,并替换掉项目里默认的那个 Capacitor 图标。

步骤四:

启动logo图标

1. 准备您的新图片

  • 请您准备好您自己的“启动页面”图片(例如,您桌面上有一个 logo.png)。

  • 重要:您不需要准备11个版本。您只需要一张高分辨率的 .png 文件。

  • 尺寸如:215*215,底色透明。

2. 添加logo.png

  • 请您再次找到并打开那个文件: app -> res -> drawable -> splash_layout.xml

  • 回到 Android Studio。

  • 在左侧文件树中,找到 res/drawable 文件夹。

  • 右键点击 drawable 文件夹->open in->Explorer" (在资源管理器中显示)。

  • Windows 文件夹会打开 drawable 文件夹。

  • 把logo.png 复制到这个文件夹里。 (我们不再需要 drawable-hdpi那些文件夹了)

3. 创建“智能”布局文件 (splash_layout.xml)

  • 回到 Android Studio。

  • 右键点击 res/drawable 文件夹。

  • 选择 “New” (新建) -> “Drawable Resource File” (Drawable 资源文件)。

  • File name (文件名): 请输入 splash_layout

  • Root element (根元素): 请输入 layer-list

  • 点击 OK。

  • 一个新的 splash_layout.xml 文件会生成。

  • 在资源管理器中记事本打开,然后替换粘贴以下全部代码:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <color android:color="?android:attr/colorBackground" /> 
    </item>

    <item
        android:width="200dp" 
        android:height="200dp"
        android:drawable="@drawable/logo"  
        android:gravity="center" />

</layer-list>

*(重要!) 在粘贴后,请务必修改 android:drawable="@drawable/logo" 这一行,把 logo 改成您自己的文件名(不带 .png)。

  • 保存并关闭 splash_layout.xml。

4. 告诉 APP 使用“新”的启动页

  • 打开 res/values/styles.xml 文件。

  • 找到 AppTheme.NoActionBarLaunch 这一段,整个<style>替换修改成

    <style name="AppTheme.NoActionBarLaunch" parent="AppTheme.NoActionBar">
        <item name="android:windowBackground">@drawable/splash_layout</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

或者

   <style name="AppTheme.NoActionBarLaunch" parent="Theme.AppCompat.Light.NoActionBar">
       <item name="android:windowBackground">@drawable/splash_layout</item>
       <item name="windowActionBar">false</item>
       <item name="windowNoTitle">true</item>
   </style>
  • 保存并关闭 styles.xml。

  • (可选,但推荐) 为了省空间,您可以删除掉那 11 张旧的 splash.png 文件了(在 drawable 文件夹下的 splash (11) 组上点右键 -> “Delete”)。

步骤五:

打包生成最终的 APK

1. 在 Android Studio 中,点击顶部菜单栏的 “Build” (构建)。

2. 选择 “Generate App Bundles or APKs” (生成…)。

3. 在子菜单中,选择 “Generate APKs” (生成 APKs)。

4. 等待构建完成(BUILD SUCCESSFUL)。

5. 在右下角弹出的通知中,点击 “Locate” (定位)。

6. 最终成品: 您会得到 app-debug.apk 文件,它现在是“布局完美”且“功能完美”的。

步骤六:

强制排除掉冲突的旧包

1.打开你的电脑文件夹,直接进入 C:\Users\Administrator.gradle 目录。

2.在该文件夹下新建一个文本文档,将其重命名为 init.gradle(必须确保删除了 .txt 后缀)。

3.用记事本打开这个文件,直接粘贴以下代码并保存:

allprojects {
    configurations.all {
        exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
        exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
    }
}

电脑里安装了 NVM for Windows(Node版本管理器)。

NVM 会强制接管系统的 Node.js 运行环境。只要它存在,你刚才通过官方安装包(.msi)手动安装的 24.20.0 就会被直接“屏蔽”掉,导致命令行依然固执地使用 NVM 锁定的旧版本(20.19.5)。

既然有 NVM 接管,最快的解决方式是直接用它的命令来切换版本,完全不需要去控制面板卸载东西。请直接在刚才那个黑色命令行窗口中按顺序执行以下命令:

  • 下载新版本 输入 nvm install 24.20.0 并回车,等待下载完成。

  • 切换到新版本 输入 nvm use 24.20.0 并回车,系统会提示版本已切换。

  • 验证并继续 输入 node -v,此时屏幕应该会正确显示 v24.20.0。 确认无误后,直接输入你的打包同步命令 npx cap sync android 即可顺利执行。

步骤七:

net::ERR_CLEARTEXT_NOT_PERMITTED 是 Android 开发中最常见的安全限制问题

第一步:找到 AndroidManifest.xml 文件 在 Android Studio 左侧的目录树中,依次展开 app -> manifests,双击打开 AndroidManifest.xml。

第二步:添加允许明文流量的代码 在文件里找到 <application 开头的那一段,在它里面的空白处(不要破坏原有的代码)加上这一行核心指令:

android:usesCleartextTraffic="true"

加完之后,你的 <application 标签看起来应该是类似这样的:

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true"> <!-- 【就是加上这一行】 -->
        
        <!-- 下面是其他的 activity 等配置... -->
    </application>

第三步:重新打包运行 加完这行代码后直接保存,点击顶部的绿色三角形(Run)重新编译安装到手机。

再次打开你的导航 App,点击局域网的 Emby、Oplist 等内网 HTTP 链接,就可以完美顺畅地跳转打开了,不会再有任何拦截。

附录一:

1.longpt网站封装带下载种子功能的MainActivity.java

// 【【【 关键:这是您的 ID !】】】
package com.xxsky.longpt;

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.WebViewListener;

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.widget.Toast;

// 【新增的解码与正则表达式工具】
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainActivity extends BridgeActivity {

    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // --- 【保留】沉浸式状态栏修复 ---
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        );
        getWindow().setStatusBarColor(Color.TRANSPARENT);

        // --- 【保留】链接覆盖修复 ---
        this.webView = this.bridge.getWebView();
        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

        // --- 【修复】接管下载功能并使用正则处理 PT 站文件名 ---
        this.webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

                // 传递网页 Cookie 绕过 PT 站下载权限拦截
                String cookies = CookieManager.getInstance().getCookie(url);
                request.addRequestHeader("cookie", cookies);
                request.addRequestHeader("User-Agent", userAgent);

                // 1. 获取基础文件名(安卓默认 API,通常会取到 download.php)
                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);

                // 2. 利用正则表达式从 HTTP 头部强行提取被 URL 编码的真实文件名
                if (contentDisposition != null) {
                    try {
                        Matcher m1 = Pattern.compile("filename\\*\\s*=\\s*UTF-8''([^;]+)", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        Matcher m2 = Pattern.compile("filename\\s*=\\s*\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        if (m1.find()) {
                            fileName = URLDecoder.decode(m1.group(1), "UTF-8");
                        } else if (m2.find()) {
                            fileName = URLDecoder.decode(m2.group(1), "UTF-8");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                // 3. 正则清理标签:剔除开头无用的站点前缀,保持名称干净并保留 H265、2160p 等关键参数
                fileName = fileName.replaceAll("^\\[.*?\\][\\.\\s_-]*", "");

                // 4. 极端情况兜底防错,确保后缀正确
                if (fileName.startsWith("download.php")) {
                    fileName = "PT_Torrent_" + System.currentTimeMillis() + ".torrent";
                } else if (!fileName.endsWith(".torrent") && mimeType != null && mimeType.contains("torrent")) {
                    fileName = fileName + ".torrent";
                }

                // 呼出系统下载器并存入 Download 文件夹
                request.setDescription("正在下载...");
                request.setTitle(fileName);
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

                DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                dm.enqueue(request);

                Toast.makeText(getApplicationContext(), "开始下载: " + fileName, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // --- 【保留】返回键逻辑修复 ---
    @Override
    public void onBackPressed() {
        if (this.webView.canGoBack()) {
            this.webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}

2.增加自动复制功能

// 【【【 关键:这是您的 ID !】】】
package com.xxsky.longpt;

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.widget.Toast;

import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// --- 【新增的剪贴板必备工具】 ---
import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.Context;

public class MainActivity extends BridgeActivity {

    private WebView webView;

    // --- 【新增核心】这是一个连接网页和安卓原生剪贴板的“桥梁” ---
    class JSBridge {
        @android.webkit.JavascriptInterface
        public void copyText(String text) {
            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            ClipData clip = ClipData.newPlainText("Copied", text);
            clipboard.setPrimaryClip(clip);
            // 呼出安卓原生底层提示,无视网页限制
            runOnUiThread(() -> Toast.makeText(getApplicationContext(), "✅ 已自动复制: " + text, Toast.LENGTH_SHORT).show());
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // --- 【保留】沉浸式状态栏修复 ---
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        );
        getWindow().setStatusBarColor(Color.TRANSPARENT);

        this.webView = this.bridge.getWebView();
        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // --- 【保留】电脑端网页自适应与缩放优化 ---
        webSettings.setUseWideViewPort(true); 
        webSettings.setLoadWithOverviewMode(true); 
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false); 

        // --- 【保留】防频繁掉线与多媒体加载优化 ---
        webSettings.setDomStorageEnabled(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

        // --- 【关键配置】将剪贴板桥梁注册到内核中 ---
        this.webView.addJavascriptInterface(new JSBridge(), "AndroidClipboard");

        // --- 【原生增强】如果长按的是标准的超链接,直接底层拦截并复制 ---
        this.webView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                WebView.HitTestResult result = ((WebView) v).getHitTestResult();
                if (result != null && result.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
                    String url = result.getExtra();
                    if (url != null) {
                        ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                        clipboard.setPrimaryClip(ClipData.newPlainText("URL", url));
                        Toast.makeText(getApplicationContext(), "✅ 链接已复制", Toast.LENGTH_SHORT).show();
                        return true; 
                    }
                }
                return false;
            }
        });

        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                
                // --- 【黑科技】强制注入“长按一键自动复制”脚本 ---
                view.evaluateJavascript(
                    "javascript:(function() { " +
                    "  var style = document.createElement('style');" +
                    "  style.innerHTML = '* { -webkit-user-select: text !important; user-select: text !important; }';" +
                    "  document.head.appendChild(style);" +
                    "  var longPressTimer;" +
                    "  document.addEventListener('touchstart', function(e) { " +
                    "    longPressTimer = setTimeout(function() { " +
                    "      var text = window.getSelection().toString();" +
                    "      if(!text) { text = (e.target.innerText || e.target.value || '').trim(); }" +
                    "      if(text && text.length > 0) { AndroidClipboard.copyText(text); } " +
                    "    }, 600); " + // 手指按住 0.6 秒即触发自动复制
                    "  });" +
                    "  document.addEventListener('touchend', function() { clearTimeout(longPressTimer); });" +
                    "  document.addEventListener('touchmove', function() { clearTimeout(longPressTimer); });" +
                    "})()", null);
            }
        });

        // --- 【保留】接管下载功能并使用正则处理 PT 站文件名 ---
        this.webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

                String cookies = CookieManager.getInstance().getCookie(url);
                request.addRequestHeader("cookie", cookies);
                request.addRequestHeader("User-Agent", userAgent);

                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);

                if (contentDisposition != null) {
                    try {
                        Matcher m1 = Pattern.compile("filename\\*\\s*=\\s*UTF-8''([^;]+)", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        Matcher m2 = Pattern.compile("filename\\s*=\\s*\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        if (m1.find()) {
                            fileName = URLDecoder.decode(m1.group(1), "UTF-8");
                        } else if (m2.find()) {
                            fileName = URLDecoder.decode(m2.group(1), "UTF-8");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                fileName = fileName.replaceAll("^\\[.*?\\][\\.\\s_-]*", "");

                if (fileName.startsWith("download.php")) {
                    fileName = "PT_Torrent_" + System.currentTimeMillis() + ".torrent";
                } else if (!fileName.endsWith(".torrent") && mimeType != null && mimeType.contains("torrent")) {
                    fileName = fileName + ".torrent";
                }

                request.setDescription("正在下载...");
                request.setTitle(fileName);
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

                DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                dm.enqueue(request);

                Toast.makeText(getApplicationContext(), "开始下载: " + fileName, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // --- 【保留】返回键逻辑修复 ---
    @Override
    public void onBackPressed() {
        if (this.webView.canGoBack()) {
            this.webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}

3.复制功能弹窗

// 【【【 关键:这是您的 ID !】】】
package com.xxsky.longpt;

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.widget.Toast;

import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// --- 【新增:原生菜单与剪贴板需要的组件】 ---
import android.view.ContextMenu;
import android.view.MenuItem;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.ClipData;

public class MainActivity extends BridgeActivity {

    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // --- 【保留】沉浸式状态栏修复 ---
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        );
        getWindow().setStatusBarColor(Color.TRANSPARENT);

        this.webView = this.bridge.getWebView();
        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // --- 【保留】电脑端网页自适应与缩放优化 ---
        webSettings.setUseWideViewPort(true); 
        webSettings.setLoadWithOverviewMode(true); 
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false); 

        // --- 【保留】防频繁掉线与多媒体加载优化 ---
        webSettings.setDomStorageEnabled(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

        // --- 【终极修复】向安卓系统注册原生长按菜单 ---
        this.webView.setLongClickable(true);
        registerForContextMenu(this.webView); // 核心:让 WebView 拥有呼出原生菜单的权限

        // --- 【保留】破解网页 JS 的防复制机制 ---
        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                view.evaluateJavascript(
                    "javascript:(function() { " +
                    "var style = document.createElement('style');" +
                    "style.innerHTML = '* { -webkit-user-select: text !important; user-select: text !important; -webkit-touch-callout: default !important; }';" +
                    "document.head.appendChild(style);" +
                    "})()", null);
            }
        });

        // --- 【保留】接管下载功能并使用正则处理 PT 站文件名 ---
        this.webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

                String cookies = CookieManager.getInstance().getCookie(url);
                request.addRequestHeader("cookie", cookies);
                request.addRequestHeader("User-Agent", userAgent);

                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);

                if (contentDisposition != null) {
                    try {
                        Matcher m1 = Pattern.compile("filename\\*\\s*=\\s*UTF-8''([^;]+)", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        Matcher m2 = Pattern.compile("filename\\s*=\\s*\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        if (m1.find()) {
                            fileName = URLDecoder.decode(m1.group(1), "UTF-8");
                        } else if (m2.find()) {
                            fileName = URLDecoder.decode(m2.group(1), "UTF-8");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                fileName = fileName.replaceAll("^\\[.*?\\][\\.\\s_-]*", "");

                if (fileName.startsWith("download.php")) {
                    fileName = "PT_Torrent_" + System.currentTimeMillis() + ".torrent";
                } else if (!fileName.endsWith(".torrent") && mimeType != null && mimeType.contains("torrent")) {
                    fileName = fileName + ".torrent";
                }

                request.setDescription("正在下载...");
                request.setTitle(fileName);
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

                DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                dm.enqueue(request);

                Toast.makeText(getApplicationContext(), "开始下载: " + fileName, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // ==========================================================
    // --- 【新增核心】创建长按原生菜单 ---
    // ==========================================================
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        
        // 探测用户手指当前按在什么东西上
        WebView.HitTestResult result = this.webView.getHitTestResult();
        if (result != null) {
            int type = result.getType();
            
            // 如果按的是链接(比如 RSS 地址)
            if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                menu.setHeaderTitle("操作链接");
                menu.add(0, 1, 0, "复制链接地址"); // 动态添加一个复制按钮
            } 
            // 如果按的是纯图片
            else if (type == WebView.HitTestResult.IMAGE_TYPE) {
                menu.setHeaderTitle("操作图片");
                menu.add(0, 2, 0, "复制图片链接");
            }
        }
    }

    // --- 【新增核心】点击菜单后的执行动作 ---
    @Override
    public boolean onContextItemSelected(MenuItem item) {
        WebView.HitTestResult result = this.webView.getHitTestResult();
        if (result == null) return false;

        String url = result.getExtra(); // 抓取底层真实的网址
        if (url == null) return false;

        // 如果用户点击了“复制”
        if (item.getItemId() == 1 || item.getItemId() == 2) {
            // 调用安卓系统剪贴板
            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            ClipData clip = ClipData.newPlainText("Copied Link", url);
            clipboard.setPrimaryClip(clip);
            
            // 底部弹个小提示
            Toast.makeText(this, "✅ 已复制到剪贴板", Toast.LENGTH_SHORT).show();
            return true;
        }
        return super.onContextItemSelected(item);
    }
    // ==========================================================


    // --- 【保留】返回键逻辑修复 ---
    @Override
    public void onBackPressed() {
        if (this.webView.canGoBack()) {
            this.webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}

4.沉浸式

// 【【【 关键:这是您的 ID !】】】
package com.xxsky.longpt;

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.widget.Toast;

import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// --- 【新增:独立弹窗与剪贴板组件】 ---
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.ClipData;

public class MainActivity extends BridgeActivity {

    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // --- 【保留】沉浸式状态栏修复 ---
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        );
        getWindow().setStatusBarColor(Color.TRANSPARENT);

        this.webView = this.bridge.getWebView();
        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // --- 【保留】电脑端网页自适应与缩放优化 ---
        webSettings.setUseWideViewPort(true); 
        webSettings.setLoadWithOverviewMode(true); 
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false); 

        // --- 【保留】防频繁掉线与多媒体加载优化 ---
        webSettings.setDomStorageEnabled(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

        // --- 【保留】破解网页 JS 的防复制机制 ---
        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                view.evaluateJavascript(
                    "javascript:(function() { " +
                    "var style = document.createElement('style');" +
                    "style.innerHTML = '* { -webkit-user-select: text !important; user-select: text !important; -webkit-touch-callout: default !important; }';" +
                    "document.head.appendChild(style);" +
                    "document.oncontextmenu = null; " +
                    "window.oncontextmenu = null; " +
                    "document.onselectstart = null; " +
                    "})()", null);
            }
        });

        // ==========================================================
        // --- 【终极修复】独立剥离的长按事件,彻底杜绝缩放误触 ---
        // ==========================================================
        this.webView.setLongClickable(true);
        this.webView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                WebView.HitTestResult result = ((WebView) v).getHitTestResult();
                if (result != null) {
                    int type = result.getType();
                    
                    // 1. 如果用户长按的是超链接(比如 RSS 地址)
                    if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("链接操作")
                                .setItems(new String[]{"复制链接地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Link", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 链接已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true; // 告诉系统:这个长按我处理了,不要再触发其他动作
                        }
                    } 
                    // 2. 如果用户长按的是图片
                    else if (type == WebView.HitTestResult.IMAGE_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("图片操作")
                                .setItems(new String[]{"复制图片地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Image", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 图片地址已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true;
                        }
                    }
                }
                // 3. 如果长按的是普通文字,返回 false,让系统自己弹出蓝色的光标供用户拖选
                return false; 
            }
        });
        // ==========================================================


        // --- 【保留】接管下载功能并使用正则处理 PT 站文件名 ---
        this.webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

                String cookies = CookieManager.getInstance().getCookie(url);
                request.addRequestHeader("cookie", cookies);
                request.addRequestHeader("User-Agent", userAgent);

                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);

                if (contentDisposition != null) {
                    try {
                        Matcher m1 = Pattern.compile("filename\\*\\s*=\\s*UTF-8''([^;]+)", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        Matcher m2 = Pattern.compile("filename\\s*=\\s*\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        if (m1.find()) {
                            fileName = URLDecoder.decode(m1.group(1), "UTF-8");
                        } else if (m2.find()) {
                            fileName = URLDecoder.decode(m2.group(1), "UTF-8");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                fileName = fileName.replaceAll("^\\[.*?\\][\\.\\s_-]*", "");

                if (fileName.startsWith("download.php")) {
                    fileName = "PT_Torrent_" + System.currentTimeMillis() + ".torrent";
                } else if (!fileName.endsWith(".torrent") && mimeType != null && mimeType.contains("torrent")) {
                    fileName = fileName + ".torrent";
                }

                request.setDescription("正在下载...");
                request.setTitle(fileName);
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

                DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                dm.enqueue(request);

                Toast.makeText(getApplicationContext(), "开始下载: " + fileName, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // --- 【保留】返回键逻辑修复 ---
    @Override
    public void onBackPressed() {
        if (this.webView.canGoBack()) {
            this.webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}

5.标准式

// 【【【 关键:这是您的 ID !】】】
package com.xxsky.nav; 

import android.os.Bundle;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.widget.Toast;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.ClipData;

public class MainActivity extends BridgeActivity {

    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 【删除了沉浸式状态栏配置,使用安卓系统默认状态栏】

        this.webView = this.bridge.getWebView();
        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // --- 电脑端网页自适应与缩放优化 ---
        webSettings.setUseWideViewPort(true); 
        webSettings.setLoadWithOverviewMode(true); 
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false); 

        // --- 防频繁掉线与多媒体加载优化 ---
        webSettings.setDomStorageEnabled(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

        // --- 破解网页 JS 防复制机制 ---
        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                view.evaluateJavascript(
                    "javascript:(function() { " +
                    "var style = document.createElement('style');" +
                    "style.innerHTML = '* { -webkit-user-select: text !important; user-select: text !important; -webkit-touch-callout: default !important; }';" +
                    "document.head.appendChild(style);" +
                    "document.oncontextmenu = null; " +
                    "window.oncontextmenu = null; " +
                    "document.onselectstart = null; " +
                    "})()", null);
            }
        });

        // --- 独立弹窗长按事件(解决缩放误触) ---
        this.webView.setLongClickable(true);
        this.webView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                WebView.HitTestResult result = ((WebView) v).getHitTestResult();
                if (result != null) {
                    int type = result.getType();
                    if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("链接操作")
                                .setItems(new String[]{"复制链接地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Link", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 链接已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true; 
                        }
                    } else if (type == WebView.HitTestResult.IMAGE_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("图片操作")
                                .setItems(new String[]{"复制图片地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Image", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 图片地址已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true;
                        }
                    }
                }
                return false; 
            }
        });

        // --- 接管原生下载功能 ---
        this.webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
                String cookies = CookieManager.getInstance().getCookie(url);
                request.addRequestHeader("cookie", cookies);
                request.addRequestHeader("User-Agent", userAgent);

                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);
                if (contentDisposition != null) {
                    try {
                        Matcher m1 = Pattern.compile("filename\\*\\s*=\\s*UTF-8''([^;]+)", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        Matcher m2 = Pattern.compile("filename\\s*=\\s*\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        if (m1.find()) {
                            fileName = URLDecoder.decode(m1.group(1), "UTF-8");
                        } else if (m2.find()) {
                            fileName = URLDecoder.decode(m2.group(1), "UTF-8");
                        }
                    } catch (Exception e) {}
                }

                fileName = fileName.replaceAll("^\\[.*?\\][\\.\\s_-]*", "");
                if (fileName.startsWith("download.php")) {
                    fileName = "PT_Torrent_" + System.currentTimeMillis() + ".torrent";
                } else if (!fileName.endsWith(".torrent") && mimeType != null && mimeType.contains("torrent")) {
                    fileName = fileName + ".torrent";
                }

                request.setDescription("正在下载...");
                request.setTitle(fileName);
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

                DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                dm.enqueue(request);
                Toast.makeText(getApplicationContext(), "开始下载: " + fileName, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // --- 返回键逻辑修复 ---
    @Override
    public void onBackPressed() {
        if (this.webView.canGoBack()) {
            this.webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}

附录二:

下拉刷新功能、下载功能、复制功能的MainActivity.java与build.gradle (Module: app)的配置

一、引入安卓系统的“下拉刷新”组件

1.AndroidX 现在的“下拉刷新”是一个独立的模块,我们需要告诉底层引擎去下载它。

在 Android Studio 左侧目录,找到 Gradle Scripts -> build.gradle (Module: app) 并双击打开。

2.滑到文件最底部的 dependencies { … } 区域,在里面新起一行,加入这行代码:

implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0'

3.点击右上角弹出的蓝色大象图标(Sync Now),等进度条跑完

菜单栏(终极兜底): 如果什么图标都没找到,直接点击最左上角的顶部菜单:File -> Sync Project with Gradle Files。

二、更新 MainActivity.java 终极代码

1.【带沉浸式状态栏】(顶栏透明,适合大部分现代网页) 网页会全屏顶到状态栏下面,视觉效果最好。

// 【【【 关键:这是您的 ID !】】】
package com.xxsky.nav; 

import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup; 
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.widget.Toast;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.ClipData;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 
import android.view.ViewTreeObserver; // 【核心】判断滑动位置

public class MainActivity extends BridgeActivity {

    private WebView webView;
    private SwipeRefreshLayout swipeRefreshLayout;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // --- 【沉浸式状态栏配置】 ---
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        );
        getWindow().setStatusBarColor(Color.TRANSPARENT);

        this.webView = this.bridge.getWebView();
        
        // --- 下拉刷新容器包裹 ---
        // (注:如果是封装 Emby,强烈建议把这块 SwipeRefreshLayout 相关的代码全删掉)
        this.swipeRefreshLayout = new SwipeRefreshLayout(this);
        ViewGroup parent = (ViewGroup) this.webView.getParent();
        parent.removeView(this.webView);
        
        this.swipeRefreshLayout.addView(this.webView, new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 
                ViewGroup.LayoutParams.MATCH_PARENT));
                
        parent.addView(this.swipeRefreshLayout, new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 
                ViewGroup.LayoutParams.MATCH_PARENT));

        this.swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                webView.reload();
            }
        });

        // ==========================================================
        // --- 【核心修复】解决下拉刷新与网页滑动的冲突 ---
        // ==========================================================
        this.webView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (swipeRefreshLayout != null) {
                    // 仅当整个网页真正处于最顶部时,才允许下拉刷新(对 Emby 等内部滚动网页无效)
                    swipeRefreshLayout.setEnabled(webView.getScrollY() == 0);
                }
            }
        });
        // ==========================================================

        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // --- 电脑端网页自适应与缩放优化 ---
        webSettings.setUseWideViewPort(true); 
        webSettings.setLoadWithOverviewMode(true); 
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false); 

        // --- 防频繁掉线与多媒体加载优化 ---
        webSettings.setDomStorageEnabled(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

        // --- 破解网页 JS 防复制机制 + 控制下拉动画 ---
        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                if (swipeRefreshLayout != null) {
                    swipeRefreshLayout.setRefreshing(false); // 停止刷新动画
                }
                view.evaluateJavascript(
                    "javascript:(function() { " +
                    "var style = document.createElement('style');" +
                    "style.innerHTML = '* { -webkit-user-select: text !important; user-select: text !important; -webkit-touch-callout: default !important; }';" +
                    "document.head.appendChild(style);" +
                    "document.oncontextmenu = null; " +
                    "window.oncontextmenu = null; " +
                    "document.onselectstart = null; " +
                    "})()", null);
            }
        });

        // --- 独立弹窗长按事件(解决缩放误触) ---
        this.webView.setLongClickable(true);
        this.webView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                WebView.HitTestResult result = ((WebView) v).getHitTestResult();
                if (result != null) {
                    int type = result.getType();
                    if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("链接操作")
                                .setItems(new String[]{"复制链接地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Link", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 链接已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true; 
                        }
                    } else if (type == WebView.HitTestResult.IMAGE_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("图片操作")
                                .setItems(new String[]{"复制图片地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Image", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 图片地址已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true;
                        }
                    }
                }
                return false; 
            }
        });

        // --- 接管原生下载功能并正则优化文件名 ---
        this.webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
                String cookies = CookieManager.getInstance().getCookie(url);
                request.addRequestHeader("cookie", cookies);
                request.addRequestHeader("User-Agent", userAgent);

                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);
                if (contentDisposition != null) {
                    try {
                        Matcher m1 = Pattern.compile("filename\\*\\s*=\\s*UTF-8''([^;]+)", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        Matcher m2 = Pattern.compile("filename\\s*=\\s*\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        if (m1.find()) {
                            fileName = URLDecoder.decode(m1.group(1), "UTF-8");
                        } else if (m2.find()) {
                            fileName = URLDecoder.decode(m2.group(1), "UTF-8");
                        }
                    } catch (Exception ignored) {} 
                }

                fileName = fileName.replaceAll("^\\[.*?\\][\\.\\s_-]*", "");
                if (fileName.startsWith("download.php")) {
                    fileName = "PT_Torrent_" + System.currentTimeMillis() + ".torrent";
                } else if (!fileName.endsWith(".torrent") && mimeType != null && mimeType.contains("torrent")) {
                    fileName = fileName + ".torrent";
                }

                request.setDescription("正在下载...");
                request.setTitle(fileName);
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

                DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                dm.enqueue(request);
                Toast.makeText(getApplicationContext(), "开始下载: " + fileName, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // --- 返回键逻辑修复 ---
    @Override
    public void onBackPressed() {
        if (this.webView.canGoBack()) {
            this.webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}

2.【不带沉浸式状态栏】(常规标准版) 保留系统顶部的原生黑色/白色状态栏,不会遮挡网页原本最上面的菜单内容,兼容性最强。

// 【【【 关键:这是您的 ID !】】】
package com.xxsky.nav; 

import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup; 
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.widget.Toast;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.ClipData;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 
import android.view.ViewTreeObserver; // 【新】引入视图滚动监听器

public class MainActivity extends BridgeActivity {

    private WebView webView;
    private SwipeRefreshLayout swipeRefreshLayout;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.webView = this.bridge.getWebView();
        
        // --- 下拉刷新容器包裹 ---
        this.swipeRefreshLayout = new SwipeRefreshLayout(this);
        ViewGroup parent = (ViewGroup) this.webView.getParent();
        parent.removeView(this.webView);
        
        this.swipeRefreshLayout.addView(this.webView, new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 
                ViewGroup.LayoutParams.MATCH_PARENT));
                
        parent.addView(this.swipeRefreshLayout, new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 
                ViewGroup.LayoutParams.MATCH_PARENT));

        this.swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                webView.reload();
            }
        });

        // ==========================================================
        // --- 【终极修复】解决下拉刷新与网页滑动的冲突 ---
        // ==========================================================
        this.webView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (swipeRefreshLayout != null) {
                    // 只有当网页在最顶部(Y轴距离为0)时,才允许触发下拉刷新
                    swipeRefreshLayout.setEnabled(webView.getScrollY() == 0);
                }
            }
        });
        // ==========================================================

        WebSettings webSettings = this.webView.getSettings();
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // --- 电脑端网页自适应与缩放优化 ---
        webSettings.setUseWideViewPort(true); 
        webSettings.setLoadWithOverviewMode(true); 
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false); 

        // --- 防频繁掉线与多媒体加载优化 ---
        webSettings.setDomStorageEnabled(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

        // --- 破解网页 JS 防复制机制 + 控制下拉动画 ---
        this.webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                if (swipeRefreshLayout != null) {
                    swipeRefreshLayout.setRefreshing(false); // 停止刷新动画
                }
                view.evaluateJavascript(
                    "javascript:(function() { " +
                    "var style = document.createElement('style');" +
                    "style.innerHTML = '* { -webkit-user-select: text !important; user-select: text !important; -webkit-touch-callout: default !important; }';" +
                    "document.head.appendChild(style);" +
                    "document.oncontextmenu = null; " +
                    "window.oncontextmenu = null; " +
                    "document.onselectstart = null; " +
                    "})()", null);
            }
        });

        // --- 独立弹窗长按事件(解决缩放误触) ---
        this.webView.setLongClickable(true);
        this.webView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                WebView.HitTestResult result = ((WebView) v).getHitTestResult();
                if (result != null) {
                    int type = result.getType();
                    if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("链接操作")
                                .setItems(new String[]{"复制链接地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Link", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 链接已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true; 
                        }
                    } else if (type == WebView.HitTestResult.IMAGE_TYPE) {
                        final String url = result.getExtra();
                        if (url != null) {
                            new AlertDialog.Builder(MainActivity.this)
                                .setTitle("图片操作")
                                .setItems(new String[]{"复制图片地址"}, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        if (which == 0) {
                                            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
                                            ClipData clip = ClipData.newPlainText("Copied Image", url);
                                            clipboard.setPrimaryClip(clip);
                                            Toast.makeText(MainActivity.this, "✅ 图片地址已复制", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                }).show();
                            return true;
                        }
                    }
                }
                return false; 
            }
        });

        // --- 接管原生下载功能并正则优化文件名 ---
        this.webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
                String cookies = CookieManager.getInstance().getCookie(url);
                request.addRequestHeader("cookie", cookies);
                request.addRequestHeader("User-Agent", userAgent);

                String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);
                if (contentDisposition != null) {
                    try {
                        Matcher m1 = Pattern.compile("filename\\*\\s*=\\s*UTF-8''([^;]+)", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        Matcher m2 = Pattern.compile("filename\\s*=\\s*\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE).matcher(contentDisposition);
                        if (m1.find()) {
                            fileName = URLDecoder.decode(m1.group(1), "UTF-8");
                        } else if (m2.find()) {
                            fileName = URLDecoder.decode(m2.group(1), "UTF-8");
                        }
                    } catch (Exception ignored) {} // 【顺手修复了警告:把 e 改成了 ignored】
                }

                fileName = fileName.replaceAll("^\\[.*?\\][\\.\\s_-]*", "");
                if (fileName.startsWith("download.php")) {
                    fileName = "PT_Torrent_" + System.currentTimeMillis() + ".torrent";
                } else if (!fileName.endsWith(".torrent") && mimeType != null && mimeType.contains("torrent")) {
                    fileName = fileName + ".torrent";
                }

                request.setDescription("正在下载...");
                request.setTitle(fileName);
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

                DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                dm.enqueue(request);
                Toast.makeText(getApplicationContext(), "开始下载: " + fileName, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // --- 返回键逻辑修复 ---
    @Override
    public void onBackPressed() {
        if (this.webView.canGoBack()) {
            this.webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}
技术 

也可以看看