Unity Android 交互与打包

4,632 阅读4分钟

一、交互

1. 创建 Android 项目

以 Android Studio 为例,创建一个 Empty 项目。

img

从 Unity 中提取 mono 的编译出来的 classes.jar 包,路径为 {Unity 安装位置}\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes\

img

如果找不到该路径,请检查是否安装 Unity 的时候没有选择 Android build support,高版本可以直接在 Unity Hub 中选择添加模块。

img

如果没有该选项,可以从官网下载 UnityDownloadAssistant,只选择安装 Android build support。

添加到 Android 项目中,右键设置 Library。

img

修改 build.gradle[Module] 。

apply plugin: 'com.android.library' // 从 application 修改为 library
dependencies {
    compileOnly files('libs/classes.jar')
}

创建主活动 MainActivity 继承 UnityPlayerActivity。

public class MainActivity extends UnityPlayerActivity{

}

AndroidManifest.xml 中添加 meta-data

<activity android:name="com.zeroyiq.sdkdemolib.MainActivity">
    <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>

2. Unity 调用 Android

Unity

通过指定 Class 调用

private const string JAVA_CLASS_Name = "com.unity3d.player.UnityPlayer"; // unity 指定 Class

/// <summary>
/// 调用 Android 方法
/// </summary>
/// <param name="javaFuncName">Android 需要执行的方法名</param>
/// <param name="args"> 传入参数 </param>
public static void CallJavaFunc(string javaFuncName, params object[] args)
{
    #if UNITY_ANDROID && !UNITY_EDITOR
        try
        {
            // 创建 java 对象
            using (AndroidJavaClass jc = new AndroidJavaClass(JAVA_CLASS_Name))
            {
                // 得到当前 Activity 对象
                using (AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"))
                {
                    // 执行 Activity 中方法 javaFuncName
                    jo.Call(javaFuncName, args);
                }
            }
        }
    catch (System.Exception ex)
    {
        Debug.Log("callSdk error:" + ex.Message);
    }
    #endif
}

public static string CallJavaFuncWithReturnValue(string javaFuncName, params object[] args)
{
    string returnValue = string.Empty;
    #if UNITY_ANDROID && !UNITY_EDITOR
        try
        {
            using (AndroidJavaClass jc = new AndroidJavaClass(JAVA_CLASS_Name))
            {
                using (AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"))
                {
                    returnValue = jo.Call<string>(javaFuncName, args);
                }
            }
        }
    catch (System.Exception ex)
    {
        Debug.Log("callSdk error:" + ex.Message);
    }
    #endif
        return returnValue;
}

public static AndroidJavaObject CreateJavaMapFromDictainary(IDictionary<string, string> parameters)
{
    AndroidJavaObject javaMap = new AndroidJavaObject("java.util.HashMap");
    IntPtr putMethod = AndroidJNIHelper.GetMethodID(javaMap.GetRawClass(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

    object[] args = new object[2];
    foreach (KeyValuePair<string, string> kvp in parameters)
    {
        Debug.LogFormat("CreateJavaMapFromDictainary k = {0}", kvp.Key);
        Debug.LogFormat("CreateJavaMapFromDictainary v = {0}", kvp.Value);
        if (kvp.Key != null && kvp.Value != null)
        {
            using (AndroidJavaObject k = new AndroidJavaObject("java.lang.String", kvp.Key))
            {
                using (AndroidJavaObject v = new AndroidJavaObject("java.lang.String", kvp.Value))
                {
                    args[0] = k;
                    args[1] = v;
                    AndroidJNI.CallObjectMethod(javaMap.GetRawObject(), putMethod, AndroidJNIHelper.CreateJNIArgArray(args));
                }
            }
        }
        else
        {
            Debug.LogWarning("Invalid Dictainary paramters");
        }
    }

    return javaMap;
}

Android

在 MainActivity 中添加方法

public class MainActivity extends UnityPlayerActivity{
    // Unity 中可以通过 CallJavaFunc("login","inputString") 调用
    public void login(String platformName) {
        Log.i(TAG, "[" + platformName + "]Begin Login by Unity.");
        ...
        Log.i(TAG, "[" + platformName + "]End Login by Unity.");
    }

    public void logout() {
        ...
    }
}

3. Android 调用 Unity

Android

通过 UnityPlayer 接口发送信息。MessageHandler 对应 Unity 场景中用来接收信息的 GameObject 的名字。ReceiveLogInfo 为执行方法名,msg 为传参,没有传入 “”。

public static void unityLogInfo(String tag, String msg) {
    Log.i(TAG, msg);
    Log.i(TAG, "UnityLogInfo: Begin");
    UnityPlayer.UnitySendMessage("MessageHandler", "ReceiveLogInfo", msg);
    Log.i(TAG, "UnityLogInfo: End");
}

Unity

创建对应 GameObject。注意方法不能是静态方法。

m_MessageHandler = new GameObject("MessageHandler");
m_MessageHandler.AddComponent<MessageHandler>();
public class MessageHandler : MonoBehaviour
{
    public void ReceiveLogInfo(string log)
    {
        Debug.Log(log);
    }
}

二、出包

实现方案:将 Android 项目 build 成 jar 或者 aar,添加到 Unity 项目指定路径(Assets\Plugins\Android\)下,就可以通过 Unity 提供的相关 Android API 进行调用 Class 中的方法。

jar 与 aar 的区别主要是,jar 包,是代码编译后的字节码归档,需要自己再导入资源,而 aar 是 Android 的归档格式,自带资源,推荐直接使用 aar。

1.a 导出 jar(二选一)

在 build.gradle[Module] 中添加导出 Task。

// 定义SDK包名称
def SDK_BASENAME = "UnitySDKDemo"
// 定义SDK包版本
def SDK_VERSION = "_V1.0.0"
// SDK包生成地址
def SDK_PATH = "build/libs"
// 删除之前的Jar包 保证每一次生成的都是最新的
task deleteOldJar(type: Delete) {
    delete SDK_PATH + SDK_BASENAME + SDK_VERSION + '.jar'
}
task exportJar(type: Copy) {
    // 从源地址拷贝
    from('build/intermediates/packaged-classes/release/')
    // 存放
    into(SDK_PATH)
    // 导入
    include('classes.jar')
    // 重命名
    rename('classes.jar', SDK_BASENAME + SDK_VERSION + '.jar')
}
// 执行脚本文件
exportJar.dependsOn(deleteOldJar, build)

执行。

img

将在 build/libs 生成的 jar 包,添加到 Unity 项目的 Assets\Plugins\Android\ 目录下。找不到 build 文件夹的确认下 Project 模式是否设置错误,改为 Project Files。

img

此外, Android 项目中两个目录,[Module]\libs\ (除去我们上文添加的 classes.jar)和 [Module]\src\main\res\,也需要一并复制到Assets\Plugins\Android\ 目录下。

img

1.b 导出 aar(二选一)

执行 assemble。

img

将 build\outputs\aar\ 下的 aar 包,添加到 Unity 项目的 Assets\Plugins\Android\ 目录下。

注意,可以用压缩软件将 aar 包打开,检测下 libs 中是否存在 classes.jar,若存在,需要手动删除。

2. 配置 Androidmanifest 和 gradle

Unity 在build 时候Androidmanifest 和 gradle 使用的是默认配置。

Androidmanifest

默认路径为 {Unity 安装位置}\Editor\Data\PlaybackEngines\AndroidPlayer\APK\

img

可以在 Assets\Plugins\Android\ 目录下添加 AndroidManifest.xml 替代默认配置。官方文档 有介绍具体内容。

我们将 Android 项目的 Androidmanifest.xml 中的内容复制过来。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zeroyiq.sdkdemolib"
    xmlns:tools="http://schemas.android.com/tools"
    android:installLocation="preferExternal">
        <supports-screens
        android:smallScreens="true"
        android:normalScreens="true"
        android:largeScreens="true"
        android:xlargeScreens="true"
        android:anyDensity="true"/>

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:theme="@style/UnityThemeSelector"
        android:resizeableActivity="true">
        <meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />
        <activity
            android:name="com.zeroyiq.sdkdemolib.MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
            </intent-filter>
            <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
        </activity>
        ...
    </application>

</manifest>

gradle 配置

默认路径为 {Unity 安装位置}\Editor\Data\PlaybackEngines\AndroidPlayer\Tools\GradleTemplates\

img

可以在 Assets\Plugins\Android\ 目录下添加同名文件替代默认配置。 官方文档 中有具体介绍每个文件的意义。

这里我们使用到了 mainTemplate.gradle,根据 Android 项目添加内容。

// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
buildscript {
    repositories {
        google()
        // 仓库
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.0'
    }
}

allprojects {
   repositories {
      google()
      flatDir {
        dirs 'libs'
      }
   }
}

apply plugin: 'com.android.library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
**DEPS**
    // 依赖
}

android {
    compileSdkVersion **APIVERSION**
    buildToolsVersion '**BUILDTOOLS**'

    defaultConfig {
consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
        minSdkVersion **MINSDKVERSION**
        targetSdkVersion **TARGETSDKVERSION**

        ndk {
            abiFilters **ABIFILTERS**
        }
        versionCode **VERSIONCODE**
        versionName '**VERSIONNAME**'
    }

    lintOptions {
        abortOnError false
    }

    aaptOptions {
        noCompress '.unity3d', '.ress', '.resource', '.obb', '.mp4', '.png'
    }
}

**SOURCE_BUILD_SETUP**

3.a 导出 apk

File\Build Settings 中直接 Build 出 apk。

img

3.b 导出项目

File\Build Settings 中勾选 Export Project,导出的项目直接用 Android Studio 打开,能够很容易的解决部分配置冲突问题,通过 Android Studio 进行编译和调试也更加方便。

img

4. 踩坑(support 冲突)

Android support 和 Androidx 两个库是不能共存的,但是在添加依赖的时候产生了冲突。有很多的解决办法。

如果是 3.a 导出 apk,可以锁定 support 依赖,在 Android Studio 的 Terminal 中执行 gradlew -q {Module 名称}:dependencies 。

然后在 mainTemplate.gradle 依赖添加如下代码。

dependencies {
    ...
    implementation ('com.twitter.sdk.android:twitter:3.1.1'){
        exclude group: 'com.android.support' // 排除 support依赖
    }
    ...
}

如果是 3.b 导出项目 可以在 Android Studio 中直接迁移项目到 AndroidX。

img