Roly's Blog

Whatever will be, will be, the future's not ours to see.

0%

Unity-Android-Plugin

Unity-Android-Plugin-开发教程1

开发环境Windows平台(Unity2017.1.0f3 Personal ,Android Studio 2.3.3)

在Unity项目中构建和使用Android Plugin

Unity支持几种类型的Android plug-ins:

Unity-Android-Plugin-开发教程1

  1. AAR 插件 和 Android Library
  2. JAR 插件
  3. 继承UnityPlayerActivity
  4. Native(C++) 插件

Unity Android runtime

Untiy Android runtime通过继承自FrameLayoutUnityPlayer实现,UnityPlayer实现了触控,键盘输入,相机,位置等特性。虽然这个UnityPlayer实现了大部分的native功能,但它不是应用程序的入口。

在通用的Android Unity应用程序中,程序的入口是UnityPlayerActivity。如果你看一下APK文件反编译后的AndroidManifest.xml文件,可以看到它是如何标记UnityPlayerActivity作为应用程序的Launcher的。

Unity-Android-Plugin-开发教程1

查看Unity安装目录发现,UnityPlayerActivity的源码可以在C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Source中查看。

Unity-Android-Plugin-开发教程1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class UnityPlayerActivity extends Activity
{
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code

// Setup activity layout
@Override protected void onCreate (Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);

getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy

mUnityPlayer = new UnityPlayer(this);
setContentView(mUnityPlayer);
mUnityPlayer.requestFocus();
}

.........

// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@Override public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return mUnityPlayer.injectEvent(event);
return super.dispatchKeyEvent(event);
}

.........
}

可以看到UnityPlayerActivity继承自Activity,并且UnityPlayerActivity持有一个UnityPlayer实例。UnityPlayerActivity通过UnityPlayer分派native 事件。

Unity-Android-Plugin-开发教程1

通用的Android插件开发,通过继承UnityPlayerActivity,并使子类成为整个应用程序的LAUNCHER Activity,接下来将介绍继承UnityPlayerActivity方式的Android Plugin

继承UnityPlayerActivity方式的Android Plugin

Unity-Android-Plugin-开发教程1

Android Plugin需要包含Android项目中build后得到的app-debug.aarManifest.xml以及资源文件等,提供给Unity项目使用。文件存放在Unity项目中的**/Assets/Plugins/Android。Unity项目中的代码通过app-debug.aar**与封装在其中的Android代码进行交互。For more details about .aar, see Android Developer Doc. And for more information about “How Unity produces the Android Manifest”, see Unity Developer Doc

Unity-Android-Plugin-开发教程1

Unity-Android-Plugin-开发教程1

那么,接下来新建Android项目,进而生成app-debug.aar文件和Manifest.xml文件。

Android端的操作

新建Android 项目

1.将项目切换成project的视图,打开app目录下的build.gradle文件,

1.将apply plugin: ‘com.android.application’,改成apply plugin: ‘com.android.library’

2.然后删除applicationId

Unity-Android-Plugin-开发教程1

2.修改Manifest.xml文件

在activity中添加

<meta-data android:name="unityplaer.UnityActivity" android:value="true"/>

Unity-Android-Plugin-开发教程1

引入Unity的classes.jar

从Unity 的安装目录找到unity的classes.jar包。
Windows目录:

C:\ProgramFiles\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\classes.jar
Mac下目录:

/Applications/Unity/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes\classes.jar
​将其拷贝到UnityAndroid项目app目录下的libs目录下,右键Add as library,导入之后可以发现在build.gradle中就有他的引入了。

Unity-Android-Plugin-开发教程1

Unity-Android-Plugin-开发教程1

编写Android项目与Unity项目交互的代码

首先需要让MainActivity继承UnityPlayerActivity,同时删除onCreate方法中的setContentView()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MainActivity extends UnityPlayerActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
//示例1:
//Unity端调用该函数
public void ShowToast(final String msg){
// 需要在UI线程下执行
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),msg, Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(MainActivity.this).setMessage(msg).show();
}
});
}
//示例2:
//Unity端调用该函数
public void setUnityText(){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),"Android 端调用setText", Toast.LENGTH_SHORT).show();
//调用Unity端函数
UnityPlayer.UnitySendMessage("Canvas","setText","Android 端调用setText");
}
});
}

}

Build得到app-debug.aar文件和Manifest.xml文件

