在本地安卓应用中管理UnityPlayer生命周期的错误

17

我正在开发一个需要在活动中加载UnityPlayer实例的Android应用程序,使用下面论坛帖子中的代码作为指南:

http://forum.unity3d.com/threads/98315-Using-Unity-Android-In-a-Sub-View

最初,应用程序正确地在名为“UnityActivity.java”的活动中显示UnityPlayer。

当用户通过按硬件返回按钮或单击操作栏返回按钮导航回MainActivity并尝试重新打开UnityActivity时,问题就开始了 - 在这种情况下,显示的是黑屏而不是UnityPlayer。论坛上的用户建议将onPause和onResume生命周期事件转发给UnityPlayer,如下面的代码所示。然而,这样做会导致以下错误出现并导致应用程序崩溃。

第一次导航到UnityActivity时记录如下:

W/libc(21095): pthread_create sched_setscheduler call failed: Operation not permitted

点击返回按钮时记录以下错误:

W/Choreographer(20963): Already have a pending vsync event. There should only be one at a time.

第二次导航到UnityActivity时记录以下错误:

A/libc(21095): Fatal signal 11 (SIGSEGV) at 0x00000000 (code=1), thread 21176 (Thread-5073)

...此时我被踢出了应用程序。

代码

这是主要活动MainActivity.java的摘录:

public void startUnityActivity(View view) {
        Intent intent = new Intent(this, UnityActivity.class);
    startActivity(intent);
}

这是Unity活动的一部分 UnityActivity.java

public class UnityActivity extends ActionBarActivity {
    UnityPlayer m_UnityPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

         setContentView(R.layout.activity_unity);

        m_UnityPlayer = new UnityPlayer(this);
        int glesMode = m_UnityPlayer.getSettings().getInt("gles_mode", 1);
        m_UnityPlayer.init(glesMode, false);

        FrameLayout layout = (FrameLayout) findViewById(R.id.unityView);
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
        layout.addView(m_UnityPlayer, 0, lp);
        m_UnityPlayer.windowFocusChanged(true);
        m_UnityPlayer.resume();
    }
    @Override
    public void onWindowFocusChanged(boolean hasFocus)
    {
        super.onWindowFocusChanged(hasFocus);
        m_UnityPlayer.windowFocusChanged(hasFocus);
    }
    @Override
    public void onPause() {
         super.onPause();  
         m_UnityPlayer.pause();
    }
    @Override
    public void onResume() {
        super.onResume(); 
        m_UnityPlayer.resume();
    }

以下是在清单文件../AndroidManifest.xml中描述活动的方式:

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name="com.package.example.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity 
        android:name="com.package.example.UnityActivity" 
        android:label="@string/title_activity_unity" 
        android:screenOrientation="portrait" 
        android:launchMode="singleTask" 
        android:parentActivityName="com.package.example.MainActivity"
        android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale">
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
    </activity>
</application>

这是UnityActivity的布局定义方式:../res/layout/activity_unity.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.package.example.UnityActivity"
tools:ignore="MergeRootFrame" >
    <FrameLayout
    android:id="@+id/unityView"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    </FrameLayout>
</FrameLayout>

我会感激任何能够指导我朝着正确方向前进的建议和解决方案。


一些反馈会很好。你还有什么问题吗? - Stefan Hoffmann
在Android Studio中如何做到这一点?你有什么想法吗? - Praneeth
4个回答

28

好的,先从简单的开始

W/libc(21095): pthread_create sched_setscheduler call failed: Operation not permitted

这是无法解决的问题。即使你直接从Unity编译Android,也会出现这个问题,因为这是引擎内部的问题。

基本设置

你链接的指南已经过时了。你不再需要从各个位置复制文件来创建一个简单的Android项目。

  1. 通过设置 Build Settings -> Android -> Google Android project 来创建一个Android项目
  2. 现在你已经有了一个完整的包可以导入到Eclipse或Android Studio中
  3. 编译和部署

在子活动中使用UnityPlayer

新Android项目中的 UnityPlayerNativeActivity 类向你展示了如何设置 UnityPlayer 以及需要转发哪些事件。以下是Unity 4.3.4所使用的版本:

package de.leosori.NativeAndroid;

import com.unity3d.player.*;
import android.app.NativeActivity;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

public class UnityPlayerNativeActivity extends NativeActivity
{
    protected UnityPlayer mUnityPlayer;     // don't change the name of this variable; referenced from native code

    // UnityPlayer.init() should be called before attaching the view to a layout - it will load the native code.
    // UnityPlayer.quit() should be the last thing called - it will unload the native code.
    protected void onCreate (Bundle savedInstanceState)
    {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);

