1. 项目概述与核心价值
最近在做一个Unity项目,需要从游戏内直接拉起安卓系统的应用安装界面,让用户安装我们打包好的APK文件。听起来是个挺常见的需求,对吧?但真上手做,尤其是在Unity 2020+版本和AndroidX环境下,你会发现网上很多老教程要么代码过时,要么步骤缺失,踩坑踩到怀疑人生。我花了差不多一周时间,把Unity调用安卓安装APK这个流程彻底跑通并封装成了一个稳定、免费的解决方案。这篇文章就是我的完整踩坑实录和一站式指南,无论你是独立开发者还是团队里的TA,都能直接拿去用。
这个功能的核心价值在于实现应用的“自更新”或“分包下载”。比如,你的游戏主体包很小,但包含大量高清资源;或者你想在游戏内推送一个独立的工具App。直接在Unity里调用安装,用户体验比引导用户去浏览器下载再手动安装要流畅得多。整个过程涉及Unity与Android原生代码的交互(AndroidJavaClass/AndroidJavaObject)、AndroidX兼容性处理、以及高版本安卓系统的文件权限和包可见性等一堆“坑”。我会从原理到代码,从Unity设置到安卓原生侧适配,一步步拆解,确保你能复现。
2. 环境准备与Unity项目配置
2.1 基础环境清单
工欲善其事,必先利其器。首先确保你的开发环境是正确且兼容的,这能避免至少50%的玄学问题。
- Unity版本 : 推荐使用 2020.3 LTS 或 2021.3 LTS 及以上版本。这些版本对AndroidX和Gradle构建系统的支持更成熟稳定。我实测在2020.3.48f1上完全通过。
-
Android开发环境
:
-
Unity Hub中安装
: 务必通过Unity Hub安装对应的Android Build Support模块,包括
Android SDK & NDK Tools和OpenJDK。Unity内置的JDK版本兼容性更好。 -
Android Studio (可选但推荐)
: 用于管理Android SDK和获取最新的SDK Build-Tools。安装后,打开SDK Manager,确保安装了
Android SDK Platform(对应你的targetSdkVersion,如API 33)和最新的SDK Build-Tools。
-
Unity Hub中安装
: 务必通过Unity Hub安装对应的Android Build Support模块,包括
-
关键Player Settings设置
: 这是最容易出错的地方,务必逐项核对。
-
Other Settings
:
-
Package Name
: 你的应用包名,如
com.YourCompany.YourGame。 -
Minimum API Level
: 根据你的用户群体设置,例如
Android 8.0 ‘Oreo’ (API Level 26)。 - Target API Level : 必须设置为 30 (Android 11) 或更高 。这是使用AndroidX和应对Scoped Storage等新存储策略的前提。Unity 2020+默认会推荐较高的API Level。
-
Package Name
: 你的应用包名,如
-
Publishing Settings
:
-
Build
: 勾选
Custom Main Gradle Template和Custom Launcher Gradle Template。这是让我们能够深度定制构建流程,注入AndroidX依赖和关键配置的 核心步骤 。勾选后,你可以在项目的Assets/Plugins/Android目录下找到生成的两个.gradle文件。
-
Build
: 勾选
-
Other Settings
:
2.2 配置Gradle以支持AndroidX
Unity旧版的Android构建系统默认使用原生的Android Support库,而Google早已推进到AndroidX。我们的安装功能需要用到AndroidX的
FileProvider
,因此必须强制项目使用AndroidX。
-
找到并编辑
mainTemplate.gradle文件(路径:Assets/Plugins/Android/mainTemplate.gradle)。 -
在
allprojects代码块内的repositories部分之后,添加以下配置:allprojects { repositories { google() jcenter() // ... 其他仓库 } // --- 添加以下配置块 --- gradle.projectsEvaluated { tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } } // --- 结束添加 --- } -
在
dependencies部分,确保添加了AndroidX Core库的依赖(Unity新版本可能已自带,但检查一下):dependencies { implementation 'androidx.core:core:1.6.0+' // 使用较新的稳定版本 implementation 'androidx.appcompat:appcompat:1.3.1+' // ... Unity自动添加的其他依赖 } -
编辑
launcherTemplate.gradle文件(路径:Assets/Plugins/Android/launcherTemplate.gradle)。 在android代码块内,添加以下配置来启用AndroidX并停用Jetifier(一个将Support库迁移到AndroidX的工具,有时会引起冲突):android { compileSdkVersion 30 // 确保与Player Settings中的Target API一致 // ... 其他配置 defaultConfig { // ... 其他配置 // --- 添加以下两行 --- multiDexEnabled true vectorDrawables.useSupportLibrary = true } compileOptions { sourceCompatibility Java 1.8 targetCompatibility Java 1.8 } // --- 添加以下配置块 --- packagingOptions { exclude 'META-INF/proguard/androidx-annotations.pro' } } dependencies { // 如果launcherTemplate.gradle里有dependencies,确保没有冲突的旧Support库 }
注意 :修改Gradle模板后, 必须 关闭当前Unity工程,然后删除项目根目录下的
Library和Temp文件夹,再重新打开Unity。这是一个强制刷新Gradle配置的“土方”,但非常有效,能解决很多构建时出现的诡异错误。
3. 核心原理与Android原生侧适配
3.1 安装APK的Android原生流程解析
在安卓原生开发中,安装一个APK文件,本质上是向系统发送一个特定的
Intent
(意图)。这个Intent的类型是
ACTION_VIEW
,并且需要携带APK文件的Uri(统一资源标识符)和MIME类型
application/vnd.android.package-archive
。
然而,从Android 7.0 (Nougat, API 24) 开始,Google引入了“文件共享”的严格限制。你不能直接使用
file://
协议的Uri来传递文件给系统安装器,否则会抛出
FileUriExposedException
异常。解决方案是使用
FileProvider
,它是
ContentProvider
的一个特殊子类,可以安全地生成一个
content://
协议的Uri,用于在应用间共享文件。
从Android 11 (API 30) 开始,又引入了“包可见性”(Package Visibility)限制。默认情况下,你的应用无法查询到系统安装器(
packageInstaller
)的完整包名,导致无法直接启动它。你需要在中声明查询权限。
我们的工作,就是在Unity C#脚本中,通过Android Java Native Interface (JNI) 桥接,调用这一系列的原生API,并处理好所有兼容性问题。
3.2 创建Android插件与FileProvider配置
Unity调用安卓原生功能,通常需要编写一个Android插件。这里我们采用最直接的方式:创建一个Android Library模块(AAR/JAR)或直接使用C#调用JNI。为了简化,我们直接使用C#调用,但需要配置必要的Android清单文件。
-
创建
AndroidManifest.xml: 在Unity项目的Assets/Plugins/Android目录下,创建一个名为AndroidManifest.xml的文件。如果该文件夹不存在,请手动创建。这个文件会与Unity自动生成的主清单合并。<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yourcompany.yourgame"> <application> <!-- 1. 声明我们的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> </application> <!-- 2. 声明安装APK所需的权限 --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- 3. 声明查询所有包的权限(用于Android 11+寻找安装器) --> <queries> <intent> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="application/vnd.android.package-archive" /> </intent> <!-- 也可以直接查询已知的包管理器包名 --> <package android:name="com.android.packageinstaller" /> <package android:name="com.google.android.packageinstaller" /> <package android:name="com.samsung.android.packageinstaller" /> <!-- 添加更多已知厂商包名... --> </queries> </manifest>-
authorities: 这是FileProvider的唯一标识符,通常使用应用包名加后缀.fileprovider。${applicationId}会在构建时自动替换成你的应用包名。 -
grantUriPermissions: 设置为true,允许我们临时授权给系统安装器访问文件的权限。 -
REQUEST_INSTALL_PACKAGES: 这个权限在Android 8.0 (API 26) 及以上是必需的,用于允许应用请求安装APK。它属于“普通权限”,在清单中声明即可,无需运行时申请。
-
-
创建
file_paths.xml资源文件 : 在Assets/Plugins/Android目录下,再创建一个res/xml文件夹(即完整路径为Assets/Plugins/Android/res/xml)。在该xml文件夹内,创建file_paths.xml文件。<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 对应Unity中Application.persistentDataPath,通常是 /data/data/包名/files --> <files-path name="internal_files" path="." /> <!-- 对应外部存储的私有目录,通常是 /storage/emulated/0/Android/data/包名/files --> <external-files-path name="external_files" path="." /> <!-- 对应外部存储的根目录,谨慎使用,需要权限 --> <external-path name="external_root" path="." /> <!-- 对应缓存目录 --> <cache-path name="cache" path="." /> </paths>这个文件定义了FileProvider可以共享的文件路径。
name是一个别名,用于在代码中构造Uri时引用;path表示该根目录下的子路径,.代表根目录本身。为了安全性和兼容性, 强烈建议将APK文件放在应用的私有目录下 ,例如Application.persistentDataPath,它对应的是<files-path>。这样不需要申请任何额外的存储权限。
4. Unity C#核心实现代码详解
4.1 封装Android安装工具类
接下来是重头戏:编写C#脚本,通过Unity的
AndroidJavaClass
和
AndroidJavaObject
来调用安卓API。
创建一个名为
AndroidPackageInstaller.cs
的C#脚本。
using UnityEngine;
using System;
using System.IO;
using System.Collections;
public class AndroidPackageInstaller : MonoBehaviour
{
// FileProvider的authorities,必须与AndroidManifest.xml中的定义一致
private const string FileProviderAuthority = "{0}.fileprovider"; // {0} 将在运行时被替换为包名
/// <summary>
/// 安装APK文件
/// </summary>
/// <param name="apkFilePath">APK文件的完整路径(例如 Application.persistentDataPath + "/update.apk")</param>
public static void InstallApk(string apkFilePath)
{
if (string.IsNullOrEmpty(apkFilePath) || !File.Exists(apkFilePath))
{
Debug.LogError("[AndroidPackageInstaller] APK file not found: " + apkFilePath);
return;
}
// 获取当前应用的包名,用于构建正确的FileProvider Authority
string packageName = Application.identifier;
string authority = string.Format(FileProviderAuthority, packageName);
// 使用AndroidJavaObject封装文件
AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkFilePath);
// 判断Android API版本,以选择不同的Uri获取方式
if (GetAndroidSDKVersion() >= 24) // Android 7.0 (Nougat) 及以上
{
// 使用FileProvider获取content:// Uri
AndroidJavaObject context = GetUnityActivity();
AndroidJavaClass fileProvider = new AndroidJavaClass("androidx.core.content.FileProvider");
AndroidJavaObject contentUri = fileProvider.CallStatic<AndroidJavaObject>("getUriForFile", context, authority, fileObj);
InstallWithUri(contentUri, apkFilePath);
}
else
{
// Android 7.0 以下,使用老式的file:// Uri (不推荐,但作为兼容)
Debug.LogWarning("[AndroidPackageInstaller] Using file:// Uri for old Android version. This may not work on all devices.");
AndroidJavaObject fileUri = new AndroidJavaClass("android.net.Uri").CallStatic<AndroidJavaObject>("fromFile", fileObj);
InstallWithUri(fileUri, apkFilePath);
}
}
/// <summary>
/// 使用获取到的Uri执行安装Intent
/// </summary>
private static void InstallWithUri(AndroidJavaObject uri, string apkFilePath)
{
try
{
AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", "android.intent.action.VIEW");
// 设置Data和Type
intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
// 添加Flags
intent.Call<AndroidJavaObject>("addFlags", 0x10000000); // Intent.FLAG_ACTIVITY_NEW_TASK
intent.Call<AndroidJavaObject>("addFlags", 0x00000001); // Intent.FLAG_GRANT_READ_URI_PERMISSION
// 对于Android 11+,可能需要更明确地设置组件,但通常使用隐式Intent即可。
// 我们的<queries>声明已经帮助系统解析这个Intent。
AndroidJavaObject activity = GetUnityActivity();
activity.Call("startActivity", intent);
Debug.Log("[AndroidPackageInstaller] Installation intent sent for: " + apkFilePath);
}
catch (Exception e)
{
Debug.LogError("[AndroidPackageInstaller] Failed to start install activity: " + e.Message + "\n" + e.StackTrace);
}
}
/// <summary>
/// 获取当前UnityPlayer的Activity对象
/// </summary>
private static AndroidJavaObject GetUnityActivity()
{
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
return unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
}
}
/// <summary>
/// 获取Android系统版本号
/// </summary>
private static int GetAndroidSDKVersion()
{
using (AndroidJavaClass version = new AndroidJavaClass("android.os.Build$VERSION"))
{
return version.GetStatic<int>("SDK_INT");
}
}
// --- 示例方法:模拟下载并安装 ---
public void StartDemoInstall()
{
string apkName = "your_update.apk";
string persistentPath = Application.persistentDataPath;
string targetApkPath = Path.Combine(persistentPath, apkName);
// 这里应该是你的APK下载逻辑。为了演示,我们假设APK已经存在。
// 例如,从StreamingAssets复制,或从网络下载。
#if UNITY_EDITOR
Debug.Log("[Demo] In Editor, simulating APK at: " + targetApkPath);
// 在编辑器下无法真实安装,可以测试路径逻辑
InstallApk(targetApkPath);
#else
if (File.Exists(targetApkPath))
{
InstallApk(targetApkPath);
}
else
{
Debug.LogError("[Demo] APK file not found for demo. Path: " + targetApkPath);
// 触发你的下载流程...
// StartCoroutine(DownloadApk(url, targetApkPath));
}
#endif
}
IEnumerator DownloadApk(string url, string savePath)
{
// 使用UnityWebRequest进行下载的示例
using (var uwr = new UnityEngine.Networking.UnityWebRequest(url, UnityEngine.Networking.UnityWebRequest.kHttpVerbGET))
{
uwr.downloadHandler = new UnityEngine.Networking.DownloadHandlerFile(savePath);
yield return uwr.SendWebRequest();
if (uwr.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
{
Debug.Log("Download complete: " + savePath);
InstallApk(savePath);
}
else
{
Debug.LogError("Download failed: " + uwr.error);
}
}
}
}
4.2 代码关键点解析与避坑指南
-
FileProviderAuthority格式 :{0}.fileprovider中的{0}会在运行时被Application.identifier(即包名)替换。这确保了与AndroidManifest.xml中android:authorities="${applicationId}.fileprovider"的声明完全匹配。 不匹配会导致FileNotFoundException。 -
API版本判断
:我们通过
GetAndroidSDKVersion()获取系统版本,并对Android 7.0及以上版本使用FileProvider。这是处理FileUriExposedException的标准做法。 -
Intent Flags
:
-
FLAG_ACTIVITY_NEW_TASK(0x10000000): 通常从非Activity的上下文(如Application Context)启动Activity时需要。虽然我们从Unity的Activity启动,但加上它更保险。 -
FLAG_GRANT_READ_URI_PERMISSION(0x00000001): 至关重要 。它临时授予系统安装器读取我们通过FileProvider共享的Uri的权限。没有这个Flag,安装器会因权限不足而无法访问APK文件。
-
-
<queries>声明的作用 :在Android 11+上,即使你正确设置了Intent,如果系统无法解析出应该由哪个应用(包管理器)来处理application/vnd.android.package-archive,startActivity可能会静默失败。<queries>清单节点提前声明了我们要查询的Intent和可能的包名,解决了这个“包可见性”问题。 -
APK文件存放位置
:代码示例中使用了
Application.persistentDataPath。这个路径对应安卓的“内部存储私有目录”,应用卸载时数据会被清除,且无需任何运行时权限即可读写。 绝对不要 将APK放在Application.streamingAssetsPath(只读)或直接放在SD卡根目录(需要权限且不安全)。
5. 完整工作流与测试验证
5.1 从下载到安装的完整流程
让我们串联起整个流程,假设一个游戏内资源更新的场景:
-
资源准备
:服务器上存放着新版本的APK文件
game_update_v1.1.apk。 - Unity端触发检查 :游戏启动时,或玩家进入某个界面,调用检查更新的逻辑。
-
下载APK
:使用
UnityWebRequest或你喜欢的网络库,将APK下载到Application.persistentDataPath目录下。确保下载路径与安装代码中的路径一致。string savePath = Path.Combine(Application.persistentDataPath, “game_update_v1.1.apk”); // ... 执行下载 -
调用安装
:下载完成后,直接调用
AndroidPackageInstaller.InstallApk(savePath)。 - 系统接管 :Unity应用会跳转到系统的包安装器界面,用户可以看到熟悉的安装按钮。安装完成后,用户可以选择“打开”新应用或“完成”。
5.2 在Unity编辑器中测试与真机调试
-
编辑器测试
:在Unity Editor中运行,
InstallApk函数会被调用,但由于没有安卓环境,AndroidJavaClass调用会失败。你可以在代码中通过#if !UNITY_EDITOR来包裹原生调用部分,或者在Editor下只打印日志。重点是测试你的下载逻辑、路径拼接和条件判断。 -
真机调试(必备)
:
- 按照第2章配置好Unity项目。
-
将
AndroidPackageInstaller.cs脚本和Assets/Plugins/Android下的所有配置文件放入项目。 - 使用USB连接一台安卓手机(建议系统版本在Android 8.0以上),并开启手机的“开发者模式”和“USB调试”。
- 在Unity的Build Settings中,切换到Android平台,直接点击“Build And Run”。首次构建可能会较慢,因为要下载Gradle依赖。
-
应用安装到手机后,你可以通过一个简单的UI按钮来触发
StartDemoInstall方法。为了测试,你可以先将一个测试用的APK文件(可以是同一个项目打出的另一个APK包)手动放入手机的/sdcard/目录,然后在代码中暂时将路径指向它(例如”/sdcard/test.apk”),但 仅用于测试 ,正式版本务必使用私有目录。 -
观察Logcat日志(可以通过Android Studio的Logcat工具或Unity的
adb logcat命令),这是排查问题的生命线。搜索AndroidPackageInstaller标签,查看是否有错误信息。
5.3 常见问题与排查技巧实录
即使按照步骤操作,也可能会遇到问题。下面是我在实测中遇到的一些典型情况及其解决方法。
问题1:构建失败,Gradle报错 “Failed to apply plugin ‘com.android.internal.application’.”
- 现象 :Unity导出Android项目时,控制台出现一堆Gradle相关的红色错误。
-
排查
:
-
检查
mainTemplate.gradle和launcherTemplate.gradle的语法,特别是括号是否匹配。 -
彻底清除Gradle缓存
:关闭Unity,删除项目根目录下的
Library、Temp文件夹,以及用户目录下的.gradle缓存文件夹(例如C:\Users\<用户名>\.gradle\caches)。重新打开Unity构建。 -
降低
compileSdkVersion和targetSdkVersion到30,确保与Unity版本兼容。
-
检查
问题2:安装时闪退,Logcat报错 “FileNotFoundException” 或 “Permission Denial”
-
现象
:点击安装按钮后,直接回到原应用,系统安装界面没有弹出。Logcat错误指向
FileProvider或权限问题。 -
排查
:
-
核对Authority
:确保C#代码中的
FileProviderAuthority格式与AndroidManifest.xml中的android:authorities完全一致。 最保险的做法是在C#代码中打印出拼接后的authority字符串,与日志中FileProvider抛异常时提示的authority进行比对 。 -
核对文件路径
:确保
file_paths.xml中定义的path与你在C#中构造File对象时使用的APK文件的 父目录 匹配。例如,APK路径是/data/data/com.xxx/files/update.apk,那么它匹配的是<files-path name="..." path="." />,因为<files-path>的根目录就是/data/data/包名/files。 -
检查APK文件是否存在
:在调用
InstallApk前,用File.Exists确认文件是否已成功下载到指定位置。
-
核对Authority
:确保C#代码中的
问题3:在Android 11+的设备上,系统安装界面不弹出
- 现象 :代码执行无报错,Logcat也没有明显错误,但就是没有跳转。
-
排查
:
-
确认
<queries>声明 :检查AndroidManifest.xml是否已经正确添加了<queries>节点。 -
检查Intent的Component
:可以尝试在代码中,在创建Intent后,显式设置包管理器包名(不推荐,因为厂商包名不同)。更好的方法是依赖
<queries>声明。 -
查看系统日志
:使用
adb logcat | grep -E “ActivityTaskManager|PackageInstaller”过滤日志,看系统是否收到了安装Intent以及如何处理。
-
确认
问题4:安装界面显示“解析包时出现问题”
- 现象 :系统安装器弹出,但点击安装后提示解析失败。
-
排查
:
- APK文件损坏 :下载的APK文件不完整。检查下载逻辑,确保文件完整写入。可以在下载完成后计算文件的MD5与服务器对比。
- APK与设备架构不兼容 :你下载的APK可能只包含了特定CPU架构(如arm64-v8a),但测试设备是x86。确保你分发的APK是通用的(在Player Settings中勾选多个Target Architectures)。
-
最低API等级不兼容
:你下载的APK的
minSdkVersion高于测试设备的系统版本。检查APK的版本信息。
问题5:在Android 14 (API 34) 上,即使有
<queries>
声明也无效
- 现象 :Android 14进一步加强了权限控制。
-
解决方案
:除了
<queries>,还需要在AndroidManifest.xml中为特定的Intent Action声明更精确的权限。添加以下权限声明(在<manifest>标签内):
注意 :<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />QUERY_ALL_PACKAGES是一个“高级”权限,在Google Play上架时可能需要声明其使用理由,并可能受到审核。因此, 优先使用<queries>中明确列出包名或Intent Filter的方式 ,仅在必要时才考虑使用此权限。
6. 进阶优化与扩展思路
6.1 增加安装前检查与用户引导
直接弹出安装界面可能会打断用户体验。更好的做法是:
-
检查是否开启“未知来源”安装
:虽然Android 8.0后此设置针对应用而非全局,但可以检查
Settings.canRequestPackageInstalls()来判断是否拥有安装权限。如果没有,可以友好地提示用户并引导至设置页面。// 注意:此API需要API Level 26 (Android 8.0) public static bool CanRequestPackageInstalls() { if (GetAndroidSDKVersion() >= 26) { AndroidJavaObject context = GetUnityActivity(); AndroidJavaObject pm = context.Call<AndroidJavaObject>("getPackageManager"); return pm.Call<bool>("canRequestPackageInstalls"); } // Android 8.0 以下默认允许 return true; } -
引导用户开启权限
:如果返回
false,可以弹窗说明,并调用以下代码跳转到设置界面:public static void OpenInstallPermissionSetting() { try { AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", "android.settings.MANAGE_UNKNOWN_APP_SOURCES"); AndroidJavaObject uri = new AndroidJavaClass("android.net.Uri").CallStatic<AndroidJavaObject>("fromParts", "package", GetPackageName(), null); intent.Call<AndroidJavaObject>("setData", uri); intent.Call<AndroidJavaObject>("addFlags", 0x10000000); // FLAG_ACTIVITY_NEW_TASK GetUnityActivity().Call("startActivity", intent); } catch (Exception e) { Debug.LogError("Failed to open settings: " + e.Message); } } private static string GetPackageName() { AndroidJavaObject context = GetUnityActivity(); return context.Call<string>("getPackageName"); }
6.2 处理安装结果回调
有时我们需要知道用户是安装成功了,还是取消了安装。这可以通过
StartActivityForResult
来实现,但Unity的Activity并没有直接提供回调接口。一个可行的方案是:
-
编写一个简单的Android原生插件(Java/Kotlin)
:创建一个新的Activity,它继承自Unity的PlayerActivity,并重写
onActivityResult方法。 -
使用UnitySendMessage
:在原生代码的
onActivityResult中,根据结果码(RESULT_OK或RESULT_CANCELED)调用UnityPlayer.UnitySendMessage将结果发送回Unity的某个GameObject。 -
修改
launcherTemplate.gradle:让应用的主Activity指向你自定义的Activity。
这个过程相对复杂,涉及到原生开发。对于大多数“点击安装”后就不需要再干预的场景,不处理回调也是可以接受的。如果需要,这可以作为后续功能扩展的一个方向。
6.3 适配不同的文件来源
我们的示例主要针对从网络下载到私有目录的APK。如果你的APK来源于其他位置,需要调整
file_paths.xml
:
-
从外部存储(SD卡)根目录读取
:需要
READ_EXTERNAL_STORAGE权限,且Android 10以上需要适配Scoped Storage,非常不推荐。 -
从其他应用共享的文件
:例如用户通过文件管理器选择了一个APK。这时你收到的是一个
content://Uri,可以直接用于安装Intent的setDataAndType,无需通过自己的FileProvider再次处理。你需要判断Uri的Scheme,如果是content://,则直接使用。
最后,这套方案我已经在多个商业项目中稳定使用。关键在于细节:Gradle配置、清单文件、FileProvider的authority和路径映射,这三者必须严丝合缝。真机测试时多关注Logcat,它能告诉你绝大部分失败的原因。把上面的步骤走通,Unity调用安卓安装APK这个功能,就再也不会是你的拦路虎了。

1138

被折叠的 条评论
为什么被折叠?



