Unity3D Native 插件开发(5)— Android 生命周期处理

发表于2016-05-17
评论1 5.7k浏览
这是一个系列教程,主要内容是如何开发 Unity 插件,在理解这个教程之前需要读者有一定的 Unity、iOS、Android 开发知识:
       

Unity3D Native 插件开发(1)— 原理解析


  这篇文章主要介绍在 Unity 中如何处理 Android 的生命周期相关代码

Android生命周期简介
  学过 Android 开发的同学一定过这张图片:


  这张图很好的描述了Android的一个Activity的生命过程,对于这个不做过多描述

Unity 生命周期
  Unity实际上是“单”Activity的 Android 程序(如果不考虑我们接入的三方插件的话),相比 Android,Unity 的生命周期是基于脚本的,并且多了Unity的本身一些特性(刷新,“Update”)
  游戏是基于帧渲染的“动画”,在Unity中,每一帧都会刷新一次,调用一次Update()函数


  由于是关于 Native 插件开发的介绍,这里对 Unity 生命周期不做过多介绍
  可以看到,Unity 的生命周期和 Android 的生命周期是有着明显的差异的,差异就必然带来问题
  完整的生命周期可以参考文档:http://wiki.unity3d.com/index.php?title=Life_cycle

Unity 入口 Activity
  Google 虽然建议用 Java 语言开发 Android 程序,但是他们还是提供了 NDK,使开发者能够用 C++ 来开发 Android 应用,而实现这个的基础就是 NativeActivity

a、什么是 NativeActivity ?
  在 Google 开发文档中有这么一句话:
Convenience for implementing anactivity that will be implemented purely in native code. That is, a game (orgame-like thing). There is no need to derive from this class; you can simplydeclare it in your manifest, and use the NDK APIs from there.
  也就是说这个 NativeActivity 可以直接调用 NDK 编译的 Native 代码(如:C++),而不必写 Java 代码。NativeActivity 是实现了与 Native 代码交互逻辑的 Android Activity 实例,应用甚至可以直接把 android.app.NativeActivity 作为 App 的入口 Activity

b、UnityPlayerActivity 与 UnityPlayerNativeActivity
  这两个类是 Unity 编译 Android 程序时的入口 Activity,区别等价于 Activity 和 NativeActivity 的区别
  本来按类名来看,UnityPlayerActivity 应该继承与 Activity,UnityPlayerNativeActivity 应该继承与 NativeActivity,一般 Unity 是用 UnityPlayerActivity 来作为入口 Activity 的
  但是出于某种原因,Unity 在 4.5 - 4.6 这几个版本中,将入口 Activity 修改成了 UnityPlayerNativeActivity,并且继承关系从
public class UnityPlayerActivity extends Activity
...
public class UnityPlayerNativeActivity extends NativeActivity
变成
public class UnityPlayerNativeActivity extends NativeActivity
...
/**
* @deprecated Use UnityPlayerNativeActivity instead.
*/
public class UnityPlayerActivity extends UnityPlayerNativeActivity
  还把 UnityPlayerActivity 标记为过时函数 !!!
  这还没完,Unity在 5.x 版本又将这一逻辑还原回去了 !!!

c、 NativeActivity 对游戏的影响
  如果我们的应用最终使用的是 NativeActivity 或其子类作为入口 Activity,那么在配置项中,我们一般会增加这样一个配置:
这个配置是用来解决 Unity 中原生 Android 控件无法接收事件(如点击)的问题。原因是 NativeActivity 会把事件传递到 Native 代码(如C++),Unity 通过这项配置使得 Android 原生控件也能接收到事件
  还是要说但是,这样就会造成用户事件(如点击)会在 Unity 和 Android 原生层同时生效 !!!

生命周期相关代码调用
  在众多的 Android SDK 开发指引中,最常见的就是在 onCreate、onPause、onResume、onActivityResult 等生命周期中添加函数调用
  那么,在 Unity 中如何处理这些函数调用呢 ?
  如果一些代码调用不需要生命周期参数,可以在对应的 Unity 生命周期进行调用
  如 Android 中的 onCreate 可以与 Unity 中的 Start 对应
  如果代码需要生命周期参数,或者类似 onActivityResult、onNewIntent 这类 Android 特有的回调,就需要特殊的逻辑进行处理。解决方案有多种,在这里我们讨论最常见的方式: 抢占 Unity 入口 Activity

