Ошибки управления жизненным циклом UnityPlayer в нативном приложении для Android
Я работаю над приложением для Android, которое должно загрузить экземпляр UnityPlayer в activiy, используя в качестве руководства код из следующего сообщения на форуме:
http://forum.unity3d.com/threads/98315-Using-Unity-Android-In-a-Sub-View.
Первоначально приложение правильно отображает UnityPlayer внутри действия, называемого "UnityActivity.java".
Проблема начинается, когда пользователь переходит обратно к MainActivity (либо нажимая кнопку возврата оборудования, либо нажимая кнопку возврата ActionBar), а затем пытается повторно открыть 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>
Я был бы благодарен за любые советы и решения, указывающие мне в правильном направлении.
4 ответа
Хорошо, сначала простые вещи
W/libc(21095): pthread_create sched_setscheduler call failed: Operation not permitted
С этим ничего не поделаешь. Вы даже получаете это при компиляции непосредственно из Unity для Android, так что это проблема внутри движка.
Базовая настройка
Руководство, на которое вы ссылаетесь, устарело. Вам больше не нужно копировать файлы из разных мест, чтобы создать простой проект Android.
- Создайте проект Android, установив
Build Settings -> Android -> Google Android project
- Теперь у вас есть готовый пакет для импорта в Eclipse или Android Studio
- Скомпилируйте и разверните
Использование UnityPlayer в субактивности
Класс UnityPlayerNativeActivity
в вашем новом проекте Android показывает, как настроить 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
вместо этого, без каких-либо проблем, насколько я могу судить. По крайней мере, это сработало во время моих экспериментов.
Самая важная часть, которую вам не хватает - это звонок mUnityPlayer.quit()
в течение onDestroy()
, Попытка создать новый экземпляр UnityPlayer
в то время как старый все еще работает, это приведет к авариям, зависаниям и бесконечным страданиям.
Неожиданное поведение mUnityPlayer.quit()
Исправляя это, вы можете быть удивлены тем, что теперь все ваше приложение просто закрывается, когда вы возвращаетесь из UnityActivity
, mUnityPlayer.quit()
убьет процесс, внутри которого он работает. Ни один метод не будет выполнен после вызова mUnityPlayer.quit()
даже не onDestroy()
метод закончится.
Путь к победе - начать UnityActivity
как новый процесс, добавив параметр android:process=":UnityKillsMe
к вашей деятельности внутри вашего AndroidManifest.xml
,
В вашем случае это будет выглядеть так
<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, поэтому вам нужно будет экспортировать их в отдельную папку и связать с частью Unity из вашего проекта Android.
В соответствии с вышеупомянутой документацией вы можете интегрировать скомпилированные классы Android и AndroidManifest.xml
как плагины в Unity.
Полученные в результате файлы.class должны быть сжаты в файл.jar и помещены в папку Assets->Plugins->Android. Поскольку манифест определяет, какое действие следует запустить, необходимо также создать новый AndroidManifest.xml. Файл AndroidManifest.xml также должен быть помещен в папку "Активы-> Плагины-> Android".
Удачи!
Вопрос два года назад, но я все еще изо всех сил пытался найти подробное руководство по нему.
Основная идея заключается в том, чтобы взять проект Unity и использовать его в качестве библиотеки в родном приложении для Android.
Я надеюсь, что это руководство поможет кому-то.
Ну, у меня нет четкого ответа на это, но я нашел пару интересных psots
1- Предупреждение pthread_create на андроиде говорит о W/libc(21095): pthread_create вызов sched_setscheduler не выполнен: операция не разрешена, пост начинается с. После вызова функции pthread_create я получаю следующее сообщение: я цитирую сообщение:
Эта ошибка означает, что процесс, пытающийся создать поток, не имеет соответствующих привилегий для установки приоритетов планирования, как указано.
и сообщение, помеченное как ответ, содержит полезную информацию. посмотрите, но в вашем коде вы НЕ вызываете pthread_create() - [1]
2- Значение сообщений хореографа в Logcat W / Choreographer (20963): уже есть ожидающее событие vsync. Там должно быть только один за один раз, я цитирую
Choreographer позволяет приложениям подключаться к vsync и правильно рассчитывать время, чтобы повысить производительность.
а также
Да, я. Я понимаю, что Choreographer, вероятно, является компонентом, который обрабатывает анимацию, и когда он не получает достаточно циклов ЦП, он пропускает некоторые кадры и выводит это сообщение отладки, так что это связано с анимацией в пользовательском интерфейсе ---[2]
так? на основе [1] и [2]: проблема с анимацией, генерируемой Unity, и она не находится под вашим контролем,
Мое собственное заключение и мнение таково: это ошибка в коде Unity, которая должна быть исправлена Unity? может быть?
извините, если не помогаю, но я пытался. я никогда не работал с Unity, но я пытался получить заключение из других постов.
удачи
Вы можете увидеть эту ссылку
Интегрируйте Unity3d view в активность Android
открытый класс UnityPlayerNativeActivity расширяет 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);
}
}