天天看點

Unity3D中使用Gradle出包開篇環境&工具使用Gradle結語

Unity3D中使用Gradle出包

  • 開篇
  • 環境&工具
  • 使用Gradle
    • 建立 Android Studio 工程
      • android端代碼
      • 導出 aar
      • unity 中導入 aar
    • 導出Android Studio 工程
  • 結語

開篇

本篇部落格講述如何在Unity3D中使用Gradle方式釋出Android包。包含的内容一,出包的兩種途徑,一種是通過AndroidStudio導出aar到Unity,然後通過Unity打出Android包,一種是通過Unity導出AndroidStudio工程,然後通過AndroidStudio打出Android包。

二,Android端java代碼和Unity端C#代碼的互相調用。

本篇部落格知識要求,對于Android Studio,Gradle,Unity3d有一定的了解。

環境&工具

Windows10 64位

Android Studio 3.5

Unity 2017.4.37f1(64bit)

使用Gradle

在Unity比較新的版本中,當切換到Android平台進行開發時,預設會使用gradle方式。可以通過 File -> Build Settings 打開Build Settings視窗檢視。

Unity3D中使用Gradle出包開篇環境&工具使用Gradle結語

如果釋出的android包沒有特殊需求,不需要改動android端的java代碼,那麼就可以直接在unity中釋出android包即可,不過現實中每個項目或多或少都有對應平台的特殊修改,最常見的就是接入SDK,這個時候就需要修改android端的java代碼了,同時還要和unity中的C#做互動。當然有的SDK因為功能特性,隻提供封裝好的jar包和一些C#源碼,不需要添加java代碼也可以接入。

建立 Android Studio 工程

首先講建立一個Android Studio 工程,然後導出 aar 給 Unity 使用的方式。

可以建立一個 Empty Activity 類型的 Android Studio 工程,工程建立完畢後預設的app module可以不做調整修改,這個module我們是不需要的,是以上面選 Basic Activity 類型也可以的。

Unity3D中使用Gradle出包開篇環境&工具使用Gradle結語

然後在這個工程中再建立一個 Android Library 類型的 module。其實也可以修改上面的 app module 類型為 Library 。我圖友善就直接建立一個 Library 類型的 module 了。

建立 Android Library module 的時候可以将包名修改為釋出android包使用的包名,和 unity 中定義的android包名一樣即可。這樣就可以建立一個 MainActivity 。讓最終打出的android包使用的是android端定義的MainActivity。

如果不需要使用自定義的 MainActivity,unity導出生成的 UnityPlayerActivity做為 MainActivity 也滿足需求的話,這個library包名就可以不和 unity settings 中的包名保持一緻。這種就類似平常接入的sdk,你通過android studio生成的aar也可以認為是一個sdk,加入了自己自定義的一些功能。

Unity3D中使用Gradle出包開篇環境&工具使用Gradle結語

Android Library 類型的 module 建立完成後,工程目錄如下圖所示。我把module名字修改為了 gamelibrary ,這個名字按喜好随意修改。

下文提到的 gamelibrary 都是這個 module.

Unity3D中使用Gradle出包開篇環境&工具使用Gradle結語

可能遇到的問題:

使用 gradle 可能經常遇到的問題就是因為網絡原因無法下載下傳依賴庫,這個時候可以修改預設倉庫位址為阿裡雲的倉庫位址。

修改工程下的 build.gradle 檔案(注意不是module中的build.gradle)。

修改前相關部分代碼片段

repositories {
        google()
        jcenter()
        
    }
           

修改後相關部分代碼片段

repositories {
        jcenter()
		maven {
            url "https://maven.aliyun.com/repository/jcenter"
        }
        maven {
            url "https://maven.aliyun.com/repository/google"
        }
    }
           

記得有兩處 repositories,都修改下

p.s.其中 jcenter() 倉庫大部分是可以使用的,如果修改完同步 gradle 的時候還是報找不到某些庫的錯誤,可以把 jcenter() 也注釋掉,隻保留aliyun的倉庫位址試試。

android端代碼

建立android library類型的module後,開始寫測試代碼。這裡建立了一個Activity,其中建立了一個Dialog,這個Dialog通過unity端喚起,同時在點選對話框的confirm按鈕後,通過 UnityPlayer.UnitySendMessage 調用unity端c#代碼定義的方法。這個方法很簡單,其中傳遞三個參數:

//第一個參數 C# 腳本所挂的 gameobject 名
//第二個參數 C# 腳本類中定義的方法名
//第三個參數 傳遞的參數
 UnityPlayer.UnitySendMessage("sdk", "CallFromAndroid", "來自android的調用");
           

UnityPlayer.UnitySendMessage 方法是 unity 官方jar包中提供的方法,是以在使用前需要在module中引入這個jar包,這個jar包位于對應版本unity的安裝目錄。

…\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\il2cpp\Release\Classes

