Revision: 2838
http://sourceforge.net/p/swingme/code/2838
Author: yuranet
Date: 2025-04-03 23:58:33 +0000 (Thu, 03 Apr 2025)
Log Message:
-----------
fix for corect error catching and set thread error handlers
Modified Paths:
--------------
iOSME/src/javax/microedition/lcdui/Canvas.java
iOSME/src/javax/microedition/media/AudioPlayer.java
iOSME/src/javax/microedition/midlet/MIDlet.java
iOSME/src/net/yura/ios/AppController.java
iOSME/src/net/yura/ios/CanvasView.java
iOSME/src/net/yura/ios/ScrollToTopGestureRecognizer.java
iOSME/src/net/yura/ios/SwingMEiOSApplication.java
iOSME/src/net/yura/ios/iOSNotifications.java
iOSME/src/net/yura/ios/iOSUtil.java
Modified: iOSME/src/javax/microedition/lcdui/Canvas.java
===================================================================
--- iOSME/src/javax/microedition/lcdui/Canvas.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/javax/microedition/lcdui/Canvas.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -3,6 +3,7 @@
import net.yura.ios.AppController;
import net.yura.ios.CanvasView;
import net.yura.ios.iOSUtil;
+import net.yura.mobile.logging.Logger;
import java.util.ArrayList;
import java.util.List;
import apple.coregraphics.c.CoreGraphics;
@@ -202,8 +203,18 @@
}
public void fireKeyPressedReleased(int code) {
- keyPressed(code);
- keyReleased(code);
+ try {
+ keyPressed(code);
+ }
+ catch (Throwable th) {
+ Logger.warn("error in keyPressed " + code, th);
+ }
+ try {
+ keyReleased(code);
+ }
+ catch (Throwable th) {
+ Logger.warn("error in keyReleased " +code, th);
+ }
}
protected void keyPressed(int keyCode) {
@@ -325,11 +336,21 @@
protected void showNotify() {}
public final void fireHideNotify() {
- hideNotify();
+ try {
+ hideNotify();
+ }
+ catch (Throwable th) {
+ Logger.warn("error in hideNotify", th);
+ }
}
public final void fireShowNotify() {
- showNotify();
+ try {
+ showNotify();
+ }
+ catch (Throwable th) {
+ Logger.warn("error in showNotify", th);
+ }
}
public void serviceRepaints() {
Modified: iOSME/src/javax/microedition/media/AudioPlayer.java
===================================================================
--- iOSME/src/javax/microedition/media/AudioPlayer.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/javax/microedition/media/AudioPlayer.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -9,6 +9,8 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import apple.avfaudio.AVAudioPlayer;
import apple.avfaudio.AVAudioSession;
import apple.avfaudio.protocol.AVAudioPlayerDelegate;
@@ -22,6 +24,13 @@
public static final AVAudioSession audioSession = AVAudioSession.sharedInstance();
+ /**
+ * we need to keep a reference to all players that are playing or iOS gets confused
+ * when the references of players still playing get collected and crashes
+ * https://stackoverflow.com/a/50530407
+ */
+ private static Set<AudioPlayer> allSounds = ConcurrentHashMap.newKeySet();
+
//AVPlayer player works equally well with local and remote media files
//AVAudioPlayer Apple recommends that you use this class for audio playback unless you are playing audio captured from a network stream
final AVAudioPlayer player;
@@ -28,7 +37,28 @@
final List<PlayerListener> listeners = new ArrayList<>();
private final static double SECONDS_MICROSECOND = 1_000_000D;
-
+/*
+ public static void configureAudioSession() {
+ try {
+ // Set the audio session category and mode.
+ // is "options: [.mixWithOthers]" needed??
+ boolean success = iOSUtil.doTry((err) -> audioSession.setCategoryError(AVFAudio.AVAudioSessionCategoryAmbient(), err));
+ if (!success) {
+ throw new Exception("result false, failed to set Category");
+ }
+ // WARNING! blocking operation, should be done in background thread
+ // active may only be needed for "Playback" Category, docs are not very clear
+ // if we do need to call this, do we need to call it when app comes back from background?
+ //boolean success2 = iOSUtil.doTry((err) -> audioSession.setActiveError(true, err));
+ //if (!success2) {
+ // throw new Exception("result false, failed to set Active");
+ //}
+ }
+ catch (Exception ex) {
+ System.out.println("Failed to set the audio session configuration: " + ex);
+ }
+ }
+*/
public AudioPlayer(InputStream ins, String mimeType) throws IOException, MediaException {
UTType type = UTType.typeWithMIMEType(mimeType);
@@ -71,8 +101,14 @@
*/
private void firePlayerUpdate(String event, Object eventData) {
// TODO on android this is run on UI thread, should we also do that here?
+ // TODO what if the listener calls removePlayerListener, we will get a ConcurrentModificationException
for (PlayerListener listener : listeners) {
- listener.playerUpdate(this, event, eventData);
+ try {
+ listener.playerUpdate(this, event, eventData);
+ }
+ catch (Exception ex) {
+ Logger.getLogger(AudioPlayer.class.getName()).log(Level.WARNING, "error firing PlayerListener " + listener, ex);
+ }
}
}
@@ -129,8 +165,8 @@
@Override
public void prefetch() throws MediaException {
- // not needed
- //player.prepareToPlay();
+ // this is not NEEDED, but in case we want to prefetch, we can give that option
+ player.prepareToPlay();
}
@Override
@@ -161,6 +197,7 @@
boolean success = player.play();
if (success) {
+ allSounds.add(this);
firePlayerUpdate(PlayerListener.STARTED, getMediaTime());
}
else {
@@ -178,6 +215,7 @@
public void close() {
// TODO some places say this needs to be called on iOSUtil.callOnMainThread
player.stop();
+ allSounds.remove(AudioPlayer.this);
firePlayerUpdate(PlayerListener.CLOSED, null);
}
Modified: iOSME/src/javax/microedition/midlet/MIDlet.java
===================================================================
--- iOSME/src/javax/microedition/midlet/MIDlet.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/javax/microedition/midlet/MIDlet.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -3,6 +3,7 @@
import net.yura.ios.iOSNotifications;
import net.yura.ios.WebViewController;
import net.yura.ios.iOSUtil;
+import net.yura.mobile.logging.Logger;
import net.yura.mobile.util.Url;
import java.io.File;
import java.lang.reflect.Method;
@@ -105,11 +106,21 @@
}
public final void doPauseApp() {
- this.pauseApp();
+ try {
+ this.pauseApp();
+ }
+ catch (Throwable ex) {
+ Logger.warn("error in pauseApp");
+ }
}
- public final void doDestroyApp(boolean unconditional) throws MIDletStateChangeException {
- this.destroyApp(unconditional);
+ public final void doDestroyApp() {
+ try {
+ this.destroyApp(true);
+ }
+ catch (Throwable th) {
+ Logger.warn("error in destroyApp(true)", th);
+ }
}
/**
Modified: iOSME/src/net/yura/ios/AppController.java
===================================================================
--- iOSME/src/net/yura/ios/AppController.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/net/yura/ios/AppController.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -1,5 +1,6 @@
package net.yura.ios;
+import net.yura.mobile.logging.Logger;
import org.moe.GCUtil;
import org.moe.natj.general.Pointer;
import org.moe.natj.general.ann.RegisterOnStartup;
@@ -254,8 +255,8 @@
try {
midlet.saveState();
}
- catch (Exception ex) {
- ex.printStackTrace();
+ catch (Throwable ex) {
+ Logger.warn("error in saveState", ex);
}
}
}
Modified: iOSME/src/net/yura/ios/CanvasView.java
===================================================================
--- iOSME/src/net/yura/ios/CanvasView.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/net/yura/ios/CanvasView.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -4,6 +4,7 @@
import net.yura.mobile.gui.KeyEvent;
import net.yura.mobile.gui.components.TextComponent;
import net.yura.mobile.gui.components.Window;
+import net.yura.mobile.logging.Logger;
import org.moe.natj.general.Pointer;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
@@ -114,27 +115,32 @@
private static final int POINTER_RELEASED = 2;
private void fireMultitouchEvent(NSSet<? extends UITouch> touches, int type) {
- int count = (int)touches.count();
- int[] xs = new int[count];
- int[] ys = new int[count];
- int[] types = new int[count];
+ try {
+ int count = (int) touches.count();
+ int[] xs = new int[count];
+ int[] ys = new int[count];
+ int[] types = new int[count];
- int c=0;
- NSEnumerator<? extends UITouch> it = touches.objectEnumerator();
- UITouch touch = it.nextObject();
- while (touch != null) {
- CGPoint point = touch.locationInView(this);
- xs[c] = (int)point.x();
- ys[c] = (int)point.y();
- types[c] = type;
+ int c = 0;
+ NSEnumerator<? extends UITouch> it = touches.objectEnumerator();
+ UITouch touch = it.nextObject();
+ while (touch != null) {
+ CGPoint point = touch.locationInView(this);
+ xs[c] = (int) point.x();
+ ys[c] = (int) point.y();
+ types[c] = type;
- touch = it.nextObject();
- c++;
- }
+ touch = it.nextObject();
+ c++;
+ }
- //System.out.println("fireMultitouchEvent " + Arrays.toString(types) + " " + Arrays.toString(xs) + " " + Arrays.toString(ys));
+ //System.out.println("fireMultitouchEvent " + Arrays.toString(types) + " " + Arrays.toString(xs) + " " + Arrays.toString(ys));
- canvas.multitouchEvent(types, xs, ys);
+ canvas.multitouchEvent(types, xs, ys);
+ }
+ catch (Throwable th) {
+ Logger.warn("error in multitouchEvent", th);
+ }
}
@Override
Modified: iOSME/src/net/yura/ios/ScrollToTopGestureRecognizer.java
===================================================================
--- iOSME/src/net/yura/ios/ScrollToTopGestureRecognizer.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/net/yura/ios/ScrollToTopGestureRecognizer.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -42,7 +42,7 @@
}
} // we HAVE to catch errors here, if we allow them to throw into obj-c code, this will kill the app!
catch (Throwable th) {
- Logger.error("error in scrollViewShouldScrollToTop", th);
+ Logger.warn("error in scrollViewShouldScrollToTop", th);
}
return false;
}
Modified: iOSME/src/net/yura/ios/SwingMEiOSApplication.java
===================================================================
--- iOSME/src/net/yura/ios/SwingMEiOSApplication.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/net/yura/ios/SwingMEiOSApplication.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -1,6 +1,7 @@
package net.yura.ios;
import net.yura.mobile.logging.Logger;
+import org.jetbrains.annotations.NotNull;
import org.moe.GCUtil;
import org.moe.natj.general.Pointer;
import org.moe.natj.general.ann.RegisterOnStartup;
@@ -9,11 +10,11 @@
import javax.microedition.midlet.MIDlet;
import apple.NSObject;
import apple.foundation.NSArray;
-import apple.foundation.NSBundle;
import apple.foundation.NSCoder;
import apple.foundation.NSData;
import apple.foundation.NSDictionary;
import apple.foundation.NSError;
+import apple.foundation.NSException;
import apple.foundation.NSLocale;
import apple.foundation.NSProcessInfo;
import apple.foundation.c.Foundation;
@@ -67,6 +68,34 @@
window = UIWindow.alloc().initWithFrame(screen.bounds());
window.setRootViewController(navigationController);
+ final Foundation.Function_NSGetUncaughtExceptionHandler_ret iOSExceptionHandler = Foundation.NSGetUncaughtExceptionHandler();
+ Foundation.NSSetUncaughtExceptionHandler(new Foundation.Function_NSSetUncaughtExceptionHandler() {
+ @Override
+ public void call_NSSetUncaughtExceptionHandler(@NotNull NSException exception) {
+ Logger.error("iOS native UncaughtException in: " + Thread.currentThread() + " " + exception);
+ if (iOSExceptionHandler != null) {
+ iOSExceptionHandler.call_NSGetUncaughtExceptionHandler_ret(exception);
+ }
+ }
+ });
+
+ /**
+ * on iOS the main thread has a different UncaughtExceptionHandler to the rest
+ * we want to also intercept that and log at 'error' level to trigger immediate crash reporting
+ * @see org.moe.IOSLauncher#main(String[])
+ * @see org.moe.natj.objc.ObjCRuntime#crashAppWhenExceptionUncaught()
+ */
+ final Thread.UncaughtExceptionHandler mainThreadUncaughtExceptionHandler = Thread.currentThread().getUncaughtExceptionHandler();
+ if (mainThreadUncaughtExceptionHandler != null && !(mainThreadUncaughtExceptionHandler instanceof ThreadGroup)) {
+ Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ Logger.error("Exception in main thread \""+thread+"\"", ex);
+ mainThreadUncaughtExceptionHandler.uncaughtException(thread, ex);
+ }
+ });
+ }
+
// will force gc each time the app is put into background
GCUtil.register();
@@ -231,12 +260,7 @@
MIDlet midlet = MIDlet.DEFAULT_MIDLET;
if (midlet != null) {
- try {
- midlet.doPauseApp();
- }
- catch (Exception ex) {
- ex.printStackTrace();
- }
+ midlet.doPauseApp();
}
}
@@ -246,12 +270,7 @@
MIDlet midlet = MIDlet.DEFAULT_MIDLET;
if (midlet != null) {
- try {
- midlet.doDestroyApp(true);
- }
- catch (Exception ex) {
- ex.printStackTrace();
- }
+ midlet.doDestroyApp();
}
}
@@ -267,22 +286,21 @@
@Override
public void applicationDidRegisterForRemoteNotificationsWithDeviceToken(UIApplication application, NSData deviceToken) {
+ try {
+ String apsEnvironment = iOSProvisioning.getApsEnvironment();
+ if (apsEnvironment != null) {
+ System.setProperty("aps-environment", apsEnvironment);
+ }
- String apsEnvironment = iOSProvisioning.getApsEnvironment();
- if (apsEnvironment != null) {
- System.setProperty("aps-environment", apsEnvironment);
- }
+ byte[] bytes = iOSUtil.toBytes(deviceToken);
+ // https://help.pushwoosh.com/hc/en-us/articles/360000364923-What-is-a-Device-token-
+ // says that the token should be lower case
+ String tokenString = IntegralToString.bytesToHexString(bytes, false);
- byte[] bytes = iOSUtil.toBytes(deviceToken);
- // https://help.pushwoosh.com/hc/en-us/articles/360000364923-What-is-a-Device-token-
- // says that the token should be lower case
- String tokenString = IntegralToString.bytesToHexString(bytes, false);
-
- try {
MIDlet.DEFAULT_MIDLET.pushNotificationsToken(tokenString);
}
- catch (Exception ex) {
- ex.printStackTrace();
+ catch (Throwable ex) {
+ Logger.warn("error in pushNotificationsToken", ex);
}
}
Modified: iOSME/src/net/yura/ios/iOSNotifications.java
===================================================================
--- iOSME/src/net/yura/ios/iOSNotifications.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/net/yura/ios/iOSNotifications.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -66,7 +66,12 @@
}
// user clicked on notification, we can clear the app icon badge
- UIApplication.sharedApplication().setApplicationIconBadgeNumber(0);
+ try {
+ UNUserNotificationCenter.currentNotificationCenter().setBadgeCountWithCompletionHandler(0, null);
+ }
+ catch (Throwable th) {
+ UIApplication.sharedApplication().setApplicationIconBadgeNumber(0);
+ }
}
/**
Modified: iOSME/src/net/yura/ios/iOSUtil.java
===================================================================
--- iOSME/src/net/yura/ios/iOSUtil.java 2025-04-03 23:18:13 UTC (rev 2837)
+++ iOSME/src/net/yura/ios/iOSUtil.java 2025-04-03 23:58:33 UTC (rev 2838)
@@ -1,5 +1,6 @@
package net.yura.ios;
+import net.yura.mobile.logging.Logger;
import org.moe.natj.general.ptr.BytePtr;
import org.moe.natj.general.ptr.Ptr;
import org.moe.natj.general.ptr.impl.PtrFactory;
@@ -7,10 +8,12 @@
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
+import java.util.function.Function;
import apple.foundation.NSArray;
import apple.foundation.NSBundle;
import apple.foundation.NSData;
import apple.foundation.NSDictionary;
+import apple.foundation.NSError;
import apple.foundation.NSLocale;
import apple.foundation.NSMutableDictionary;
import apple.foundation.NSOperationQueue;
@@ -129,7 +132,12 @@
MAIN_QUEUE.addOperationWithBlock(new NSOperationQueue.Block_addOperationWithBlock() {
@Override
public void call_addOperationWithBlock() {
- thing.run();
+ try {
+ thing.run();
+ }
+ catch (Throwable th) {
+ Logger.warn("error in callOnMainThread: " + thing, th);
+ }
}
});
}
@@ -194,4 +202,13 @@
return NSURL.URLWithString(url);
}
}
+
+ public static <R> R invokeChecked(Function<Ptr<NSError>, R> method) throws Exception {
+ Ptr<NSError> outError = PtrFactory.newObjectReference(NSError.class);
+ R result = method.apply(outError);
+ if (outError.get() != null) {
+ throw new Exception("Error: " + outError.get());
+ }
+ return result;
+ }
}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|