分别在/app/build/outputs/aar和/app/src/main目录下

Unity-Android-Plugin-开发教程1

接下来需要将app-debug.aar用解压软件打开,并且删除libs目录下的classes.jar

Unity-Android-Plugin-开发教程1

Unity端的操作

创建Unity项目

新建Unity项目,并新建如下目录将Android端得到的app-debug.aar文件和Manifest.xml文件放在**/Plugins/Android**目录下,同时在Hierarchy下按照图示新建Canvas,Button和Text:

Unity-Android-Plugin-开发教程1

编写C#脚本

同时新建名为AndroidTest.cs的C#脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class AndroidTest : MonoBehaviour {


// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}
public void BtnShowMessage()
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using(AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
// 调用Android端方法
jo.Call("ShowToast", "Unity调用了Android中的AlertDialog");
}
}
}

public void BtnSetText()
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using (AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
// 调用Android端方法
jo.Call("setUnityText");
}
}
}

//Android端调用该方法
public void setText(string result){
Text text = GameObject.Find ("UnityText").GetComponent<Text> ();
text.text = result;
}

}

编译运行

按照标号步骤进行1. 选择Build的平台->->2. 添加Scenes ->-> 3. 设置Identification ->-> 4. 设置包名和 API Level ->-> 5. 编译运行

Unity-Android-Plugin-开发教程1

效果展示

Unity-Android-Plugin-开发教程1

使用AARJAR方式的Android Plugin

Android端的操作

在Android Studio中新建项目

1.选择Add No Activity

Unity-Android-Plugin-开发教程1

  1. 新建Modue,选择Android Library

Unity-Android-Plugin-开发教程1

  1. 添加AndroidPlugin.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.os.BatteryManager;
    import java.text.SimpleDateFormat;
    import java.util.Date;

    public class AndroidPlugin
    {
    // Needed to get the battery level.
    private Context context;

    public AndroidPlugin(Context context)
    {
    this.context = context;
    }

    // Return the battery level as a float between 0 and 1 (1 being fully charged, 0 fulled discharged)
    public float GetBatteryPct()
    {
    Intent batteryStatus = GetBatteryStatusIntent();

    int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

    float batteryPct = level / (float)scale;
    return batteryPct;
    }

    // Return whether or not we're currently on charge
    public boolean IsBatteryCharging()
    {
    Intent batteryStatus = GetBatteryStatusIntent();

    int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
    return status == BatteryManager.BATTERY_STATUS_CHARGING ||
    status == BatteryManager.BATTERY_STATUS_FULL;
    }

    private Intent GetBatteryStatusIntent()
    {
    IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    return context.registerReceiver(null, ifilter);
    }

    //Get System Time
    public String getSysTime(){
    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).toString();
    }
    }
  2. Build Module之后,在模块的子目录**/build/outputs/arr中得到.aar** 和 .jar

    Unity-Android-Plugin-开发教程1

Unity端的操作

创建Unity项目

将得到的**.arr**(将**.arr解压就可以得到.jar**) 放在Assert目录下,可以放在任意的目录下,官方推荐使用**.aar** 。并且按照如下目录新建Canvas和Text。

Unity-Android-Plugin-开发教程1

编写C#脚本

编写BatteryLevelPlugin.cs :

Unity提供了两种方式与java进行交互

  1. AndroidJNI 和 AndroidJNIHelper

  2. AndroidJavaClass , AndroidJavaObject 和 AndroidJavaProxy

    Unity-Android-Plugin-开发教程1

同时,官方推荐如下调用方式:

Unity-Android-Plugin-开发教程1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BatteryLevelPlugin{

public static float GetBatteryLevel()
{
if (Application.platform == RuntimePlatform.Android)
{
using (var javaUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using (var currentActivity = javaUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
{
using (var androidPlugin = new AndroidJavaObject("com.hiscene.androidsysinfo.AndroidPlugin", currentActivity))
{
return androidPlugin.Call<float>("GetBatteryPct");
}
}
}
}

return 1f;
}

public static string GetSysTime() {

AndroidJNI.AttachCurrentThread();


IntPtr javaClass = AndroidJNI.FindClass("com/unity3d/player/UnityPlayer");

IntPtr fid = AndroidJNI.GetStaticFieldID(javaClass, "currentActivity", "Landroid/app/Activity;");

IntPtr obj = AndroidJNI.GetStaticObjectField(javaClass, fid);


IntPtr pluginClass = AndroidJNI.FindClass("com/hiscene/androidsysinfo/AndroidPlugin");

IntPtr initMethod = AndroidJNI.GetMethodID(pluginClass, "<init>", "(Landroid/content/Context;)V");

jvalue[] jv = new jvalue[1];

//TODO

IntPtr pobj = AndroidJNI.NewObject(pluginClass, initMethod,jv);


IntPtr enableMethod = AndroidJNI.GetMethodID(pluginClass, "getSysTime", "()Ljava/lang/String;");

return AndroidJNI.CallStringMethod(pobj, enableMethod, new jvalue[1]);

}

//方式一:
public static string GetSysTimeAndroidJNI()
{

AndroidJNI.AttachCurrentThread();

IntPtr javaClass = AndroidJNI.FindClass("com/hiscene/androidsysinfo/SysTime");

IntPtr initMethod = AndroidJNI.GetMethodID(javaClass, "<init>", "()V");


IntPtr pobj = AndroidJNI.NewObject(javaClass, initMethod, AndroidJNIHelper.CreateJNIArgArray(new object[1]));


IntPtr enableMethod = AndroidJNI.GetMethodID(javaClass, "getSysTime", "()Ljava/lang/String;");

return AndroidJNI.CallStringMethod(pobj, enableMethod, new jvalue[1]);

}

//方式二:
public static string GetSysTimeAndroidJavaClass()
{
if (Application.platform == RuntimePlatform.Android)
{
using (var javaUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using (var currentActivity = javaUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
{
using (var androidPlugin = new AndroidJavaObject("com.hiscene.androidsysinfo.AndroidPlugin", currentActivity))
{
return androidPlugin.Call<string>("getSysTime");
}
}
}
}

return "Time yyyy-MM-dd HH:mm:ss";
}
}

编写BatteryMonitor.cs :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BatteryMonitor : MonoBehaviour {

public Text batteryLevelText;
public Text batteryLevelIcon;
public Text sysTime;

static readonly string BATTERY_LEVEL_100 = Char.ConvertFromUtf32(0xf240);
static readonly string BATTERY_LEVEL_75 = Char.ConvertFromUtf32(0xf241);
static readonly string BATTERY_LEVEL_50 = Char.ConvertFromUtf32(0xf242);
static readonly string BATTERY_LEVEL_25 = Char.ConvertFromUtf32(0xf243);
static readonly string BATTERY_LEVEL_0 = Char.ConvertFromUtf32(0xf244);

// Update is called once per frame
void Update () {
UpdateStatusIndicators();
sysTime.text = BatteryLevelPlugin.GetSysTime();

}

void UpdateStatusIndicators()
{
var currentBatteryLevel = BatteryLevelPlugin.GetBatteryLevel() * 100f;
batteryLevelText.text = String.Format("{0}%", currentBatteryLevel);

// Show the icon that matches the current level most closely.
if (currentBatteryLevel >= 88)
{
batteryLevelIcon.text = BATTERY_LEVEL_100;
}
else if (currentBatteryLevel >= 63)
{
batteryLevelIcon.text = BATTERY_LEVEL_75;
}
else if (currentBatteryLevel >= 38)
{
batteryLevelIcon.text = BATTERY_LEVEL_50;
}
else if (currentBatteryLevel >= 13)
{
batteryLevelIcon.text = BATTERY_LEVEL_25;
}
else
{
batteryLevelIcon.text = BATTERY_LEVEL_0;
}
}
}

按照步骤编译运行

步骤:1. 选择Build的平台->->2. 添加Scenes ->-> 3. 设置Identification ->-> 4. 设置包名和 API Level ->-> 5. 编译运行

效果展示

Unity-Android-Plugin-开发教程1

源代码

https://github.com/rolyyu/UnityAndroidPlugin

参考文献

[1] https://docs.unity3d.com/Manual/PluginsForAndroid.html

[2] http://www.voidcn.com/blog/Silk2018/article/p-6632911.html

[3] http://blog.csdn.net/zhangdi2017/article/details/65629589

[4] https://www.yangzhenlin.com/unity-android-plugin/

[5] https://medium.com/@datdeveloper/how-to-make-android-plugin-for-unity-take-photo-from-camera-and-gallery-c12fe247c770

[6] http://addcomponent.com/android-native-plugin-unity/