Hi, guys
I’m trying to write a android screen-saver with Android/DreamService.
I tried to build my own DreamService extension from codes of JME/AndroidHarness .
below is my implementation of Android/DreamService:
public class MyService extends DreamService implements TouchListener, SystemListener {
protected final static Logger logger = Logger.getLogger(MyService.class.getName());
/**
* The application class to start
*/
protected String appClass = "AnnoCore";
/**
* The jme3 application object
*/
protected LegacyApplication app = null;
/**
* Sets the desired RGB size for the surfaceview. 16 = RGB565, 24 = RGB888.
* (default = 24)
*/
protected int eglBitsPerPixel = 24;
/**
* Sets the desired number of Alpha bits for the surfaceview. This affects
* how the surfaceview is able to display Android views that are located
* under the surfaceview jME uses to render the scenegraph.
* 0 = Opaque surfaceview background (fastest)
* 1->7 = Transparent surfaceview background
* 8 or higher = Translucent surfaceview background
* (default = 0)
*/
protected int eglAlphaBits = 0;
/**
* The number of depth bits specifies the precision of the depth buffer.
* (default = 16)
*/
protected int eglDepthBits = 16;
/**
* Sets the number of samples to use for multisampling.<br>
* Leave 0 (default) to disable multisampling.<br>
* Set to 2 or 4 to enable multisampling.
*/
protected int eglSamples = 0;
/**
* Set the number of stencil bits.
* (default = 0)
*/
protected int eglStencilBits = 0;
/**
* Set the desired frame rate. If frameRate higher than 0, the application
* will be capped at the desired frame rate.
* (default = -1, no frame rate cap)
*/
protected int frameRate = -1;
/**
* Sets the type of Audio Renderer to be used.
* <p>
* Android MediaPlayer / SoundPool can be used on all
* supported Android platform versions (2.2+)<br>
* OpenAL Soft uses an OpenSL backend and is only supported on Android
* versions 2.3+.
* <p>
* Only use ANDROID_ static strings found in AppSettings
*
*/
protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT;
/**
* If true Android Sensors are used as simulated Joysticks. Users can use the
* Android sensor feedback through the RawInputListener or by registering
* JoyAxisTriggers.
*/
protected boolean joystickEventsEnabled = false;
/**
* If true KeyEvents are generated from TouchEvents
*/
protected boolean keyEventsEnabled = true;
/**
* If true MouseEvents are generated from TouchEvents
*/
protected boolean mouseEventsEnabled = true;
/**
* Flip X axis
*/
protected boolean mouseEventsInvertX = false;
/**
* Flip Y axis
*/
protected boolean mouseEventsInvertY = false;
/**
* if true finish this activity when the jme app is stopped
*/
protected boolean finishOnAppStop = true;
/**
* set to false if you don't want the harness to handle the exit hook
*/
protected boolean handleExitHook = true;
/**
* Title of the exit dialog, default is "Do you want to exit?"
*/
protected String exitDialogTitle = "Do you want to exit?";
/**
* Message of the exit dialog, default is "Use your home key to bring this
* app into the background or exit to terminate it."
*/
protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
/**
* Set the screen window mode. If screenFullSize is true, then the
* notification bar and title bar are removed and the screen covers the
* entire display. If screenFullSize is false, then the notification bar
* remains visible if screenShowTitle is true while screenFullScreen is
* false, then the title bar is also displayed under the notification bar.
*/
protected boolean screenFullScreen = true;
/**
* if screenShowTitle is true while screenFullScreen is false, then the
* title bar is also displayed under the notification bar
*/
protected boolean screenShowTitle = true;
/**
* Splash Screen picture Resource ID. If a Splash Screen is desired, set
* splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
* splashPicID = 0, then no splash screen will be displayed.
*/
protected int splashPicID = 0;
protected OGLESContext ctx;
protected GLSurfaceView view = null;
protected boolean isGLThreadPaused = true;
protected ImageView splashImageView = null;
protected FrameLayout frameLayout = null;
final private String ESCAPE_EVENT = "TouchEscape";
private boolean firstDrawFrame = true;
private boolean inConfigChange = false;
@Override
@SuppressWarnings("unchecked")
public void onAttachedToWindow() {
initializeLogHandler();
logger.fine("onCreate");
super.onCreate();
//requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Discover the screen resolution
//TODO try to find a better way to get a hand on the resolution
WindowManager wind = this.getWindowManager();
Display disp = wind.getDefaultDisplay();
Log.d("Daydream", "Resolution from Window, width:" + disp.getWidth() + ", height: " + disp.getHeight());
// Create Settings
logger.log(Level.FINE, "Creating settings");
AppSettings settings = new AppSettings(true);
settings.setEmulateMouse(mouseEventsEnabled);
settings.setEmulateMouseFlipAxis(mouseEventsInvertX, mouseEventsInvertY);
settings.setUseJoysticks(false);
settings.setEmulateKeyboard(false);
settings.setBitsPerPixel(eglBitsPerPixel);
settings.setAlphaBits(eglAlphaBits);
settings.setDepthBits(eglDepthBits);
settings.setSamples(eglSamples);
settings.setStencilBits(eglStencilBits);
settings.setResolution(disp.getWidth(), disp.getHeight());
settings.setAudioRenderer(audioRendererType);
settings.setFrameRate(frameRate);
// Create application instance
try {
if (app == null) {
Class clazz = Class.forName(appClass);
app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance();
}
app.setSettings(settings);
app.start();
} catch (Exception ex) {
handleError("Class " + appClass + " init failed", ex);
setContentView(new TextView(this));
}
ctx = (OGLESContext) app.getContext();
if(ctx==null){
logger.severe("NULLLLL~~~~~~~~~~");
}
view = ctx.createView(this);
// store the glSurfaceView in JmeAndroidSystem for future use
JmeAndroidSystem.setView(view);
// AndroidHarness wraps the app as a SystemListener.
ctx.setSystemListener(this);
layoutDisplay();
}
@Override
public void onDreamingStarted(){
super.onDreamingStarted();
gainFocus();
}
@Override
public void onDreamingStopped(){
super.onDreamingStopped();
loseFocus();
}
@Override
public void onDetachedFromWindow(){
logger.fine("onDestroy");
if (app != null) {
app.stop(!isGLThreadPaused);
}
setContentView(new TextView(this));
ctx = null;
app = null;
view = null;
JmeAndroidSystem.setView(null);
super.onDetachedFromWindow();
}
public Application getJmeApplication() {
return app;
}
/**
* Called when an error has occurred. By default, will show an error message
* to the user and print the exception/error to the log.
*/
@Override
public void handleError(final String errorMsg, final Throwable t) {
String stackTrace = "";
String title = "Error";
if (t != null) {
// Convert exception to string
StringWriter sw = new StringWriter(100);
t.printStackTrace(new PrintWriter(sw));
stackTrace = sw.toString();
title = t.toString();
}
final String finalTitle = title;
final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
+ "\n" + stackTrace;
logger.log(Level.SEVERE, finalMsg);
}
/**
* Called by the android alert dialog, terminate the activity and OpenGL
* rendering
*
* @param dialog ignored
* @param whichButton the button index
*/
/**
* Gets called by the InputManager on all touch/drag/scale events
*/
@Override
public void onTouch(String name, TouchEvent evt, float tpf) {
if (name.equals(ESCAPE_EVENT)) {
switch (evt.getType()) {
case KEY_UP:
if (app != null) {
app.stop(true);
}
app = null;
this.finish();
break;
default:
break;
}
}
}
public void layoutDisplay() {
logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID);
if (view == null) {
logger.log(Level.FINE, "view is null!");
}
logger.log(Level.FINE, "Splash Screen Skipped.");
setContentView(view);
}
/**
* Removes the standard Android log handler due to an issue with not logging
* entries lower than INFO level and adds a handler that produces
* JME formatted log messages.
*/
protected void initializeLogHandler() {
Logger log = LogManager.getLogManager().getLogger("");
for (Handler handler : log.getHandlers()) {
if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) {
Log.v("MyService", "Removing Handler class: " + handler.getClass().getName());
}
log.removeHandler(handler);
}
Handler handler = new AndroidLogHandler();
log.addHandler(handler);
handler.setLevel(Level.ALL);
}
@Override
public void initialize() {
app.initialize();
if (handleExitHook) {
// remove existing mapping from SimpleApplication that stops the app
// when the esc key is pressed (esc key = android back key) so that
// AndroidHarness can produce the exit app dialog box.
if (app.getInputManager().hasMapping(SimpleApplication.INPUT_MAPPING_EXIT)) {
app.getInputManager().deleteMapping(SimpleApplication.INPUT_MAPPING_EXIT);
}
app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK));
app.getInputManager().addListener(this, new String[]{ESCAPE_EVENT});
}
}
@Override
public void reshape(int width, int height) {
app.reshape(width, height);
}
@Override
public void rescale(float x, float y) {
app.rescale(x, y);
}
@Override
public void update() {
app.update();
// call to remove the splash screen, if present.
// call after app.update() to make sure no gap between
// splash screen going away and app display being shown.
}
@Override
public void requestClose(boolean esc) {
app.requestClose(esc);
}
@Override
public void destroy() {
onDetachedFromWindow();
}
@Override
public void gainFocus() {
logger.fine("gainFocus");
if (view != null) {
view.onResume();
}
if (app != null) {
//resume the audio
AudioRenderer audioRenderer = app.getAudioRenderer();
if (audioRenderer != null) {
audioRenderer.resumeAll();
}
//resume the sensors (aka joysticks)
if (app.getContext() != null) {
JoyInput joyInput = app.getContext().getJoyInput();
if (joyInput != null) {
if (joyInput instanceof AndroidSensorJoyInput) {
AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
androidJoyInput.resumeSensors();
}
}
}
}
isGLThreadPaused = false;
if (app != null) {
app.gainFocus();
}
}
@Override
public void loseFocus() {
logger.fine("loseFocus");
if (app != null) {
app.loseFocus();
}
if (view != null) {
view.onPause();
}
if (app != null) {
//pause the audio
AudioRenderer audioRenderer = app.getAudioRenderer();
if (audioRenderer != null) {
audioRenderer.pauseAll();
}
//pause the sensors (aka joysticks)
if (app.getContext() != null) {
JoyInput joyInput = app.getContext().getJoyInput();
if (joyInput != null) {
if (joyInput instanceof AndroidSensorJoyInput) {
AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
androidJoyInput.pauseSensors();
}
}
}
}
isGLThreadPaused = true;
}
public MyService() {
}
}
and I got error messages shown below:
SEVERE Failed to create JmeSystem delegate:
{0}
java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance(Native Method)
at com.jme3.system.JmeSystem.tryLoadDelegate(JmeSystem.java:238)
at com.jme3.system.JmeSystem.checkDelegate(JmeSystem.java:250)
at com.jme3.system.JmeSystem.showSettingsDialog(JmeSystem.java:226)
at com.jme3.app.SimpleApplication.start(SimpleApplication.java:120)
at com.cottonwoodanalytics.anno.screansaver.MyService.onAttachedToWindow(MyService.java:221)
at com.android.internal.policy.PhoneWindow$DecorView.onAttachedToWindow(PhoneWindow.java:3784)
at android.view.View.dispatchAttachedToWindow(View.java:15810)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3139)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1782)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1488)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7451)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
at android.view.Choreographer.doCallbacks(Choreographer.java:695)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7225)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: java.lang.NoClassDefFoundError: com.jme3.system.JmeSystemDelegate$$ExternalSyntheticLambda0
at com.jme3.system.JmeSystemDelegate.<init>(JmeSystemDelegate.java:65)
at com.jme3.system.android.JmeAndroidSystem.<init>(JmeAndroidSystem.java:41)
at java.lang.reflect.Constructor.newInstance(Native Method)
at com.jme3.system.JmeSystem.tryLoadDelegate(JmeSystem.java:238)
at com.jme3.system.JmeSystem.checkDelegate(JmeSystem.java:250)
at com.jme3.system.JmeSystem.showSettingsDialog(JmeSystem.java:226)
at com.jme3.app.SimpleApplication.start(SimpleApplication.java:120)
at com.cottonwoodanalytics.anno.screansaver.MyService.onAttachedToWindow(MyService.java:221)
at com.android.internal.policy.PhoneWindow$DecorView.onAttachedToWindow(PhoneWindow.java:3784)
at android.view.View.dispatchAttachedToWindow(View.java:15810)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3139)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1782)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1488)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7451)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
at android.view.Choreographer.doCallbacks(Choreographer.java:695)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7225)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
SEVERE Class com.cottonwoodanalytics.anno.core.AnnoCore init failed
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean com.jme3.system.JmeSystemDelegate.showSettingsDialog(com.jme3.system.AppSettings, boolean)' on a null object reference
at com.jme3.system.JmeSystem.showSettingsDialog(JmeSystem.java:227)
at com.jme3.app.SimpleApplication.start(SimpleApplication.java:120)
at com.cottonwoodanalytics.anno.screansaver.MyService.onAttachedToWindow(MyService.java:221)
at com.android.internal.policy.PhoneWindow$DecorView.onAttachedToWindow(PhoneWindow.java:3784)
at android.view.View.dispatchAttachedToWindow(View.java:15810)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3139)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1782)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1488)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7451)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
at android.view.Choreographer.doCallbacks(Choreographer.java:695)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7225)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
SEVERE NULLLLL~~~~~~~~~~
Shutting down VM
FATAL EXCEPTION: main
Process: com.cottonwoodanalytics.anno.screansaver, PID: 13864
java.lang.NullPointerException: Attempt to invoke virtual method 'android.opengl.GLSurfaceView com.jme3.system.android.OGLESContext.createView(android.content.Context)' on a null object reference
at com.cottonwoodanalytics.anno.screansaver.MyService.onAttachedToWindow(MyService.java:232)
at com.android.internal.policy.PhoneWindow$DecorView.onAttachedToWindow(PhoneWindow.java:3784)
at android.view.View.dispatchAttachedToWindow(View.java:15810)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3139)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1782)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1488)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7451)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
at android.view.Choreographer.doCallbacks(Choreographer.java:695)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7225)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
as shown in android documentation, I move the init code from start() to onAttachedToWindow()
Did I just messed up with the lifecycle ? or other reasons?