【问题标题】:How to install Android apk from code in unity如何统一从代码安装Android apk
【发布时间】:2017-07-10 12:48:19
【问题描述】:

我找到了用于 Java 的 sn-p。如何在 C# Unity 中编写这样的代码?

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(new File("link to downloaded file")),"application/vnd.android.package-archive");
startActivity(intent);

【问题讨论】:

  • 我在你的描述中看不到任何详细的问题。乍一看,我会说这段代码应该在 C# 中工作。
  • 我的意思是,您需要在描述中添加尽可能多的细节。作为 c# 开发人员,我在这段代码中看不到任何问题 - 它应该在 c# 中工作。您应该添加注释,说明 c# Unity 框架没有这些类,并且您认为应该使用 AndroidJavaObjectAndroidJavaClass 来实现相同的行为。 :)

标签: c# android unity3d android-intent


【解决方案1】:

您可以构建一个 jar/aar 插件并从 C# 调用它。这更容易做到。

另一种解决方案是使用AndroidJavaObjectAndroidJavaClass 直接执行此操作,无需插件。使用AndroidJavaObjectAndroidJavaClass 进行操作需要大量测试才能正确完成。下面是我用来做的。它会下载一个 APK 然后安装它。

首先创建一个名为 "TextDebug" 的 UI 文本,以便您在下载/安装期间看到发生了什么。如果不这样做,则必须注释掉或删除所有 GameObject.Find("TextDebug").GetComponent<Text>().text... 代码行。

void Start()
{
    StartCoroutine(downLoadFromServer());
}

IEnumerator downLoadFromServer()
{
    string url = "http://apkdl.androidapp.baidu.com/public/uploads/store_2/f/f/a/ffaca37aaaa481003d74725273c98122.apk?xcode=854e44a4b7e568a02e713d7b0af430a9136d9c32afca4339&filename=unity-remote-4.apk";


    string savePath = Path.Combine(Application.persistentDataPath, "data");
    savePath = Path.Combine(savePath, "AntiOvr.apk");

    Dictionary<string, string> header = new Dictionary<string, string>();
    string userAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36";
    header.Add("User-Agent", userAgent);
    WWW www = new WWW(url, null, header);


    while (!www.isDone)
    {
        //Must yield below/wait for a frame
        GameObject.Find("TextDebug").GetComponent<Text>().text = "Stat: " + www.progress;
        yield return null;
    }

    byte[] yourBytes = www.bytes;

    GameObject.Find("TextDebug").GetComponent<Text>().text = "Done downloading. Size: " + yourBytes.Length;


    //Create Directory if it does not exist
    if (!Directory.Exists(Path.GetDirectoryName(savePath)))
    {
        Directory.CreateDirectory(Path.GetDirectoryName(savePath));
        GameObject.Find("TextDebug").GetComponent<Text>().text = "Created Dir";
    }

    try
    {
        //Now Save it
        System.IO.File.WriteAllBytes(savePath, yourBytes);
        Debug.Log("Saved Data to: " + savePath.Replace("/", "\\"));
        GameObject.Find("TextDebug").GetComponent<Text>().text = "Saved Data";
    }
    catch (Exception e)
    {
        Debug.LogWarning("Failed To Save Data to: " + savePath.Replace("/", "\\"));
        Debug.LogWarning("Error: " + e.Message);
        GameObject.Find("TextDebug").GetComponent<Text>().text = "Error Saving Data";
    }

    //Install APK
    installApp(savePath);
}

public bool installApp(string apkPath)
{
    try
    {
        AndroidJavaClass intentObj = new AndroidJavaClass("android.content.Intent");
        string ACTION_VIEW = intentObj.GetStatic<string>("ACTION_VIEW");
        int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic<int>("FLAG_ACTIVITY_NEW_TASK");
        AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", ACTION_VIEW);

        AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkPath);
        AndroidJavaClass uriObj = new AndroidJavaClass("android.net.Uri");
        AndroidJavaObject uri = uriObj.CallStatic<AndroidJavaObject>("fromFile", fileObj);

        intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
        intent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
        intent.Call<AndroidJavaObject>("setClassName", "com.android.packageinstaller", "com.android.packageinstaller.PackageInstallerActivity");

        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        currentActivity.Call("startActivity", intent);

        GameObject.Find("TextDebug").GetComponent<Text>().text = "Success";
        return true;
    }
    catch (System.Exception e)
    {
        GameObject.Find("TextDebug").GetComponent<Text>().text = "Error: " + e.Message;
        return false;
    }
}

