美文网首页Android2UnityunityUnity跨平台技术分享
Android Unity Plugin 开发指南(转载)

Android Unity Plugin 开发指南(转载)

作者: 雨落随风 | 来源:发表于2017-08-14 17:10 被阅读495次

    Unity Android Plugin开发指南
    原创 2016-07-05 haodongyuan QQ音乐技术团队


    本文将介绍如何在Unity工程中使用Android或者Java的库,包括:
    • 如何在Unity项目中使用Android Plugin
    • Unity-Android相互调用
    • Unity接口设计的最佳实践
    • 如何构建Unity-Android混合项目
    • 如何调试Unity和Android代码
    • 附录:跨虚拟机调用的实现

    如何在Unity项目中使用Android Plugin


    Android Plugin需要包含一个jar和对应的封装代码。后者用来封装Android代码,提供给Unity项目使用。
    jar放在Unity项目的/Assets/Plugins/Android
    中,Android插件的其他依赖也放在此处。
    封装代码可以是C#文件,或者dll文件,都放在/Assets
    中,若是dll,需在Unity C#工程中添加此dll依赖。
    此外,如果Android插件包含资源,按照原有目录结构放到/Assets/Plugins/Android
    中即可。
    如果需要额外的系统权限,需要在AndroidManifest.xml中添加,这个文件默认是没有的,如果要修改的话,必须手动添加一份Unity可用的manifest,可参考【附录】中提供的模板。
    最后,工程的结构类似这样:

    Assets
    └── Plugins
        ├── Android
        │   ├── AndroidManifest.xml
        │   ├── android_sdk.jar
        │   └── res
        │       ├── values
        │       └── drawable
        └── unity_wrapper.dll
    

    Unity 从5.2.0b3版本开始很好地支持aar格式的文件,可以将资源打包进aar中,不必再放置到该目录下

    Unity与Android之间相互调用


    准确来说,应该是两个VM之间的相互调用:mono/il2cpp 和 dalvik/art,分别运行Unity应用和Android应用,这两个虚拟机运行在同一个进程中。
    为了方便起见,后文将前者称为Unity,后者称为Android


    如上图所示,Unity通过UnityEngine
    提供的API调用Android的方法;Android借助com.unity.player
    包提供的API调用Unity的方法。
    前者可以直接调用Android对象或者类的方法,而后者只能调用Unity中指定GameObject
    所挂载的脚本的方法,或者通过动态代理的方式调用Unity的方法。

    Unity调用Java方法


    UnityEngine提供了两个类来分别访问Java的实例对象以及类对象:AndroidJavaObjectAndroidJavaClass
    前者表示java.lang.Object
    或其子类,后者表示java.lang.Class
    。他们提供相同的实例方法:

    方法 返回值 说明
    Call void 调用实例方法
    Call<T> T 调用实例方法
    CallStatic void 调用类方法
    CallStatic<T> T 调用实例方法
    Get<T> T 获取成员变量
    GetStatic<T> T 获取类的成员变量
    Set(T) void 设置成员变量
    SetStatic(T) void 设置类的成员变量

    注意:

    • T的类型只能为原始值类型(int、long、string等等),或者AndroidJavaObject
      AndroidJavaClass
      ,或者内容为原始值类型或AndroidJavaObject
      的数组

    • Get和Set方法直接操作成员变量,而不是通过getter或setter

    下面将通过一段代码来演示:如何获取一个AndroidJavaClass
    实例,并且调用其getInstance
    方法获取其对象,然后调用此对象的方法。
    在开始之前,先看一下我们用到的Java类

    package example;
    
    public class Player {
      private final static Player instance = new Player();
      public static Player getInstance() {
        return instance;
      }
    
      public float volume = 1.0f;
      public int getDuration() {}
      public void setDataSource(String dataSource) {}
      public AudioInfomation getAudioInfomation() {}
    }
    

    首先,在工程中添加 UnityEngine.dll
    依赖,该文件位于Unity安装目录下的Editor/Data/Managed
    目录中,注意,添加依赖后,将其设置为不拷贝到本地。
    现在,我们来获取Player
    这个类并获取其单例:

    AndroidJavaObject player = new AndroidJavaClass("example.Player").CallStatic<AndroidJavaObject>("getInstance");
    

    然后对player
    对象调用其Java方法:

    player.Set("volume", 0.8f);
    player.Call("setDataSource", "http://example.com/stream.m4a");
    int duration = player.Call<int>("getDuration");
    AndroidJavaObject info = player.Call<AndroidJavaObject>("getAudioInfomation");
    

    注意,返回值类型为AndroidJavaObject
    的方法有个共同的缺陷:如果Android侧返回null,该方法将报错:JNI: Init'd AndroidJavaObject with null ptr!

    这是因为,在AndroidJavaObject
    的构造函数中,遇到IntPtr.Zero(即null)
    会报错:

    internal AndroidJavaObject(IntPtr jobject) : this(){
      if (jobject == IntPtr.Zero){
        throw new Exception("JNI: Init'd AndroidJavaObject with null ptr!");
      }
      // ...
    }
    

    该缺陷存在于5.3.1f1版本之前的UnityEngine,一个可行的办法是:先获取Android方法返回结果的指针,如果指针为空就返回null,否则返回指针的对象。

    Android调用Unity方法


    在Android中,有两种方式调用Unity的方法:

    • 通过AndroidJavaProxy进行无感知调用
    • 通过com.unity3d.player.UnityPlayer.UnitySendMessage方法显式调用

    ndroidJavaProxy

    AndroidJavaProxy常用于在Unity中实现Java的interface,比如有这么一个java interface:

    package demo;
    
    interface PlayStateListener {
      void onBufferFinished(SongInfo songInfo);
      void onBufferProgress(String songId, long buffered, long total);
    }
    

    对应的C#类就是这样:

    class PlayStateChangedHandler : AndroidJavaProxy {
      internal PlayStateChangedHandler() : base(new AndroidJavaClass("demo.PlayStateListener")) {}
      public void onBufferFinished(AndroidJavaObject songInfoObject) {}
      public void onBufferProgress(string songId, long buffered, long total) {}
    }
    

    有几点需要注意:

    1. Unity侧的方法必须为public,且有相同的名称和类似的签名
    2. 如果Android侧方法的传参或返回值为类类型,对应Unity侧只能为AndroidJavaObject
    3. 4.6.8f1版本的UnityEngine有BUG,无法在AndroidJavaProxy
      中传递long类型的值,该问题在Unity 5中已经修复

    有关AndroidJavaProxy的实现,在附录中有详细介绍

    UnityPlayer.UnitySendMessage

    这需在Android工程中添加Unity提供的jar依赖,它位于Unity安装目录下:/Editor/Data/PlaybackEngines/AndroidPlayer/Viariations/{backend}/{buildType}/Classes/classes.jar

    其中,backend是Unity项目脚本执行器的类型,有mono和il2cpp两种,与Unity项目的”Script Backend”一致。
    然后通过以下代码来访问挂载在TGameObj对象上的脚本的OnButtonClick方法:UnityPlayer.UnitySendMessage("TGameObj", "OnButtonClick", "Greetings from Java");

    Unity接口设计的最佳实践


    本节将介绍一个用于封装Java代码的通用设计方式,可以高效地将Java代码的API“移植”到C#,同时保持可扩展性。该设计将Java代码中的类及其结构反射到C#代码中,至于该类的细节(比如继承关系、接口实现等)将被忽略,因为需要反射的都是暴露给用户的API接口,用户不应该关心这些细节。
    如下图所示:


    Java中的demo.Foo类通过Reflection反射到C#的Mirrored.Foo中,demo.Foo中的公共字段和方法都按照原有结构被反射。

    注意,这里的反射只是单向地从Java反射到C#。如果要从C#反射到Java,可以参考本节进行扩展。

    反射的实现


    在开始之前,我们需要明确哪些类需要反射。对于int, long, double等原始类型以及string类型,UnityEngine已经帮我们处理好了,只剩下java.lang.Object的派生类需要我们反射。

    反射基类的设计

    我们使用AndroidObjectMirror作为反射类的父类。

    public abstract class AndroidObjectMirror : IDisposable {
      protected AndroidJavaObject AJObject { get; private set; }
    
      internal void FromJava(AndroidJavaObject ajo) {
          AJObject = ajo;
          InitFromJava(ajo);
      }
    
      public virtual void Dispose() { AJObject?.Dispose(); }
    
      protected virtual InitFromJava(AndroidJavaObject ajo) {}
    }
    

    AJObject这个反射对象被创建时,被反射对象的引用计数将会增加(AndroidJNISafe.NewGlobalRef),在Dispose
    方法中,其引用计数将会减少(AndroidJNISafe.DeleteGlobalRef)。

    之后,子类通过覆写InitFromJava方法来进行成员变量的初始化:
    子类可以创建和被反射类“一样的”方法,并将所有的调用委托给成员变量AJObject即可。例如:

    int Add(int a, int b) { 
     return AJObject.Call<int>("add", a, b);
    }
    

    总结一下,反射的逻辑如下图所示:

    反射的实现

    借助于AndroidObjectMirror
    ,我们可以这样来定义上文提及的example.Player
    的反射类:

    class Player : AndroidObjectMirror {
      public static Player Instance {
        get {
          var javaObject = AJObject.CallStatic<AndroidJavaObject>("getInstance");
          return Reflection.Reflect<Player>(javaObject);
        }
      }
    
      public float Volume {
        get { return AJObject.Get<float>("volume"); }
        set { AJObject.Set<float>("volume", value); }
      }
    
      public void Start() { AJObject.Call("start"); }
      // ..
    }
    

    注意,在获取单例时,我们用了这样一行代码:
    return Reflection.Reflect(javaObject);

    Reflection这个工具类用来反射Java的对象,即将AndroidJavaObject的对象反射为派生自AndroidObjectMirror的类的对象。
    其中的Reflect方法是这样实现的:

    public static T Reflect<T>(AndroidJavaObject ajo) where T : AndroidObjectMirror, new() {
        if (ajo == null) {
            return default(T);
        }
        try {
            var result = new T();
            result.FromJava(ajo);
            return result;
        }
        catch (Exception e) {
            Debug.LogError("failed to reflect from Java : " + e.Message);
            return default(T);
        }
    }
    

    这里的逻辑很直接:创建一个AndroidJavaObject对象ajo,然后在InitFromJava方法中通过ajo来初始化这个对象的成员变量。注意,这里约束了类型T必须提供无参公共构造函数,因此AndroidJavaMirror必须通过InitFromJava(AndroidJavaObject)来初始化,而没有将AndroidJavaObject放在构建函数中。
    至于InitFromJava方法,它可以是这样:

    protected override void InitFromJava(AndroidJavaObject ajo) {
        title = ajo.Get<string>("title");
    }
    

    或者类似反序列化的方式在运行时进行解析:

    protected virtual void InitFromJava(AndroidJavaObject androidJavaObject) {
      var namingConverter = DefaultNamingConverter.Instance;
      var publicFields = GetType().GetFields();
      foreach (var field in publicFields) {
        var javaFieldName = namingConverter.GetJavaFieldName(field.Name);
        var value = androidJavaObject.Get(field.GetType(), javaFieldName);
        field.SetValue(this, value, BindingFlags.SetField, null, null);
      }
    }
    

    如何构建Unity-Android混合项目


    本节将介绍如何使用Gradle来构建混合了不同平台项目的工程。
    以一个SDK类型的工程为例,我们来看一下工程的内容:

    1. Android SDK
    2. Android Demo (快速调试)
    3. Unity Bridge (封装Android SDK)
    4. Unity Demo (演示并调试Unity Bridge)

    目录结构如下:

    RootDir (工程根目录)
    |
    |-- Android (Android相关模块)
    |   |-- Demo_Android
    |   |-- SDK_Android
    |
    |-- Unity (Unity相关模块)
        |-- Demo_Unity
        |-- Bridge_Unity
    

    其中:

    • Android的两个模块可以用Android gradle插件进行编译与打包
    • Bridge_Unity可以用msbuild(windows)或者xbuild(linux)构建
    • Demo_Unity需要购买了Unity Pro之后才能自动化构建。

    接下来,我们将在各自模块的构建脚本中添加构建任务,分别构建这些模块,最后,在工程的根构建脚本中,创建自动化的构建脚本。

    Android SDK的构建


    Jar包构建任务

    SDK将以Jar的形式提供给Unity Bridge使用,因此需要添加打包成jar的构建任务。我们利用已有的Android构建任务链,创建Jar构建任务。
    已有的构建脚本位于RootDir/Android/SDK_Android/build.gradle,在其中加入Jar构建任务:

    android.libraryVariants.all { v ->
      def type = v.name.capitalize()
      task("jar$type", type: Jar) {
          archiveName "sdk-$type.jar"
          dependsOn v.javaCompile
          from v.javaCompile.destinationDir, configurations.compile.collect {
              it.isDirectory() ? it : zipTree(it)
          }
      }
    }
    

    task后面的闭包会在gradle脚本构建时运行,用来定义此任务的属性:

    • archiveName: 输出Jar包的文件名,默认为模块名称
    • dependsOn: 此任务的依赖
    • from: 要打包的class

    这里需要注意:
    依赖
    dependsOn: v.javaCompile
    此任务必须在v.javaCompile完成之后运行,即java文件被编译成class文件之后再将这些class打包成Jar。
    要打包的class

    from v.javaCompile.destinationDir, configurations.compile.collect {
      it.isDirectory() ? it : zipTree(it)
    }
    

    这里说明要打包的class有两处:模块自身的和依赖的Jar包。

    Proguard构建任务

    对外发布时,通常需要对代码进行混淆。对于我们自定义的Jar任务,必须手动添加混淆任务:

    task("proguardJar$type", type: ProGuardTask) {
        dependsOn "jar$type"
        configuration android.getDefaultProguardFile('proguard-android.txt')
        configuration 'proguard-rules.pro'
        injars "build/libs/sdk-$type.jar"
        outjars "$outputFolder/sdk-$suffix.jar"
        dontshrink
        ignorewarnings
    }.doFirst {
        delete outputFolder
    }
    

    注意其中的dependsOn "jar$type",这样就将混淆任务和jar任务串联了起来。

    发布任务

    为了便于其他构件脚本获取此模块的最新构建结果,我们将输出的Jar拷贝到latest目录中。

    task("buildJar$type", type: Copy, group: 'build') {
        dependsOn "cleanBuildJar$type"
        from outputFolder
        into "build/outputs/libs/latest/$type
        if (type.equals("Release")) {
            dependsOn "proguardJar$type"
        } else {
            dependsOn "jar$type"
        }
    }
    

    至此,Android SDK构建任务添加完成。

    Android Demo的构建


    Andriod Gradle Plugin已经提供Demo的构建任务。

    Unity Bridge的构建


    在开始之前,我们需要配置好构建环境:对于Windows系统,需要用到msbuild,它会随着Visutal Studio一同安装;对于linux/unix系统,可以使用xbuild,它是Mono里面的一个工具。下文将使用xbuild。

    准备工作完成后,我们来创建CSharpBuildTask
    这个构建任务。该任务其实就是封装了对xbuild的调用:

    package demo
    
    class CSharpBuildTask extends DefaultTask {
        @Input File solutionFile;
        @Input String configuration;
        @Input String builderCmd = "/usr/local/bin/xbuild"
    
        @TaskAction def compile() {
            def cmd = "$builderCmd $solutionFile"
            if (configuration != null) {
                cmd += " /p:Configuration=$configuration"
            }
            def proc = cmd.execute()
            proc.waitFor()
            if (proc.exitValue() != 0) {
                throw new BuildException("failed to build csharp project: ${proc.getErrorStream()}", null)
            }
        }
    }
    

    CSharpBuildTask.groovy放在RootDir/buildSrc/src/main/groovy/demo下,这样就可以在所有子模块中使用该任务。
    之后,在RootDir/Unity/Bridge_Unity目录下创建build.gradle文件,作为此模块的构建脚本。内容为:

    import demo.CSharpBuildTask
    
    def buildTypes = ["Release", "Debug"]
    def localProps = new Properties()
    localProps.load(project.file('local.properties').newDataInputStream())
    buildTypes.each { v ->
        task("buildLib$v", type: CSharpBuildTask) {
            builderCmd = localProps["msbuild.dir"].toString()
            solutionFile = new File("Unity_Bridge")
            configuration = v
        }
    }
    

    local.properties文件位于RootDir/Unity/Bridge_Unity,内容为:

    # local.properties 
    msbuild.dir=/usr/local/bin/xbuild
    

    至此,我们用gradle查看一下是否成功创建了此构建任务:

    $ gradlew tasks
    

    可以看到,buildLib构建任务已经创建。

    Unity Demo的构建


    受限于Unity,只有Unity Pro及以上版本才支持代码或者命令行的方式进行构建。
    首先,我们需要在/Asset/Editor中创建一个脚本,通过BuildPipeLine来构建Unity工程:

    public class BuildScript: MonoBehaviour{
      static void BuildAndroid(){
        string[] scenes = {"Assets/Demo.unity"};
        BuildPipeline.BuildPlayer(scenes, "AndroidBuild", BuildTarget.Android, BuildOptions.None);
      }
    }
    

    然后,在RootDir/buildSrc中创建UnityBuildTask:

    class UnityBuildTask extends DefaultTask {
        @Input String unityExePath
    
        @TaskAction def compile() {
            def cmd = "$unityExePath -quit -batchmode -executeMethod BuildScript.BuildAndroid"
            def proc = cmd.execute()
            proc.waitFor()
            if (proc.exitValue() != 0) {
                throw new BuildException("failed to build unity project", null)
            }
        }
    }
    

    最后,在RootDir/Unity/Demo_Unity
    中创建build.gradle,并在其中创建一个构建任务:

    // 有关localProps见前文
    task("buildUnityDemo",type: UnityBuildTask, group: 'build') {
      unityExePath = localProps["unitybuild.dir"].toString();
    }
    

    BuildPipeLine以及Unity的命令行调用可以参考官方文档:http://docs.unity3d.com/Manual/CommandLineArguments.html

    混合构建


    上面已经介绍了各个模块各自的构建方法,现在,我们将在根模块的构建脚本中将他们串联起来。

    首先来梳理一下所有构建任务之间的依赖关系,已有的构建任务有:
    其中,箭头表示依赖关系,Unity的Demo同时依赖于Unity和Android的SDK,同时还要将生成的SDK拷贝到Unity Demo项目中的特定位置,这样Demo才能正常运行。
    这些构建任务的依赖关系如下图所示:
    我们在根模块中创建这些构建任务:
    • copyUnitySDKToDemo:将生成的Unity SDK拷贝到Unity Demo
    • copyAndroidSDKToDemo:将生成的Android SDK拷贝到Unity Demo
    • buildUnitySDK:buildLib的马甲
    • buildAndroidSDK:buildJar的马甲
    • buildUnityDemo:构建Unity demo
    • buildAndroidDemo: 构建Android demo

    我们可以在根模块的build.gradle中添加这些任务,但会使得build.gradle
    变得非常混乱。因此我们采用Plugin的方式,来进行这些任务的创建。
    现在我们来创建SDKBuildPlugin,在RootDir/buildSrc/src/main/groovy/demo中新建SDKBuildPlugin.groovy:

    package demo
    
    class SDKBuildPlugin implements Plugin<Project> {
      def buildTypes = ["Release", "Debug"]
      @Override
      void apply(Project project) {
      }
    }
    

    接下来,为每个Build Type创建构建任务。在apply方法中,添加如下代码:

    buildTypes.each { v ->
      project.task("buildAndroidSDK$v",
        dependsOn: ":sdk_android:buildJar$v")    
      project.task("buildUnityDemo$v") {
        dependsOn "cleanUnityDemo", "copyUnitySDKToDemo$v", "copyAndroidSDKToDemo$v"
      }
      project.task("copyAndroidSDKToDemo$v", type: Copy) {
        dependsOn "buildAndroidSDK$v"
        from "$androidSDKProjectDir/build/outputs/libs/latest/$v/"
        into "$unityDemoProjectDir/Assets/Plugins/Android"
              include "*.jar"
        }
      // ...
    }
    

    这里创建了三个典型的任务,其中buildAndroidSDK仅声明其依赖于sdk_android模块的buildJar任务,相当于为buildJar任务创建了一个别名。
    其他的构建任务的创建不做赘述。
    最后在build.gradle中应用此插件:

    // build.gradle
    import com.tencent.qqmusic.MusicUnitySDKBuildPlugin
    // 中间略
    apply plugin: MusicUnitySDKBuildPlugin
    

    SDK的发布任务


    SDK对外提供的内容比较繁杂,包括:

    • SDK的库文件(dll与jar)
    • Demo APP或工程
    • Demo 工程
    • 接口文档
    • Change log

    这些内容都可以通过gradle的构建任务来自动完成。发布目录的结构如下:

    Publish
    |-- 1.0
    |   |-- 1.0.0.0
    |   |   |-- Debug
    |   |   |-- Release
    |   |   |-- ChangeLog.md
    |   |   |-- 接口文档.md
    |   |   |-- Demo
    |   |
    |   |-- 1.0.0.1
    |
    |-- 2.0
    .   
    .
    

    构建任务的结构如下图:


    这里的构建任务都很简单,不做详述。注意拷贝Demo工程的时候,需要过滤掉build结果。
    至此,我们完成了SDK的构建系统。

    如何调试


    C#和Java的调试都只能通过adb远程调试来进行。
    首先用USB连接手机,在命令行中输入adb tcpip 5555
    然后进入adb shell,用ifconfig查看手机的ip地址,之后通过adb connect xxx.xxx.xxx.xxx:5555连接手机
    连接成功之后就可以通过MonoDevelop或者Android Studio的【Attach to process】进行调试了。
    注意:
    如果使用Xamarian进行C#代码的调试,可能无法找到【Attach to process】,这时候需要下载这个插件:
    http://forum.unity3d.com/threads/unity-add-ins-for-monodevelop-xamarin-studio-5-9.329880/
    如果在Android Studio中无法看到程序的进程,请确保包含Java代码的Android工程已经被正确载入

    附录


    AndroidJavaObject.Call的实现


    这里分C#和Java两部分讲解。
    C#部分
    整个调用序列如下图:


    简单来说,整个流程为:
    1. 通过GetMethodId找到方法对应的内存地址
    2. 创建入参,同时处理AndroidJavaObjectAndroidJavaProxy等特殊类型的参数
    3. 通过内存地址调用目标方法

    其中,最关键的部分在于1.1.1 AndroidJNI.CallStaticObjectMethod,这个方法用于调用Android侧对象或者类的方法,其中:

    • ReflectionHelper_classPtr : 指向Java类com.unity.player.ReflectionHelper
      的指针
    • getMethodId_ptr : 指向上述Java类的getMethodID
      方法的指针
    • methodInfo : 包括方法名、签名、是否静态方法等信息

    意思就是,调用Android侧的ReflectionHelper.getMethodID方法,先在Android侧获取到methodInfo描述的Method实例,然后将其指针传回给Unity侧。

    Java部分


    这部分主要是ReflectionHelper这个类,负责获取Android侧类的成员(变量、方法、构造函数),以及创建用于AndroidJavaProxy的Android侧proxy对象。

    AndroidJavaProxy的实现


    首先,我们来看一下如何从AndroidJavaProxy生成一个java.lang.Proxy
    在上一节中,我们知道,所有的AndroidJavaObject.Call方法都会调用AndroidJNIHelper.CreateJNIArgArray方法,该方法就由AndroidJavaProxy实例生成了一个Proxy实例:

    class _AndroidJNIHelper {
      public static jvalue[] CreateJNIArgArray(object[] args) {
        // ...
        else if (obj is AndroidJavaProxy) {
          array[num].l = AndroidJNIHelper.CreateJavaProxy((AndroidJavaProxy)obj);
        }
        // ...
      }
    }
    

    虽然AndroidJNIHelper.CreateJavaProxy(AndroidJavaProxy)这个方法是native的,无法分析其实现,但是我们可以参考_AndroidJNIHelper.CreateJavaProxy(int,AndroidJavaProxy)方法:

    // _AndroidJNIHelper
    public static IntPtr CreateJavaProxy(int delegateHandle, AndroidJavaProxy proxy){
      return AndroidReflection.NewProxyInstance(delegateHandle, proxy.javaInterface.GetRawClass());
    }
    

    两者的实现应该是类似的,最终都是调用Android侧的ReflectionHelper.newProxyInstance方法,用来在Android侧创建一个动态代理:

    // ReflectionHelper
    protected static Object newProxyInstance(int paramInt, final Class[] paramArrayOfClass){
      // ..
      return Proxy.newProxyInstance(ReflectionHelper.class.getClassLoader(), paramArrayOfClass, new InvocationHandler()
      {
        public final Object invoke(Object proxy, Method method, Object[] args)
        {
          return ReflectionHelper.nativeProxyInvoke(this.a, method.getName(), args);
        }
       // ..
      });
    }
    

    可以看到,Android侧通过ReflectionHelper.nativeProxyInvoke将该侧的方法调用代理到了Unity侧,而Unity侧对应的方法为:

    // UnityEngine._AndroidJNIHelper
    public static IntPtr InvokeJavaProxyMethod(AndroidJavaProxy proxy, IntPtr jmethodName, IntPtr jargs){
      // ...
      IntPtr result;
      using (AndroidJavaObject androidJavaObject = proxy.Invoke(AndroidJNI.GetStringUTFChars(jmethodName), array)){
        if (androidJavaObject == null) {
          result = IntPtr.Zero;
        } else {
          result = AndroidJNI.NewLocalRef(androidJavaObject.GetRawObject());
        }
      }
      return result;
    }
    

    最终通过proxy.Invoke调用自己的目标方法。

    AndroidManifest.xml


    https://github.com/yhd4711499/unity_android_plugin/blob/master/AndroidManifest.xml
    其中的@string/app_name@drawable/app_icon为Unity项目中包含的资源,与Android项目中的资源无关。
    如果同时还用到了自己的Activity,需要将其中的<activity android:name=""/>
    改成自己的。

    转载请注明原作者:haodongyuan@tencent.com

    相关文章

      网友评论

        本文标题:Android Unity Plugin 开发指南(转载)

        本文链接:https://www.haomeiwen.com/subject/aemurxtx.html