        getWindow().takeSurface(null);
        setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
        getWindow().setFormat(PixelFormat.RGB_565);

        mUnityPlayer = new UnityPlayer(this);
        if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true))
            getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN,
                                   WindowManager.LayoutParams.FLAG_FULLSCREEN);

        int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
        boolean trueColor8888 = false;
        mUnityPlayer.init(glesMode, trueColor8888);

        View playerView = mUnityPlayer.getView();
        setContentView(playerView);
        playerView.requestFocus();
    }
    protected void onDestroy ()
    {
        mUnityPlayer.quit();
        super.onDestroy();
    }

    // onPause()/onResume() must be sent to UnityPlayer to enable pause and resource recreation on resume.
    protected void onPause()
    {
        super.onPause();
        mUnityPlayer.pause();
    }
    protected void onResume()
    {
        super.onResume();
        mUnityPlayer.resume();
    }
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        mUnityPlayer.configurationChanged(newConfig);
    }
    public void onWindowFocusChanged(boolean hasFocus)
    {
        super.onWindowFocusChanged(hasFocus);
        mUnityPlayer.windowFocusChanged(hasFocus);
    }
    public boolean dispatchKeyEvent(KeyEvent event)
    {
        if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
            return mUnityPlayer.onKeyMultiple(event.getKeyCode(), event.getRepeatCount(), event);
        return super.dispatchKeyEvent(event);
    }
}

尽管UnityPlayerNativeActivity扩展了NativeActivity,但根据我的实验,您仍然可以从ActionBarActivity继承而不会遇到任何问题。你正在错过的最重要的部分是在onDestroy()期间调用mUnityPlayer.quit()。试图在旧实例仍在运行时创建UnityPlayer的新实例将导致崩溃、挂起活动和无尽的痛苦。
修复后,您可能会惊讶地发现,当您从UnityActivity返回时,整个应用程序都会关闭。mUnityPlayer.quit()将杀死其正在其中运行的进程。在调用mUnityPlayer.quit()之后,没有一个方法会执行,甚至onDestroy()方法也不会完成。
通向胜利的道路是通过在AndroidManifest.xml中将参数android:process=":UnityKillsMe添加到您的活动中作为一个新的进程来启动UnityActivity
在您的情况下,它看起来像这样:
<activity 
    android:name="com.package.example.UnityActivity" 
    android:label="@string/title_activity_unity" 
    android:screenOrientation="portrait" 
    android:launchMode="singleTask" 
    android:process=":UnityKillsMe"
    android:parentActivityName="com.package.example.MainActivity"
    android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale">
    <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
    <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />
</activity>

我对参数 unityplayer.ForwardNativeEventsToDalvik 不确定... 最初创建的项目将其设置为 false, 而官方(已过时)文档提到:

由于触摸/运动事件在本地代码中处理,因此 Java 视图通常不会看到这些事件。然而,在 Unity 中有一种转发机制,允许事件传播到 DalvikVM。

在我的小示例项目中,我没有看到任何区别。

前进的道路

您需要找到一种将您的开发与 Unity 与 Android 项目相结合的工作流程,或者反之亦然。再次使用 Unity 导出将与您在 Android 项目中所做的更改冲突,因此您需要导出到一个单独的文件夹并从 Android 项目中链接到 Unity 部分。

根据上述文档,您可以将编译后的 Android 类和 AndroidManifest.xml 作为插件集成到 Unity 中。

生成的 .class 文件应压缩成 .jar 文件,并放置在 Assets->Plugins->Android 文件夹中。由于清单规定了要启动的活动,因此还需要创建一个新的 AndroidManifest.xml。AndroidManifest.xml 文件也应放置在 Assets->Plugins->Android 文件夹中。

祝你好运!


太棒了,Stefan!你的回答让我的Android项目正常工作了!谢谢你一百万次!!!只是好奇...你是从哪里找到:UnityKillsMe进程名称的?我似乎找不到任何合适的文档。 - gts101
@gts101 :) 你可以随意命名进程。正如你在链接的Android文档中所读到的,指定进程名称将导致为活动创建一个新进程,这是唯一重要的事情。Unity将终止此进程,因此需要指定名称。如果没有新进程,Unity将简单地终止整个Android应用程序。 - Stefan Hoffmann
谢谢 Stefan,非常感谢! - gts101
奇怪,我不需要添加“android:process”来防止我的主活动被杀死。我在我的onBackPressed函数中添加了这些代码 mUnityPlayer.quit(); finish(); - Ethan Chen
这篇帖子对我的开发工作来说是一个非常重要的转折点。太棒了! - Andro Selva
显示剩余2条评论