对于 Android API 24 及更高版本,这需要不同的代码,因为 API 已更改。下面的 C# 代码基于 this Java 答案。

//For API 24 and above
private bool installApp(string apkPath)
{
    bool success = true;
    GameObject.Find("TextDebug").GetComponent<Text>().text = "Installing App";

    try
    {
        //Get Activity then Context
        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        AndroidJavaObject unityContext = currentActivity.Call<AndroidJavaObject>("getApplicationContext");

        //Get the package Name
        string packageName = unityContext.Call<string>("getPackageName");
        string authority = packageName + ".fileprovider";

        AndroidJavaClass intentObj = new AndroidJavaClass("android.content.Intent");
        string ACTION_VIEW = intentObj.GetStatic<string>("ACTION_VIEW");
        AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", ACTION_VIEW);


        int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic<int>("FLAG_ACTIVITY_NEW_TASK");
        int FLAG_GRANT_READ_URI_PERMISSION = intentObj.GetStatic<int>("FLAG_GRANT_READ_URI_PERMISSION");

        //File fileObj = new File(String pathname);
        AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkPath);
        //FileProvider object that will be used to call it static function
        AndroidJavaClass fileProvider = new AndroidJavaClass("android.support.v4.content.FileProvider");
        //getUriForFile(Context context, String authority, File file)
        AndroidJavaObject uri = fileProvider.CallStatic<AndroidJavaObject>("getUriForFile", unityContext, authority, fileObj);

        intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
        intent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
        intent.Call<AndroidJavaObject>("addFlags", FLAG_GRANT_READ_URI_PERMISSION);
        currentActivity.Call("startActivity", intent);

        GameObject.Find("TextDebug").GetComponent<Text>().text = "Success";
    }
    catch (System.Exception e)
    {
        GameObject.Find("TextDebug").GetComponent<Text>().text = "Error: " + e.Message;
        success = false;
    }

    return success;
}

编辑:

如果遇到异常:

尝试调用虚方法 'android.content.res.XmlResourceParser android.content.pm.packageItemInfo.loadXmlMetaData(android.c‌​ontent.pm.PackageMan‌​ager.java.lang.Strin‌​g)'

你必须做的事情很少。

1.从您的“AndroidSDK/extras/android/support/v4/android-support-v4.jar”中复制“android-support-v4.jar” 将目录复制到您的 “UnityProject/Assets/Plugins/Android” 目录。

2.在您的UnityProject/Assets/Plugins/Android目录中创建一个名为“AndroidManifest.xml”的文件,并将以下代码放入其中。

确保将 "com.company.product" 替换为您自己的软件包名称。有 2 个实例出现这种情况。您必须同时替换它们:

这些可以在 package="com.company.product"android:authorities="com.company.product.fileprovider" 中找到。不要更改或删除 "fileprovider",也不要更改任何其他内容。

这里是“AndroidManifest.xml”文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product" xmlns:tools="http://schemas.android.com/tools" android:installLocation="preferExternal" android:versionName="1.0" android:versionCode="1">
  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
  <application android:theme="@style/UnityThemeSelector" android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true">
    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
    </activity>

    <provider
          android:name="android.support.v4.content.FileProvider"
          android:authorities="com.company.product.fileprovider"
          android:exported="false"
          android:grantUriPermissions="true">
      <meta-data
          android:name="android.support.FILE_PROVIDER_PATHS"
          android:resource="@xml/provider_paths"/>
    </provider>

  </application>
  <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23" />
</manifest>

3.在您的“UnityProject/Assets/Plugins/Android/res/xml”中创建一个名为“provider_paths.xml”的新文件 目录并将下面的代码放入其中。如您所见,您必须创建一个 res,然后创建一个 xml 文件夹。

确保将 "com.company.product" 替换为您自己的软件包名称。 它只出现一次

以下是您应该放入此“provider_paths.xml”文件的内容:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <!--<external-path name="external_files" path="."/>-->
  <external-path path="Android/data/com.company.product" name="files_root" />
  <external-path path="." name="external_storage_root" />
</paths>

【讨论】:

  • android-support.v4.jar 现在在不同的地方。这是有关如何获取它的快速视频。 youtu.be/E8iKrGaKITQ
【解决方案2】:

我只是想为@Programmer 给出的精彩答案添加更新,以反映对 Android SDK 和 Unity 的更改。我希望它对其他人有用。我正在使用 SDK 版本 26 和 Unity 2017.3.1。我对几乎所有这些都是新手,所以如果我错过或误解了什么,请纠正我!

  1. android-support-v4.jar(用于获取 FileProvider 类)不再可用。对于高达 24.1.1 的版本,它位于 Android\sdk\extras\android\m2repository\com\android\support\support-v4\24.1.1 但对于之后的版本,您必须改为使用Android\sdk\extras\android\m2repository\com\android\support\support-core-utils 上的 support-core-utils。使用相关的 .aar 文件代替 jar 文件并将其拖到 Unity 项目中的 Plugins 文件夹中。
  2. 您需要将 android.permission.REQUEST_INSTALL_PACKAGES 添加到您的 AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.productsomethingelse" xmlns:tools="http://schemas.android.com/tools" android:installLocation="preferExternal" android:versionName="1.0" android:versionCode="1">
      <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
      <application android:theme="@style/UnityThemeSelector" android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true">
        <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name">
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
          <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
        </activity>
    
        <provider
              android:name="android.support.v4.content.FileProvider"
              android:authorities="com.company.product.fileprovider"
              android:exported="false"
              android:grantUriPermissions="true">
          <meta-data
              android:name="android.support.FILE_PROVIDER_PATHS"
              android:resource="@xml/provider_paths"/>
        </provider>
    
      </application>
      <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
      <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26" />
    </manifest>
    

    请注意,我还将 com.company.product 的第一个实例更改为 com.company.productsomethingelse

  3. 现在应该可以正常构建并且可以工作了,尽管在构建过程中 Unity 发出警告 OBSOLETE - 提供 Android 资源 在 Assets/Plugins/Android/res 中已弃用,请移动您的 资源到 AAR 或 Android 库。。为了解决这个问题 创建一个新的 zip 文件并将您的 AndroidManifest.xml 放在顶部 拉链的水平。然后添加详细的provider_paths.xml @Programmer 进入文件夹结构中的 zip res/xml/provider_paths.xml。重命名 zip 以为其提供 .aar 文件扩展名,然后将其拖到您的 Unity 项目中 资产/插件文件夹。我将 AndroidManifest.xml 条目更改为 com.company.productsomethingelse 的原因是,当我使用 com.company.product 时,Unity 构建过程引发了命名冲突。我认为因为清单现在位于单独的 aar 中,所以它需要有一个不同的包名称。

