如何从Unity中的代码安装Android apk

时间:2017-07-10 12:48:20

标签: c# android unity3d android-intent

我找到了Java代码片段。如何在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);

4 个答案:

答案 0 :(得分:8)

你可以构建一个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克)'

你必须做一些事情。

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>

答案 1 :(得分:3)

我只是想对@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。代替jar文件,使用相关的.aar文件并将其拖到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正在发出警告过时-提供Android资源 不推荐使用Assets / Plugins / Android / res中的 资源到AAR或Android库。。为了解决这个问题 创建一个新的zip文件,并将您的AndroidManifest.xml放在顶部 压缩级别。然后添加provider_paths.xml,详细信息由 @Programmer进入文件夹结构中的zip res / xml / provider_paths.xml 。重命名该压缩文件以为其提供.aar文件扩展名,然后将其拖到您的Unity项目中 资产/插件文件夹。我将AndroidManifest.xml条目更改为 com.company.productsomethingelse 的原因是,当我使用 com.company.product 时,Unity构建过程引发了命名冲突。我认为,因为清单现在位于单独的aar中,因此需要有一个不同的包名称。

答案 2 :(得分:2)

在更高版本的Unity中,它变得更加复杂。 Assets/Plugins/Android/res已弃用。借助Android 9.0,android.support.v4被合并到androidx.core中,所有内容都被重命名并四处移动。幸运的是,Unity现在可以更好地支持Android插件了,因此使用此解决方案您不再需要在项目中添加任何jar或aar

请注意,Android具有与此安装过程相关的一堆nag屏幕。每个应用都有一个一次性接受屏幕,用于显示不受信任的版本,如果您在设备上使用了Google Play,则Play保护会添加一堆屏幕,使您的应用看起来像恶意软件。 (这是一件好事,因为您只应在自己拥有的自助服务终端模式的设备上执行此操作,即可在其中关闭Play保护。)

添加到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版本匹配。如果可以的话,可以,但是目标必须至少为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());
    }
}

答案 3 :(得分:1)

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

没有这些权限,我们将无法访问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>