a、修改 AndroidManifest.xml 配置
  将 Android 的入口 Activity 修改为我们自己的 Activity,需要修改工程 AndroidManifest.xml 文件
  好在 Unity 提供了 AndroidManifest.xml 模板文件,修改起来也十分简单:将模板文件拷贝一份到
Assets/Plugins/Android/AndroidManifest.xml
  并修改其中的类容,Unity 就会用这份文件作为 Android 编译时的 AndroidManifest 模板文件
  先找到模板文件,Mac OS 在 Unity 安装目录的下
./PlaybackEngines/AndroidPlayer/Apk/AndroidManifest.xml
Windows 在安装目录下:
EditorDataPlaybackEnginesandroidplayerAndroidManifest.xml
  Unity提供的标准模板(Unity 5.x)内容如下:
"http://schemas.android.com/apk/res/android" package="com.unity3d.player" android:installlocation="preferExternal" android:versioncode="1" android:versionname="1.0">
   "true" android:normalscreens="true" android:largescreens="true" android:xlargescreens="true" android:anydensity="true">  
   "@style/UnityThemeSelector" android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true">
       "com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name">          
               "android.intent.action.MAIN">
               "android.intent.category.LAUNCHER">          
           "unityplayer.UnityActivity" android:value="true">
  如果我们想要把入口Activity修改为“com.tencent.imsdk.UnityPlayerActivity”,值需要修改 application 下标记为 LAUNCHER 的 activity 名称(name)即可,即把:
   ...
   
   ...
修改为:
   ...
   
   ...
其他字段(如包名等)不用修改,Unity在编译的时候会自动版本我们修改

b、编译自己的 Activity
  在编写自定义入口 Activity 的时候需要格外小心,Unity 的一些代码调用不能随意改动
  为了保险起见,我们可以利用 Unity 导出一个空 Android 工程,然后再根据这个工程进行改造。编译时把“Bundle Identifier”设置为游戏的包名(如:com.tencent.imsdk),在Android工程时勾选“Google Android Project”,就可以导出一个 Android 空工程,
  我们新建一个 Android 的库,包名命名为游戏的包名(如:com.tencent.imsdk),将 Unity 导出的空工程中的 Java 文件拷贝到库工程中,一般包括如下三个文件:
UnityPlayerActivity.java
UnityPlayerNativeActivity.java
UnityPlayerProxyActivity.java
  我们根据自己的入口 Activity 类型,在 UnityPlayerActivity 或 UnityPlayerNativeActivity 中添加自己的代码,编译成 jar 包,并拷贝到 Assets/Plugins/Android/libs 目录下
  完成这些步骤,编译 Unity 工程,入口 Activity 就被替换成我们自己的 Activity 了

依赖生命周期的函数调用
  在 Android 中,很多函数是依赖 Android Looper 消息机制的,如绝大部分的 UI 函数,包括上一节中 Android 代码中用到的 AlertDialog,如果不在具有 Looper 的线程中运行,会有如下异常抛出 :
AndroidJavaException: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
  我们为了解决这个问题,有两种解决方案:

a、runOnUiThread
  在 Android 中,Activity 提供了 runOnUiThread,可以在 UiThread 中运行这类函数调用,我们可以在写 Android 代码时就放在 UiThread 中运行,或者 Unity 中可以这样调用:
var activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic("currentActivity");
 activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
 {
     ...
 }));
注意:在 UiThread 中运行也有很多弊端,如果函数比较复杂的话,会造成卡 UI 的情况

b、HandlerThread
HandlerThread 继承与 Thread,构造函数需要传递一个 String 类型的名称
HandlerThread handlerThread = new HandlerThread("some_name");
handlerThread.start();
HandlerThread 带有 Android 的 Looper,可以通过函数调用获取:
Looper looper =  handlerThread.getLooper();
  这样我们就可以获取一个 Handler :
Handler handler =  handler = new Handler(handlerThread.getLooper());
  然后通过 Handler 来执行代码:
handler.post(new Runnable() {
   @Override
   public void run() {
     ...
   }
});
注意:在 Handler 中运行也有问题,如果其中的一个 Runnable “卡”了,将导致后面的 Runnable 都无法执行,那么我们需要管理这个队列,具体做法就不再描述,不是本节重点

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引