如何在Xamarin.Forms中自动安装并运行SD卡中的APK文件?
我来帮你解决这个APK自动安装的问题!首先得明确:Android从8.0(API 26)开始,安全机制做了很大调整,普通第三方APP是无法完全静默自动安装APK的——这是系统的安全限制,目的是防止恶意软件私自安装应用。不过我们可以优化安装流程,先解决你当前代码里的问题,再告诉你如何实现接近"自动"的安装体验,以及系统级静默安装的前提。
一、先修复你当前代码的问题(解决无法唤起安装/FileUriExposedException)
你的代码现在有几个关键问题:
- 使用
Uri.FromFile在Android 7.0+会触发FileUriExposedException,必须用FileProvider来获取安全的文件Uri - 权限配置不全,缺少Android 8.0+必需的
REQUEST_INSTALL_PACKAGES权限 - 没有检查安装未知来源应用的权限,导致直接唤起安装可能失败
步骤1:更新AndroidManifest权限
把你的权限配置改成下面这样,适配不同Android版本:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 10及以下需要写入权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- Android 8.0+ 安装未知来源应用必需 --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- Android 11+ 访问公共存储需要这个权限 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
步骤2:配置FileProvider
在AndroidManifest.xml的<application>标签内添加FileProvider配置:
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
然后在你的Android项目的Resources/xml目录下创建file_paths.xml文件(如果没有xml目录就新建一个):
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 允许访问外部存储的所有文件 --> <external-path name="external_files" path="." /> </paths>
步骤3:修正ApkInstall方法的代码
替换你的SDCardInstall.cs代码,加入权限检查和FileProvider的使用:
using Android.App; using Android.Content; using Android.OS; using AndroidX.Core.Content; using Java.IO; using System.IO; using Xamarin.Forms; [assembly: Dependency(typeof(YourAndroidNamespace.SDCardInstall))] namespace YourAndroidNamespace { public class SDCardInstall : SDCard { private const int RequestInstallPermissionCode = 1001; public void ApkInstall(string filename) { // 修正重复定义变量的问题,直接使用传入的filename var apkPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, "Install apk", filename); System.Diagnostics.Debug.WriteLine($"APK完整路径:{apkPath}"); var apkFile = new File(apkPath); if (!apkFile.Exists()) { System.Diagnostics.Debug.WriteLine("错误:APK文件不存在!"); return; } // 检查Android 8.0+的安装权限 if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { if (!Android.App.Application.Context.PackageManager.CanRequestPackageInstalls()) { // 引导用户跳转到开启未知来源应用权限的页面 var intent = new Intent(Settings.ActionManageUnknownAppSources, Android.Net.Uri.Parse($"package:{Android.App.Application.Context.PackageName}")); ((Activity)Forms.Context).StartActivityForResult(intent, RequestInstallPermissionCode); return; } } // 使用FileProvider获取安全的Uri var apkUri = FileProvider.GetUriForFile(Forms.Context, $"{Forms.Context.PackageName}.fileprovider", apkFile); // 构建安装意图 var installIntent = new Intent(Intent.ActionView); installIntent.SetDataAndType(apkUri, "application/vnd.android.package-archive"); installIntent.AddFlags(ActivityFlags.GrantReadUriPermission); // 给安装器临时读取权限 installIntent.AddFlags(ActivityFlags.NewTask); Forms.Context.StartActivity(installIntent); } } }
步骤4:处理权限申请结果
在你的Android项目的MainActivity.cs中重写OnActivityResult方法,处理用户授权后的逻辑:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (requestCode == 1001) { if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { if (PackageManager.CanRequestPackageInstalls()) { // 用户已经授权,再次调用安装方法(替换成你的APK文件名) DependencyService.Get<SDCard>().ApkInstall("XYZ.apk"); } } } }
二、关于"自动安装"的说明
如开头所说,普通第三方APP无法实现完全静默的自动安装,这是Android的安全限制:
- 只有拥有系统签名的系统级APP,或者ROOT后的设备,才能使用
PackageInstallerAPI实现静默安装,而且需要申请android.permission.INSTALL_PACKAGES系统权限(这个权限普通APP无法通过常规渠道获取) - 普通APP能做到的"自动",就是先引导用户开启未知来源权限,然后自动唤起系统安装界面,用户只需要点击"安装"即可
如果你是开发系统级应用(比如定制ROM的预装APP),可以尝试用PackageInstaller实现静默安装,代码示例如下:
using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; using System.IO; using System.Threading.Tasks; public async Task SilentInstallApk(string apkPath) { var packageInstaller = Application.Context.PackageManager.PackageInstaller; var sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall); sessionParams.SetAppPackageName(Application.Context.PackageName); int sessionId = packageInstaller.CreateSession(sessionParams); using var session = packageInstaller.OpenSession(sessionId); // 将APK文件写入安装会话 using var inputStream = new FileStream(apkPath, FileMode.Open); using var outputStream = session.OpenWrite("package", 0, -1); await inputStream.CopyToAsync(outputStream); session.Fsync(outputStream); // 创建安装完成的回调意图 var intent = new Intent(Application.Context, typeof(MainActivity)); intent.SetAction("com.yourpackage.INSTALL_COMPLETE"); var pendingIntent = PendingIntent.GetActivity(Application.Context, 0, intent, PendingIntentFlags.UpdateCurrent); // 提交安装请求 session.Commit(pendingIntent.IntentSender); }
注意:这个方法需要系统签名和INSTALL_PACKAGES权限,普通APP调用会报错。
最后总结
- 先按照第一步的方法修复你的代码,解决当前无法正常唤起安装的问题
- 普通APP无法完全自动安装,只能优化流程减少用户操作
- 静默安装只有系统级APP或ROOT设备才能实现
内容的提问来源于stack exchange,提问作者Judson Abraham
相关产品推荐
相关产品推荐

