在RoboVM中真的很奇怪NullPointerException

时间:2015-06-06 22:52:57

标签: ios nullpointerexception robovm

如果我使用任何非null 参数调用以下RoboVM方法:

public static void runOnUiThread(final Runnable runnable) {
    System.out.println("Inside runOnUiThread():");
    System.out.println("  Null-check: "+(runnable==null));

    NSOperation operation = new NSOperation() {

        @Override
        public void main() {
            System.out.println("Inside main():");
            System.out.println("  Null-check: "+(runnable==null));
            runnable.run();                 // NullPointerException here?!? How???
            System.out.println("  main() completed");
        }
    };

    NSOperationQueue.getMainQueue().addOperation(operation);        
}

输出:

Inside runOnUiThread():
  Null-check: false
Inside main():
  Null-check: true
java.lang.NullPointerException
    at RoboVMTools$1.main(RoboVMTools.java)
    at org.robovm.apple.foundation.NSOperation.$cb$main(NSOperation.java)
    at org.robovm.apple.uikit.UIApplication.main(Native Method)
    at org.robovm.apple.uikit.UIApplication.main(UIApplication.java)
    at Main.main(Main.java)

到底是怎么回事?更重要的是,我该如何解决它?

  • 我尝试在operation.addStrongRef(runnable);之前添加NSOperationQueue...。没有区别。
  • 我还尝试将匿名内部类移动到自己的类中,该类具有private final字段来存储传递给其构造函数的runnable。结果相同。

我只是错过了一些非常明显的东西吗?

2 个答案:

答案 0 :(得分:1)

你对GC是正确的。在从Objective-C端调用操作之前,您的NSOperation实例是垃圾收集的。当NSOperationQueue调用Java端时,将创建一个NSOperation匿名类的新实例,该实例没有对Runnable实例的引用,而是null结果是NullPointerException被抛出。

使用addStrongRef()解决问题的方法是正确的,但只有mainQueue.addStrongRef(operation)和相应的removeStrongRef()调用才足够:

public static void runOnUiThread(final Runnable runnable) {

    final NSOperationQueue mainQueue = NSOperationQueue.getMainQueue();

    NSOperation operation = new NSOperation() {

        @Override
        public void main() {
            runnable.run();
            mainQueue.removeStrongRef(this);
        }
    };

    mainQueue.addStrongRef(operation);
    mainQueue.addOperation(operation);      
}

这将阻止Java operation实例(以及从Runnable可以访问的任何Java对象)进行GC操作,直到释放Objective-C NSOperationQueue实例。由于Objective-C端队列是一个单例,它在应用程序的生命周期内不会被释放。

RoboVM NSOperationQueue Java类提供addOperation()方法的版本,该方法采用Runnable。使用此方法时,RoboVM将负责保留Objective {C端所需的Runnable实例。对于采用@Block带注释的Runnable类型参数或任何org.robovm.objc.block.VoidBlock*org.robovm.objc.block.Block*接口的方法,情况也是如此。

使用此addOperation()方法,您的代码将变为:

public static void runOnUiThread(Runnable runnable) {
    NSOperationQueue.getMainQueue().addOperation(runnable);      
}

PS。 RoboVM使用的GC与Apple垃圾收集器无关,因此Apple的文档不会帮助您理解这样的问题。

答案 1 :(得分:0)

嗯......这解决了它:

public static void runOnUiThread(final Runnable runnable) {

    final NSOperationQueue mainQueue = NSOperationQueue.getMainQueue();

    NSOperation operation = new NSOperation() {

        @Override
        public void main() {
            runnable.run();

            mainQueue.removeStrongRef(runnable);
            mainQueue.removeStrongRef(this    );
        }
    };

    mainQueue.addStrongRef(runnable );
    mainQueue.addStrongRef(operation);

    mainQueue.addOperation(operation);      
}

但不要问我为什么这是必要的。 Apple文档说"In garbage-collected applications, the queue strongly references the operation object."所以,我之前尝试的operation.addStrongRef(runnable);应该已经足够了,因为操作对象应该被队列引用。但我猜这个世界并不总是像我解释文档那样工作。