Xamarin.Forms (Android) 从本地共享仓库下载APK实现应用更新的问题
问题原因
你当前用的WebClient仅支持HTTP/HTTPS/FTP这类标准Web协议,而\\192.168.0.15\images\example.jpg这类路径属于SMB/CIFS局域网共享协议,WebClient无法直接解析该类协议的地址,因此会调用失败。
推荐解决方案
方案一:服务端搭建内网HTTP文件服务(最优,改动最小)
该方案无需修改你现有的下载逻辑,仅需要在存APK的本地服务器上部署轻量HTTP服务即可:
- 你可以在Windows服务器上用IIS新建虚拟目录指向APK存储的共享文件夹,也可以用更轻量的Nginx、甚至Python一行命令
python -m http.server 8080(在共享文件夹目录下执行)快速搭建文件服务,只要保证移动端和服务器在同一局域网下可以访问对应端口即可。 - 配置完成后把原来的SMB下载地址替换为内网HTTP地址即可,比如
http://192.168.0.15:8080/app-release.apk,你原有的WebClient下载逻辑完全可以复用,稳定性和兼容性远高于直接访问SMB共享。
方案二:客户端集成SMB协议适配库
如果必须保留SMB共享的使用方式,可以引入SMB客户端库实现共享文件访问,这里推荐使用纯C#实现的SMBLibrary库:
- 首先在Xamarin.Android项目中安装
SMBLibraryNuGet包 - 替换原有下载逻辑,示例代码如下:
using SMBLibrary; using SMBLibrary.Client; using System.IO; using System.Linq; public void DownloadFileFromSMB(string smbFullPath, string localSaveDir, string smbUsername = "", string smbPassword = "") { // 创建本地存储目录 Directory.CreateDirectory(localSaveDir); var fileName = Path.GetFileName(smbFullPath); var localSavePath = Path.Combine(localSaveDir, fileName); // 解析SMB路径 var pathSegments = smbFullPath.TrimStart('\\').Split('\\'); if (pathSegments.Length < 3) { OnFileDownloaded?.Invoke(this, new DownloadEventArgs(false)); return; } string serverIp = pathSegments[0]; string shareName = pathSegments[1]; string remoteFilePath = string.Join("\\", pathSegments.Skip(2)); SMB2Client smbClient = new SMB2Client(); try { // 连接SMB服务器 bool connectSuccess = smbClient.Connect(System.Net.IPAddress.Parse(serverIp), SMBTransportType.DirectTCPTransport); if (!connectSuccess) throw new Exception("SMB服务器连接失败"); // 登录认证,匿名访问直接传空账号密码 NTStatus loginStatus = smbClient.Login(string.Empty, smbUsername, smbPassword); if (loginStatus != NTStatus.STATUS_SUCCESS) throw new Exception("SMB认证失败"); // 接入共享目录 var share = smbClient.TreeConnect(shareName, out NTStatus treeStatus); if (treeStatus != NTStatus.STATUS_SUCCESS) throw new Exception("共享目录访问失败"); // 打开远程文件 object fileHandle; FileStatus fileStatus; NTStatus openFileStatus = share.CreateFile(out fileHandle, out fileStatus, remoteFilePath, AccessMask.GENERIC_READ, FileAttributes.Normal, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null); if (openFileStatus != NTStatus.STATUS_SUCCESS) throw new Exception("远程文件打开失败"); // 读取文件写入本地 long fileSize = share.GetFileInformation(fileHandle).EndOfFile; using var localFs = new FileStream(localSavePath, FileMode.Create, FileAccess.Write); long offset = 0; int bufferSize = 4096; while (offset < fileSize) { NTStatus readStatus = share.ReadFile(out byte[] data, fileHandle, offset, bufferSize); if (readStatus != NTStatus.STATUS_SUCCESS) throw new Exception("文件读取失败"); localFs.Write(data, 0, data.Length); offset += data.Length; } // 释放资源 share.CloseFile(fileHandle); share.Disconnect(); smbClient.Logoff(); smbClient.Disconnect(); OnFileDownloaded?.Invoke(this, new DownloadEventArgs(true)); } catch { if (smbClient.IsConnected) { smbClient.Logoff(); smbClient.Disconnect(); } OnFileDownloaded?.Invoke(this, new DownloadEventArgs(false)); } }
- 注意要在AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 安卓8.0以上安装APK需要的权限 --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
内容的提问来源于stack exchange,提问作者David Zomada
相关产品推荐
相关产品推荐