使用 il2cpp 或者 mono 目錄下的 class.jar 都可以,因為最後導出 aar 時不會把 class.jar 打包進去,這裡引入 class.jar 隻是為了代碼不報錯,可以正常導出 aar 。

找到 class.jar 拷貝到 gamelibrary module 中 libs 目錄中,然後在Android Studio中右鍵選中 class.jar 執行 Add as Library 就可以把 class.jar 引入到 gamelibrary module中。這個時候打開 gamelibrary 中的 build.gradle 會發現多了一條代碼,這個就是執行 Add as Library 操作後 Android Studio 自動幫你處理。如果拷貝完 class.jar 後,自己手動在 build.gradle 中寫上這段代碼,然後同步下gradle,效果是一樣的。

dependencies {
	....
    implementation files('libs/classes.jar')
}
           

測試 Activity 代碼如下:

package com.good.game;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.Nullable;

import com.unity3d.player.UnityPlayer;

public class GradleTest extends Activity
{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    /**
     * 調用 unity 中 c# 代碼定義的方法
     */
    public static void sendMessageToUnity(){
        UnityPlayer.UnitySendMessage("sdk", "CallFromAndroid", "來自android的調用");
    }

    /**
     * unity端調用這個方法
     */
    public static void showDialog(String msg){
        Log.d("unitycall", "callFromUnity: " + msg);
        final AlertDialog.Builder alertDialog = new AlertDialog.Builder(UnityPlayer.currentActivity).setIcon(R.drawable.app_icon)
                .setTitle("對話框").setMessage(msg)
                .setNegativeButton("close",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                .setPositiveButton("confirm",
                        new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();

                        //關閉對話框的時候調用unity方法
                        sendMessageToUnity();
                    }
                });
        alertDialog.create().show();

    }
}

           

導出 aar

将上一步編寫好代碼的 gamelibrary 導出給 unity 使用的時候,不能把引入的 unity jar 包打包進去,因為這次是使用unity出包android,unity會把class.jar再次打到android包中,這樣就會出現重複類的錯誤。是以需要修改 gamelibrary 中的 build.gradle,将 class.jar 修改為隻在編譯使用。修改後的代碼如下:

dependencies {
	//implementation fileTree(include: ['*.jar'], dir: 'libs')
	...
    compileOnly files('libs/classes.jar')
}
           

同步完畢後,選中 gamelibrary,然後在工具欄找到 Build 接着執行

Build -> Make module ‘gamelibrary’

Make 成功後,會在 build\output\aar 目錄生成一個 aar 包。

gamelibrary\build\outputs\aar

unity 中導入 aar

回到 unity ,建立一個工程,然後在工程中建立一個場景,場景中建立一個Text,一個Button和一個空GameObject并将GameObject修改名字為 sdk。再建立兩個C#腳本,一個為 Demo.cs 用來實作Button點選調用和Text文本展示字元串。一個 MessageCenter.cs 用來和 java 代碼互動。将MessageCenter.cs 拖到 sdk 上。再将Demo.cs拖到 Main Camera 中,然後拖動指派 LogTxt,ClickButton和MsgCenter。

接下來在 Assets 中建立一個 Plugins 目錄,在其中再建立一個 Android 目錄,将上一步生成的 gamelibrary-debug.aar 放置到 Android 目錄。最終目錄如下圖所示。

Unity3D中使用Gradle出包開篇環境&工具使用Gradle結語

場景層級如圖所示:這個場景可以根據自己需求改變的。

Unity3D中使用Gradle出包開篇環境&工具使用Gradle結語

Demo.cs代碼

using UnityEngine;
using UnityEngine.UI;

public class Demo : MonoBehaviour
{

	public Button ClickButton;

	public Text LogTxt;

	public MessageCenter MsgCenter;
	
	// Use this for initialization
	void Start () 
	{
		//android回調後,顯示android傳遞過來的字元串
		MsgCenter.CallBack = str => { LogTxt.text = str; };
		
		ClickButton.onClick.AddListener(OnClickButton);
	}

	/// <summary>
	/// 點選按鈕調用android端定義方法
	/// </summary>
	private void OnClickButton()
	{
		Debug.Log("unitycall OnClickButton");
		MsgCenter.CallStaticFunc("showDialog", "來自unity的調用");
	}

}

           

MessageCenter.cs代碼

using System;
using UnityEngine;

public class MessageCenter : MonoBehaviour
{
    public Action<string> CallBack;
    
    /// <summary>
    /// 和android端互動類的完整類名
    /// </summary>
    private const string ClassName = "com.good.game.GradleTest";


    /// <summary>
    /// 調用android端定義靜态方法
    /// </summary>
    /// <param name="func">靜态方法名</param>
    /// <param name="args">傳遞參數</param>
    public void CallStaticFunc(string func, params object[] args)
    {
        using (AndroidJavaClass cls = new AndroidJavaClass(ClassName))
        {
            cls.CallStatic(func, args);
        }
    }