7

这个问题发生在两年前,但我仍然很难找到一个详细的指南。

因此我写了一篇指南

主要想法是将Unity项目作为库在本地Android应用程序中使用。

希望这篇指南能够帮助到某些人。


我在这行代码中遇到了“无法解析符号'mContext'”的问题:“Intent intent = new Intent(mContext, UnityPlayerActivity.class);”。 - Noopur Dabhi
1
修复了这个问题,使用了“Intent intent = new Intent(getBaseContext(), UnityPlayerActivity.class);”。 - Noopur Dabhi
嗨,David,非常棒的指南!是否还有一种方法可以公开一些类,以允许Android应用程序触发Unity活动中的方法? - iridic
@David,我按照你的指南成功地将Unity游戏集成到本机Android中。但是我想从Unity发送游戏分数到本机Android。我该怎么做?我尝试了这个方法,但它没有起作用。https://stackoverflow.com/questions/53192603/passing-data-from-unity-to-a-native-app-how-to - Sachin Singh

1

嗯,我没有明确的答案,但我找到了几篇有趣的帖子。

1-pthread_create warning on android谈论了W/libc(21095): pthread_create sched_setscheduler call failed: Operation not permitted,该帖子以调用pthread_create函数后,我收到以下消息:开头,我从帖子中引用:

此错误意味着尝试创建线程的进程没有适当的特权来设置指定的调度优先级。

并且标记为答案的帖子也有一些好的信息。在那里看看,但是在您的代码中没有调用pthread_create() -- [1]

2-Meaning of Choreographer messages in LogcatW/Choreographer(20963): Already have a pending vsync event. There should only be one at a time,我引用:

Choreographer允许应用程序连接到vsync,并适时进行定时以提高性能。

是的,我明白编舞者可能是处理动画的组件,当它没有足够的CPU周期时,它会跳过一些帧并输出此调试消息,因此这与UI上的动画有关---[2]

那么? 根据[1]和[2]:这是由Unity生成的动画问题,并且不在您的控制范围内,

我的结论和意见是: 这是Unity代码中的一个错误,需要由Unity修复?也许?
抱歉,如果没有帮助,但我已经尝试过了。 我从其他帖子中得出结论,但我从未使用过Unity。

祝你好运


据我所见,你将500分中的522分用于悬赏了。我看到你处于困境中,所以,不,我这样做不是为了奖励,而是因为我希望能够帮助别人 :) - Yazan

0
你可以看到这个链接:

将Unity3d视图集成到Android活动中

以下是代码:

public class UnityPlayerNativeActivity extends NativeActivity { protected UnityPlayer mUnityPlayer; // 不要更改此变量的名称;从本地代码引用

// UnityPlayer.init() should be called before attaching the view to a layout - it will load the native code.
// UnityPlayer.quit() should be the last thing called - it will unload the native code.
protected void onCreate (Bundle savedInstanceState)
{
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);

    getWindow().takeSurface(null);
    setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
    getWindow().setFormat(PixelFormat.RGB_565);

    mUnityPlayer = new UnityPlayer(this);
    if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true))
        getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN,
                               WindowManager.LayoutParams.FLAG_FULLSCREEN);

    int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
    boolean trueColor8888 = false;
    mUnityPlayer.init(glesMode, trueColor8888);

    View playerView = mUnityPlayer.getView();
    setContentView(playerView);
    playerView.requestFocus();
}
protected void onDestroy ()
{
    mUnityPlayer.quit();
    super.onDestroy();
}

// onPause()/onResume() must be sent to UnityPlayer to enable pause and resource recreation on resume.
protected void onPause()
{
    super.onPause();
    mUnityPlayer.pause();
}
protected void onResume()
{
    super.onResume();
    mUnityPlayer.resume();
}
public void onConfigurationChanged(Configuration newConfig)
{
    super.onConfigurationChanged(newConfig);
    mUnityPlayer.configurationChanged(newConfig);
}
public void onWindowFocusChanged(boolean hasFocus)
{
    super.onWindowFocusChanged(hasFocus);
    mUnityPlayer.windowFocusChanged(hasFocus);
}
public boolean dispatchKeyEvent(KeyEvent event)
{
    if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
        return mUnityPlayer.onKeyMultiple(event.getKeyCode(), event.getRepeatCount(), event);
    return super.dispatchKeyEvent(event);
}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接