【讨论】:

    【解决方案3】:

    使用更高版本的 Unity,它会变得有点复杂。 Assets/Plugins/Android/res 已弃用。在 Android 9.0 中,android.support.v4 并入了androidx.core,一切都被重命名并移动了。幸运的是,Unity 现在在支持 Android 插件方面做得更好了,因此使用此解决方案您不再需要向项目添加任何 jar 或 aar

    请注意,Android 有许多与此安装过程相关的唠叨屏幕。每个应用程序都有一个用于不受信任的构建的一次性接受屏幕,如果您在设备上安装了 google play,播放保护会添加一堆屏幕,使您的应用程序看起来像恶意软件。 (这是一件好事,因为您应该只在您拥有的 kiosk 模式设备上执行此操作,您可以在其中关闭 Play Protect。)

    添加到Programmer's answer。在 Unity 2019 和 2020 中,可以直接在 Unity 中编译插件,不再需要 jar 文件。在 2019 年,它需要在 Assets/Plugins/Android 中并有几个额外的文件。 2020年的文件夹只需要后缀.androidlib,可以在Assets/的任意位置。

    package.androidlib
      src
        main
          res
            xml
              provider_paths.xml
          AndroidManifest.xml
      build.gradle
      AndroidManifest.xml  (only for 2019)
      project.properties  (only for 2019)
    

    provider_paths.xml:正如程序员所解释的。

    AndroidManifest.xml: 主要是为了给这个新库一个包 ID。这不应与任何其他 ID 匹配;它可以是任何东西,通常是com.companyname.packagename。请注意,它位于main/ 文件夹中

    我们也可以在这里完成 AndroidManifest.xml 的其余部分,因为现在所有的 AndroidManifest.xml 文件都将合并到最终的 APK 中。 FileProvider 包已更改为androidx.core.content.FileProvider

    我注意到你需要android.permission.REQUEST_INSTALL_PACKAGES;但我不确定您是否需要像 Kaushix 建议的那样 READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE。 (也许在某些设备上?我认为项目设置->写入权限涵盖了这一点。)

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.anything.anythingelse">
        <application>
            <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="${applicationId}.provider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/provider_paths"/>
            </provider>
        </application>
        <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    </manifest>
    

    build.gradle: 这应该与您的项目目标和最低 SDK 版本相匹配。少一点也没关系,但是target需要至少28才能使用androidx。

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion 28
    
    
        defaultConfig {
            minSdkVersion 16
            targetSdkVersion 28
        }
    }
    
    dependencies {
        implementation 'androidx.appcompat:appcompat:1.1.0'
    }
    

    对于 2019 年,额外的 AndroidManifest.xml 和 project.properties 文件的存在纯粹是为了告诉 Unity 构建此文件夹。在 2020 年,Unity 已经足够聪明,不需要它们了。

    AndroidManifest.xml:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- This extra manifest, as well as project.properties in this folder shouldn't be necessary with the newer gradle project format. Can remove if you upgrade to 2020.1. -->
    </manifest>
    

    project.properties:

    android.library=true
    

    AndroidPostProcess.cs:

    我们还需要一个编辑器脚本来在主 gradle 文件中启用 AndroidX 支持,并打开 Jetifier,它应该修复针对旧 android.support 库编译的任何其他插件。

    using UnityEditor.Android;
    
    public class AndroidPostProcess : IPostGenerateGradleAndroidProject
    {
        public int callbackOrder => 0;
        public void OnPostGenerateGradleAndroidProject(string path)
        {
            string gradlePropertiesPath = path + "/gradle.properties";
            string[] lines = File.ReadAllLines(gradlePropertiesPath);
    
            StringBuilder builder = new StringBuilder();
            foreach (string line in lines)
            {
                if (line.Contains("android.useAndroidX"))
                {
                    continue;
                }
                if (line.Contains("android.enableJetifier"))
                {
                    continue;
                }
                builder.AppendLine(line);
            }
            builder.AppendLine("android.useAndroidX=true");
            builder.AppendLine("android.enableJetifier=true");
            File.WriteAllText(gradlePropertiesPath, builder.ToString());
        }
    }
    

    【讨论】:

    • 我会为所有这些工作的简单项目而杀。我不断收到 ClassNotFoundException: androidx.core.content.FileProvider 。可能 AndroidPostProcess 没有正确执行
    • 我没有一个可以运行的简单项目,但有一点很有帮助的是查看实际输出,当您保持统一打开时,它将位于 Temp/ 中的某个位置。如果找不到,请尝试将路径参数打印到OnPostGenerateGradleAndroidProject(string path)
    【解决方案4】:

    在这里,我们必须更改您的清单文件。我们必须授予读取和写入权限。 更改此设置后,我们可以轻松安装下载的应用程序文件。

    如果没有这些权限,我们将无法访问 Android 外部文件。

    <?xml version="1.0" encoding="utf-8"?>
        <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product" xmlns:tools="http://schemas.android.com/tools" android:installLocation="preferExternal" android:versionName="1.0" android:versionCode="1">
          <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
          <application android:theme="@style/UnityThemeSelector" android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true">
            <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name">
              <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
              </intent-filter>
              <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
            </activity>
    
            <provider
                  android:name="android.support.v4.content.FileProvider"
                  android:authorities="com.company.product.fileprovider"
                  android:exported="false"
                  android:grantUriPermissions="true">
              <meta-data
                  android:name="android.support.FILE_PROVIDER_PATHS"
                  android:resource="@xml/provider_paths"/>
            </provider>
    
          </application>
          <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
          <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
          <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
          <uses-sdk android:minSdkVersion="16"   android:maxSdkVersion="29"  />
        </manifest>
    

    【讨论】:

      【解决方案5】:

      在所有这些很棒的答案之后,我仍然遇到一个问题:应用程序在启动时立即关闭。 在我的情况下,将 Fileprovider 代码添加到清单会导致此问题。

      修复: 我手动将 androidx.core 库添加到 Unity 项目中。

      1. 下载库 (https://mvnrepository.com/artifact/androidx.core/core/1.0.1)
      2. 将 aar 文件复制到文件夹“Assets\Plugins\Android\libs”中
      3. 在 Unity 中单击 aar- 文件并选中“Android”复选标记

      我仍在寻找其他一些问题,但至少应用程序现在可以启动。 也许我可以用这些信息帮助别人

      【讨论】:

        猜你喜欢
        • 2016-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多