    /// <summary>
    /// 調用 android 端定義有傳回值靜态方法
    /// </summary>
    /// <param name="func">方法名</param>
    /// <param name="args">傳遞參數</param>
    /// <returns>傳回值</returns>
    public int CallStaticFuncValue(string func, params object[] args)
    {
        using (AndroidJavaClass cls = new AndroidJavaClass(ClassName))
        {
            return cls.CallStatic<int>(func, args);
        }
    }

    private void CallFromAndroid(string msg)
    {
        Debug.Log("CallFromAndroid: " + msg);
        if (CallBack != null)
        {
            CallBack.Invoke(msg);
        }
    }

}

           

這個時候就可以通過unity打android包了。不過這個時候可能會在打包的時候出錯。

如上面所說,gamelibrary 可以選擇和 unity 使用一樣的包名,然後可以建立一個自定義的 MainActivity 覆寫 unity 生成的 UnityPlayerActivity使用。如果是這樣的話,在出包的時候有可能會出現

D8: Program type already present: xxxx.BuildConfig 這樣的錯誤。

D8: Program type already present: com.good.game.BuildConfig
           

這個錯誤是因為導出的 aar 中有一個 BuildConfig.class,unity出包的時候也會生成一個 BuildConfig.class 這樣就會有重複的類,導緻出包産生錯誤。

可以通過解壓 aar,然後再解壓其中的 class.jar 可以看到這個 BuildConfig.class。将這個 BuildConfig.class 删除後,再生成 aar 即可。具體操作步驟:

  1. 修改 aar 檔案擴充名為 zip
  2. 使用 winrar 軟體解壓上面改名後的 zip
  3. 從解壓檔案中找到 class.jar 檔案,然後使用winrar打開。注意是右鍵打開,而不是解壓。找到 BuildConfig.class 後再右鍵删除。這步對于 class.jar 的操作都是在 winrar 軟體中進行的。
  4. 将第2步解壓的所用檔案使用 winrar 壓縮成 zip 格式檔案。注意隻需要全選這些檔案壓縮即可,不要再放到一個單獨的檔案夾中。
  5. 将上一步的壓縮檔案修改擴充名為 aar。

有部落格說可以通過在 gradle 中配置一個 task ,在生成 aar 的時候剔除這個 BuildConfig.class,我沒有試過。其實這個手動删除 BuildConfig.class 在目前的項目中我也沒有使用,我是開啟了混淆,gradle出包的時候會自動将無用的代碼剔除,其中就包括這個 BuildConfig.class。

MainActivity補充:

如果确定要使用自定義的 MainActivity,除了library中使用同樣的包名外,還需要修改下 AndroidManifest 檔案。需要調整的地方:

  1. 将 MainActivity 定義到 AndroidManifest.xml 中,同時聲明為 MainActivity。就是在intent-filter中聲明 action 為 MAIN,category 為 LAUNCHER。同時聲明 meta-data
<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" />
           
  1. 将這個 AndroidManifest.xml 檔案放置到 unity 工程中的 Plugins/Android 目錄下。這樣使用 unity 出包時,不會被unity預設的 manifest 覆寫掉。

經過上面的一系列操作,已經實作了Android Studio導出aar給unity,unity選擇gradle方式出包。

導出Android Studio 工程

接下來講第二種方式,通過unity導出Android Studio工程,然後通過Android Studio出包。

和unity出包類似,unity端配置好包名,版本号,targetversion等資訊後,在Build Settings中勾選 Export Project ,點選 export 按鈕,選擇一個目錄就可以導出一個 Android Studio 工程。

Unity3D中使用Gradle出包開篇環境&amp;工具使用Gradle結語

然後使用 Android Studio 打開導出的工程,等 gradle 同步完成後,就可以編寫java代碼了,可以将上一部分的 GradleTest.java 直接拷貝到工程中使用,使用 Android Studio 直接 run 包。運作正常後再使用Build -> Generate Signed Bundle / APK 生成簽名的包釋出即可。

其實大部分内容還是在編寫兩頭代碼上。

這種方式相對上面導出 aar 方式簡便了一些,同時不會出現 BuildConfig 問題。

結語

兩種方式看項目需求。一般來說android端的java代碼不會頻繁改動,隻是每次更新sdk,如果有api變動或者有其他需求改動,才會進行代碼修改。改動後可以保持很長時間。這樣一次導出 aar 可以使用很長時間,每次出包使用 unity 釋出 APK 即可。

第二種方式因為unity端的代碼改動比較頻繁,是以基本上每次出包,都會需要先導出Android Studio 工程,再使用Android Studio打包,進行兩步操作。當然畢竟使用了gradle,可以通過 OnPostprocessBuild 加 gradle 腳本方式進行腳本打包。像配置了 Jenkins 這種 CI/CD 環境後,可以做到一鍵出包。

上面的兩步手動操作就可以忽略了。

繼續閱讀