【混合開發】vue+Android、iPhone、鴻蒙、win、macOS、Linux之android 把assert里的dist.zip 包解壓到sd卡里

一圖勝千言

在這里插入圖片描述
上一篇有

<!-- 讀寫外部存儲 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="28"/><!-- Android 10+ 用 MediaStore/SAF,無需額外權限 -->
  1. 運行時權限(Activity/Fragment)
private static final int REQ_CODE = 100;private void checkPermissionAndUnzip() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},REQ_CODE);return;}}unzipAssets();
}@Override
public void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQ_CODE && grantResults.length > 0&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {unzipAssets();} else {Toast.makeText(this, "需要存儲權限", Toast.LENGTH_SHORT).show();}
}
  1. 解壓工具類
public class ZipUtils {public static void unzipAsset(Context ctx, String assetName,File destDir) throws IOException {if (!destDir.exists()) destDir.mkdirs();try (InputStream in = ctx.getAssets().open(assetName);ZipInputStream zin = new ZipInputStream(in)) {ZipEntry entry;byte[] buffer = new byte[4096];while ((entry = zin.getNextEntry()) != null) {File file = new File(destDir, entry.getName());if (entry.isDirectory()) {file.mkdirs();} else {// 確保父目錄存在File parent = file.getParentFile();if (!parent.exists()) parent.mkdirs();try (FileOutputStream out = new FileOutputStream(file)) {int len;while ((len = zin.read(buffer)) != -1) {out.write(buffer, 0, len);}}}zin.closeEntry();}}}
}
  1. 調用解壓
private void unzipAssets() {new Thread(() -> {try {// 目標目錄:/storage/emulated/0/Android/<package>/web/distFile destDir = new File(Environment.getExternalStorageDirectory(),"Android/" + getPackageName() + "/web/dist");ZipUtils.unzipAsset(this, "dist.zip", destDir);runOnUiThread(() ->Toast.makeText(this, "解壓完成", Toast.LENGTH_SHORT).show());} catch (IOException e) {e.printStackTrace();runOnUiThread(() ->Toast.makeText(this, "解壓失敗:" + e.getMessage(),Toast.LENGTH_SHORT).show());}}).start();
}
  1. 使用示例
checkPermissionAndUnzip();

使用解壓結果

File webDir = new File(getFilesDir(), "web");
File indexHtml = new File(webDir, "index.html");

其他

net:ERR_ACCESS_DENIED

net::ERR_ACCESS_DENIED 并不是網絡錯誤,而是 WebView 拒絕訪問本地文件 的通用提示。
99% 的場景只踩了下面 3 個坑 之一,按清單逐條檢查即可解決。


? 1. 文件不在「允許路徑」里(最常見)

場景是否允許
/data/data/<包>/files/xxx? 私有目錄,允許
/storage/emulated/0/xxx? 需 可讀權限 + file:// 白名單
assets/res/raw/? 需 file:///android_asset/ 協議

正確打開姿勢

// 私有目錄 files/web/index.html
webView.loadUrl("file:///data/data/" + getPackageName() + "/files/web/index.html");// assets 目錄
webView.loadUrl("file:///android_asset/web/index.html");

? 2. 忘記開 JavaScript文件訪問

WebSettings ws = webView.getSettings();
ws.setJavaScriptEnabled(true);          // 必須
ws.setAllowFileAccess(true);            // 必須
ws.setAllowContentAccess(true);         // 建議
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {ws.setAllowFileAccessFromFileURLs(true);   // assets 里調 JS 需要ws.setAllowUniversalAccessFromFileURLs(true);
}

? 3. Android 10+ 分區存儲 限制

  • targetSdkVersion ≥ 29 且文件在 外部存儲 時,
    即使申請了 READ_EXTERNAL_STORAGE 也打不開。

快速解決(開發階段)

<applicationandroid:requestLegacyExternalStorage="true"... />

正式上架 請把文件放到:

  • getFilesDir() / getCacheDir()
  • assets/
  • 或使用 FileProvider 生成 content:// URI

? 4. 用了 FileProvider 卻給錯路徑(少見)

若用 FileProvider.getUriForFile() 生成 content:// 地址,必須

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

WebView 不支持 content:// 直接 loadUrl(),請轉回 file:// 或拷貝到私有目錄。


🎯 一鍵排查清單

檢查項正確示例
文件存在?new File(path).exists() 返回 true
路徑協議?file:///data/data/包名/files/xxx.html
JS 開關?setJavaScriptEnabled(true)
文件訪問?setAllowFileAccess(true)
外部存儲?requestLegacyExternalStorage="true" 或放私有目錄

? 最小可用代碼(私有目錄)

WebView webView = findViewById(R.id.webView);
WebSettings ws = webView.getSettings();
ws.setJavaScriptEnabled(true);
ws.setAllowFileAccess(true);File htmlFile = new File(getFilesDir(), "web/index.html");
webView.loadUrl("file://" + htmlFile.getAbsolutePath());

🎯 一句話總結

ERR_ACCESS_DENIED 99% 是 路徑不在白名單沒開 setAllowFileAccess(true);把文件放進 私有目錄assets 并按上面設置即可秒解。

net::ERR_ADDRESS_UNREACHABLE

code=-2 / desc=net::ERR_ADDRESS_UNREACHABLE 不是 WebView 的 BUG,而是 TCP 三層無法與目標地址建立連接 的通用報錯。
把常見成因做成「檢查清單」,按順序 1→7 秒定位即可。


🔍 1. 地址寫錯 / 端口未監聽

  • 用系統瀏覽器訪問 同一 URL,若也打不開 → 服務器或地址問題。
  • 檢查 IP、端口、大小寫、http/https

🔍 2. 本機網絡不通

  • 手機 飛行模式 / VPN / 代理 忘記關。
  • 電腦熱點 無 Internet
  • 公司/校園網 禁止設備互訪

🔍 3. DNS 解析失敗

  • 域名拼錯 → ping 域名 返回 unknown host
  • 手機 DNS 被污染 → 換 8.8.8.8 再測。

🔍 4. 防火墻 / 安全組攔截

  • 服務器 未放行端口 → telnet IP 端口 連不上。
  • 本地防火墻(Windows Defender / macOS 防火墻)阻斷入站。

🔍 5. 局域網地址在模擬器里寫錯

  • 模擬器 不是真機localhost / 127.0.0.1 指向 模擬器自己
  • 正確寫法:http://10.0.2.2:端口(Android 模擬器專用宿主機地址)。

🔍 6. 公司/測試 Wi-Fi 開啟 AP 隔離

  • 路由器后臺 → 關閉 AP 隔離 即可設備互通。

🔍 7. 代理 / VPN 全局攔截

  • 關閉 Charles / Fiddler / Clash 全局代理再測。
  • WebView 加代理頭:
    webView.setWebViewClient(new WebViewClient() {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {return false;   // 不攔截}
    });
    

? 最小排查腳本(adb 快速驗證)

adb shell ping -c 3 192.168.x.x      # 先看網絡通不通
adb shell curl -I http://192.168.x.x:3000

能通 → 代碼問題;不通 → 網絡問題。


? WebView 側兜底處理

webView.setWebViewClient(new WebViewClient() {@Overridepublic void onReceivedError(WebView view,WebResourceRequest request,WebResourceError error) {if (error.getErrorCode() == ERROR_HOST_LOOKUP ||error.getErrorCode() == ERROR_CONNECT ||error.getErrorCode() == ERROR_TIMEOUT) {view.loadUrl("file:///android_asset/offline.html");}}
});

🎯 一句話總結

ERR_ADDRESS_UNREACHABLE = 地址不可達,按「瀏覽器能否打開 → 模擬器地址是否正確 → 防火墻/代理是否攔截 → DNS 是否解析」四步排查,99% 秒定位。

net::ERR_FILE_NOT_FOUND

code=-1 / desc=net::ERR_FILE_FOUND 并不是網絡錯誤,而是 WebView 訪問本地文件時路徑寫錯或文件根本不存在
按下面 4 步一次性排查:


? 1. 文件真的存在嗎?

File f = new File(path);
Log.d("WEBVIEW", "exists=" + f.exists() + "  abs=" + f.getAbsolutePath());

exists=false → 路徑拼錯 / 沒拷進去 / 大小寫錯誤。


? 2. 路徑前綴必須拼對

位置正確前綴示例
私有目錄 getFilesDir()file:///data/data/包名/files/...file:///data/data/com.demo/files/web/index.html
外部存儲(SD)file:///storage/emulated/0/...file:///storage/emulated/0/Android/com.demo/web/index.html
assetsfile:///android_asset/...file:///android_asset/web/index.html

常見拼寫錯誤
? file://data/... (少一個 /
? file:///android_assets/... (多了 s


? 3. 空格 / 中文 / 特殊字符

本地文件含空格或中文 → URLEncoder 編碼

String path = new File(dir, "index 1.html").getAbsolutePath();
path = Uri.encode(path);          // 空格→%20
webView.loadUrl("file://" + path);

? 4. 用 FileProvider 給路徑(推薦 Android 7+)

防止 file:// 被禁止,統一用 content://

<!-- AndroidManifest.xml -->
<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" />
</provider>

res/xml/file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths><files-path name="web" path="web/" />
</paths>

Java 代碼:

File htmlFile = new File(getFilesDir(), "web/index.html");
Uri uri = FileProvider.getUriForFile(this,BuildConfig.APPLICATION_ID + ".fileprovider",htmlFile);
webView.loadUrl(uri.toString());

? 5. 兜底日志(復制即用)

webView.setWebViewClient(new WebViewClient() {@Overridepublic void onReceivedError(WebView view,WebResourceRequest request,WebResourceError error) {Log.e("WEBVIEW", "code=" + error.getErrorCode()+ " desc=" + error.getDescription()+ " url=" + request.getUrl().toString());}
});

打印出的 url 就是 WebView 實際訪問的地址,直接拷到瀏覽器/文件管理器 即可驗證是否存在。


🎯 一句話總結

ERR_FILE_NOT_FOUND = 路徑錯 or 文件不在,用 File.exists() 確認 → 拼對 file:///... → 特殊字符 Uri.encode() → 推薦 FileProvider 一步到位。

好用的開發工具

推薦理由

postman在國內使用已經越來越困難:
1、登錄問題嚴重
2、Mock功能服務基本沒法使用
3、版本更新功能已很匱乏
4、某些外力因素導致postman以后能否使用風險較大
5、postman會導致電腦卡頓,而且使用的功能越多越慢,尤其是win電腦,太讓人郁悶了
出于以上考慮因此筆者自己開發了一款api調試開發工具SmartApi,滿足基本日常開發調試api需求

SmartApi
win版本不大于1M;運行消耗性能極低
macos 版本不大于100M;運行消耗性能極低
非常適合開發設備或性能有限的開發環境

SmartApi只為開發服務

官網地址SmartApi

http://www.smartapi.site/

在這里插入圖片描述


舊版本已停止維護

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98768.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98768.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98768.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

線程的創建.銷毀

線程線程的創建在 C 中&#xff0c;線程的創建核心是通過std::thread類實現的&#xff0c;其構造函數需要傳入一個可調用對象&#xff08;Callable Object&#xff09;作為線程入口。可調用對象包括普通函數、lambda 表達式、函數對象&#xff08;functor&#xff09;、類的成員…

MySQL基礎全面解析

MySQL作為最流行的關系型數據庫管理系統之一&#xff0c;是每一位開發者必備的核心技能。本文將系統性地解析MySQL的基礎知識&#xff0c;結合關鍵概念與實戰應用&#xff0c;幫助您構建扎實的數據庫基礎。1. SQL與NoSQL的本質區別SQL&#xff08;結構化查詢語言&#xff09;數…

4、幽絡源微服務項目實戰:后端公共模塊創建與引入多租戶模塊

前言 上節我們將電網巡檢系統的前端vue2項目創建、配置&#xff0c;并構建了最基礎的多租戶界面&#xff0c;本節來繼續構建后端的公共模塊、多租戶模塊&#xff0c;并將公共模塊引入到多租戶模塊中。 創建公共模塊和多租戶模塊 在back父工程下創建兩個Module&#xff0c;和…

STM32學習路線開啟篇:芯片簡介與課程簡介

編寫不易,請多多指教,覺得不錯可以關注一下,相互學習 前言 一、課程配套資源 1、面包板 2、面包板專用的跳線 3、面包板的飛線 4、杜邦線 5、STM32F103C8T6最小系統板 6、0.96寸的OLED顯示屏模塊 7、電位器 8、按鈕 9、LED燈 10、STLINK 11、USB轉串口(TTL)模塊 12、源蜂鳴器模…

圖像直方圖

圖像直方圖就是用來統計圖像像素值分布的。灰度圖分布讀取灰度圖phone cv2.imread(phone.png, cv2.IMREAD_GRAYSCALE) a phone.ravel() plt.hist(a, bins256) plt.show()如何可以獲得當前像素值分布讀取各通道的像素值分布img cv2.imread(phone.png) colors (b, g, r) for …

分類別柱狀圖(Vue3)

效果圖&#xff1a;需求&#xff1a;男女年齡段占比<template><div class"go-ClassifyBar01"><v-chartref"vChartRef":option"option"style"width: 100%; height: 800px"></v-chart></div> </templa…

Apache Dubbo學習筆記-使用Dubbo發布、調用服務

Apache Dubbo經常作為一個RPC框架來使用&#xff0c;這篇文章主要介紹使用Dubbo配合注冊中心來發布和調用服務。 Apache Dubbo和Spring Boot、JDK的版本對應關系。 Dubbo 分支最新版本JDKSpring Boot組件版本詳細說明3.3.x (當前文檔)3.3.08, 17, 212.x、3.x詳情- 版本變更記錄…

Python學習——字典和文件

前面python的學習中我們已經學習了python的函數和列表元組相關的內容&#xff0c;接下來我們來學習剩下的python語法&#xff1a;字典和文件 相關代碼已經上傳到作者的個人gitee&#xff1a;樓田莉子/Python 學習喜歡請點個贊謝謝 目錄 字典 創建字典 查找key 新增/修改元素 …

swiper插件的使用

官方網址&#xff1a;https://www.swiper.com.cn/ 1、點擊導航欄&#xff0c;獲取Swiper里邊的下載Swiper 2、選擇要下載的版本【本次案例版本5.4.5】&#xff0c;然后解壓縮文件夾&#xff0c;拿到swiper.min.js和swiper.min.css文件&#xff0c;放到項目對應的css文件和js文…

Vue3+JS 組合式 API 實戰:從項目痛點到通用 Hook 封裝

Vue3 組合式 API 的實戰技巧 —— 組合式 API 幫我解決了不少 Options API 難以應對的問題&#xff0c;尤其是在代碼復用和復雜組件維護上。一、為什么放棄 Options API&#xff1f;聊聊三年項目里的真實痛點?剛接觸 Vue3 時&#xff0c;我曾因 “慣性” 繼續用 Options API 寫…

把 AI 塞進「電梯按鈕」——基于 64 kB 零樣本聲紋的離線故障預測按鈕

標簽&#xff1a;零樣本聲紋、電梯按鈕、離線 AI、TinyML、RISC-V、低功耗、GD32V303、故障預警 ---- 1. 背景&#xff1a;為什么按鈕要「聽聲音」&#xff1f; 全國 700 萬臺電梯&#xff0c;按鈕故障率 0.3 %/年&#xff0c;卻常出現&#xff1a; ? 機械卡滯、觸點氧化&…

清華大學聯合項目 論文解讀 | MoTo賦能雙臂機器人:實現零樣本移動操作

研究背景 移動操作是機器人領域的核心挑戰&#xff0c;它使機器人能夠在各種任務和動態日常環境中為人類提供幫助。傳統的移動操作方法由于缺乏大規模訓練&#xff0c;往往難以在不同任務和環境中實現泛化。而現有操作基礎模型雖在固定基座任務中表現出強泛化性&#xff0c;卻無…

go webrtc - 2 webrtc重要概念

webrtc是一套音視頻傳輸技術生態&#xff0c;不是一個協議或一個什么東西。3種模式本文基于 SFU 形式闡述&#xff01;重要概念&#xff1a;sfu 服務負責&#xff1a;信令 服務負責&#xff1a;peerConnection&#xff1a;track&#xff1a;房間&#xff1a;虛擬分組概念用戶&a…

“下游任務”概念詳解:從定義到應用場景

“下游任務”概念詳解&#xff1a;從定義到應用場景 一、什么是“下游任務”&#xff1f; 在機器學習&#xff08;尤其是深度學習&#xff09;中&#xff0c;“下游任務”&#xff08;Downstream Task&#xff09;是相對“上游過程”而言的目標任務——可以理解為&#xff1a;我…

視頻怎么做成 GIF?用 oCam 一鍵錄制 GIF 動畫超簡單

GIF 動圖因其生動直觀、無需點擊播放的特點&#xff0c;越來越受歡迎。你是否也曾看到一段有趣的視頻&#xff0c;想把它做成 GIF 發給朋友或用在PPT里&#xff1f;其實&#xff0c;將視頻片段轉換為 GIF 并不需要復雜的視頻剪輯技術&#xff0c;使用一款支持直接錄制為 GIF 的…

Vue.config.js中的Webpack配置、優化及多頁面應用開發

Vue.config.js中的Webpack配置、優化及多頁面應用開發 在Vue CLI 3項目中&#xff0c;vue.config.js文件是工程化配置的核心入口&#xff0c;它通過集成Webpack配置、優化策略和多頁面開發支持&#xff0c;為項目構建提供高度可定制化的解決方案。本文將從基礎配置、性能優化、…

行業學習【電商】:直播電商的去頭部化、矩陣號?

聲明&#xff1a;以下部分內容含AI生成這兩個詞是當前直播電商和MCN領域的核心戰略&#xff0c;理解了它們就理解了行業正在發生的深刻變化。一、如何理解“去頭部化”&#xff1f;“去頭部化” 指的是平臺或MCN機構有意識地減少對超頭部主播&#xff08;如曾經的李佳琦、薇婭&…

【MFC視圖和窗口基礎:文檔/視圖的“雙胞胎”魔法 + 單文檔程序】

大家好&#xff0c;我是你的MFC編程小伙伴&#xff01;學MFC就像探險古墓&#xff1a;到處是神秘的“房間”&#xff08;窗口&#xff09;和“寶藏”&#xff08;數據&#xff09;。今天咱們聊聊核心概念 – 視圖、窗口和文檔。這些是MFC的“骨架”&#xff0c;懂了它們&#x…

深度學習(六):代價函數的意義

在深度學習的浩瀚世界中&#xff0c;代價函數&#xff08;Cost Function&#xff09;&#xff0c;又稱損失函數&#xff08;Loss Function&#xff09;或目標函數&#xff08;Objective Function&#xff09;&#xff0c;扮演著至關重要的角色&#xff0c;它就像一個導航員&…

Kable使用指南:Android BLE開發的現代化解決方案

概述 Kable&#xff08;com.juul.kable:core&#xff09;是一個專為Android藍牙低功耗&#xff08;BLE&#xff09;開發設計的Kotlin協程友好庫。它通過提供簡潔的API和響應式編程模式&#xff0c;極大地簡化了BLE設備交互的復雜性。本文將詳細介紹Kable的使用方法&#xff0c;…