我可以从处理程序中获取未来的对象吗?
Handler handler = new Handler(getMainLooper());
Future<String> future = handler.post(new Callable<String>() {
public String call() throw Exception {
// run in the main thread
return askForPassword();
}
}); // can I do something like this?
String password = future.get(); // wait until finish
// do network things...
我有一个网络线程,我需要向用户询问密码,因为我需要显示一个输入对话框,我必须在主线程上执行此操作,但处理程序无法返回值。
我可以做同样的事情Handler handler = new Handler(getMainLooper());
String password = null;
handler.post(() -> {
// run in the main thread
password = askForPassword();
});
while (password == null) { /*wait until finish*/ }
// do network things...
但这看起来很愚蠢和不方便
答案 0 :(得分:1)
Handler
基本上是异步的,因此无论你放在哪里都不能保证立即运行(此外,你可以postDelayed
或postAtTime
)。然后很明显,您不能将Handler
中的任何值直接返回到发布它的代码。所以你必须解决这个问题。
另一个障碍是,在Java中,你的闭包只能捕获final
变量,你也必须解决这个问题。
新API(CompletableFuture
)
不幸的是,原始Future
Java API不适用于组合。如果您仅定位新设备,因此可以使用较新的CompletableFuture
,则可以执行以下操作:
CompletableFuture<String> askForPasswordNewApi() {
// your UI code that creates Future
}
void doNetworkThingNewApi() {
// some network stuff
final CompletableFuture<String> passwordFutureWrapper = new CompletableFuture<String>();
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// run in the main thread
CompletableFuture<String> future = askForPasswordNewApi();
// bind the real future to the outer one
future.handle((r, ex) -> {
if (ex != null)
passwordFutureWrapper.completeExceptionally(ex);
else
passwordFutureWrapper.complete(r);
return 0;
});
}
});
// wait until finish
// don't forget to handle InterruptedException here
String password = passwordFutureWrapper.get();
// do more network things...
}
这个想法很简单:创建可由final
捕获的外部passwordFutureWrapper
变量Handler
,并将此包装器绑定到真实的future
旁注 :如果askForPassword
已经返回Future
但您无法使用新API,则可能已重新实施类似于CompletableFuture
的东西,所以你只需稍微修改一下这个代码就可以将一个未来绑定到另一个未来。
旧API
如果您尚未在代码中使用CompletableFuture
,但仍然以某种方式使用基于Future
的签名的方法:
Future<String> askForPasswordOldApi()
你可以更明确地做到这一点:
void doNetworkThingOldApi() {
// some network stuff
final CountDownLatch syncLock = new CountDownLatch(1);
final Future<String>[] futureWrapper = new Future<String>[1];
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// run in the main thread
final CompletableFuture<String> future = askForPasswordOldApi();
futureWrapper[0] = future;
syncLock.countDown();
}
});
String password;
try {
// 1 minute should be quite enough for synchronization between threads
if (!syncLock.await(1, TimeUnit.MINUTES)) {
// log error, show some user feedback and then stop further processing
return;
}
password = futureWrapper[0].get(); // wait until finish
} catch (InterruptedException ex) {
// log error, show some user feedback and then stop further processing
return;
} catch (ExecutionException ex) {
// log error, show some user feedback and then stop further processing
return;
}
// do more network things...
}
这里的想法如下:
使用单元素数组作为简单容器来解决final
- 闭包限制
使用CountDownLatch
确保网络和UI线程之间的同步,即当我们开始使用futureWrapper[0]
等待结果时,get
不为空。
更新(图书馆API设计)
如果您正在设计API并希望通过回调处理具有不同附加方案的登录单个条目,我会使用类似于CompletableFuture
的自定义实现:
public interface ResultHandler<T> {
void resolve(T result);
void cancel();
}
class ResultHandlerImpl<T> implements ResultHandler<T> {
enum State {
Empty,
Resolved,
Cancelled
}
private final Object _lock = new Object();
private State _state = State.Empty;
private T _result;
@Override
public void resolve(T result) {
synchronized (_lock) {
if (_state != State.Empty) // don't override current state
return;
_result = result;
_state = State.Resolved;
_lock.notifyAll();
}
}
@Override
public void cancel() {
synchronized (_lock) {
if (_state != State.Empty) // don't override current state
return;
_state = State.Cancelled;
_lock.notifyAll();
}
}
public boolean isCancelled() {
synchronized (_lock) {
return _state == State.Cancelled;
}
}
public boolean isDone() {
synchronized (_lock) {
return _state == State.Resolved;
}
}
public T get() throws InterruptedException, CancellationException {
while (_state == State.Empty) {
synchronized (_lock) {
_lock.wait();
}
}
if (_state == State.Resolved)
return _result;
else
throw new CancellationException();
}
}
我可能会创建接口public
但是实现ResultHandlerImpl
package-private,因此用户更难以搞清楚实现细节。然后在回调方法中,我将回调ResultHandler
作为参数传递(实际上它显然是ResultHandlerImpl
):
public interface LoginCallback {
void askForPassword(ResultHandler<String> resultHandler);
}
我的login
方法看起来像这样(假设你有私有方法tryRestoreSession
,不需要密码和loginWithPassword
需要):
public boolean login(final LoginCallback loginCallback) {
if (tryRestoreSession()) {
return true;
} else {
final ResultHandlerImpl<String> passwordHandler = new ResultHandlerImpl<>();
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// run in the main thread
loginCallback.askForPassword(passwordHandler);
}
});
String password;
try {
password = passwordHandler.get();
} catch (CancellationException e) {
return false;
} catch (InterruptedException e) {
return false;
}
return loginWithPassword(password);
}
}
我认为这里很重要:
我认为将回调传递给LoginCallback
可以更容易地使用标准Java编写基于异步UI的实现。
ResultHandlerImpl
有cancel
方法。因此,如果用户忘记密码,则有一种方法可以取消整个登录过程,而不会遇到后台线程一直等待密码
ResultHandlerImpl
使用显式同步和wait
/ notifyAll
在不同线程上的操作之间建立happens-before
关系以避免。
ResultHandlerImpl
使用wait
/ notifyAll
,因此后台线程在等待用户界面时不会占用CPU(和电池)。
答案 1 :(得分:0)
使用“等待和通知”而不是循环
进行更新更新了2个同步方法
最后,我最终得到了对象包装器(thansk到@SergGr)和处理程序
class ObjectWrapper<T> {
T object;
boolean ready;
synchronized void set(T object) {
this.object = object;
this.ready = true;
notifyAll();
}
T get() {
while (!ready) {
synchronized(this) {
try {
wait();
} catch (InterruptedException e) {
return null;
}
}
}
return object;
}
}
在我的网络线程中
Handler handler = new Handler(getMainLooper());
ObjectWarpper<String> wrapper = new ObjectWarpper<>();
handler.post(() -> wrapper.set(askForPassword()));
String password = wrapper.get();