Android开发中如何从ExecutorService.execute()获取返回值
解决方案:从后台线程获取认证Token
你的核心问题是同步方法阻塞主线程+多请求下结果无法安全返回,以下是三种适配Android场景的实现方案:
方案1:回调接口(Android传统异步模式)
通过定义回调接口,将异步任务的结果通过回调方法传递到主线程,天然支持多请求独立处理。
步骤1:在通信类中定义回调接口
public class LedGo { private ExecutorService executor = Executors.newSingleThreadExecutor(); private Handler handler = new Handler(Looper.getMainLooper()); private boolean connected = false; private String authToken; // 定义认证结果回调接口 public interface AuthTokenCallback { void onSuccess(String authToken); void onFailure(String errorCode); void onNotConnected(); } // 原connect方法保持不变 public boolean connect(String ip, int port, SSLContext cf, Runnable onSuccess, Runnable onFailure) { // 你的原有连接逻辑 return connected; }
步骤2:修改genAuthToken为异步方法
public void genAuthToken(String userName, String passwd, AuthTokenCallback callback) { if (!connected) { // 主线程回调未连接状态 handler.post(callback::onNotConnected); return; } executor.execute(() -> { try { // 构造请求JSON JSONObject json = new JSONObject(); json.put("func", "genAuthToken"); json.put("userName", userName); json.put("passwd", passwd); // 发送请求并接收响应 pw.println(json); String reply = scan.nextLine(); // 解析响应 JSONObject responseJson = new JSONObject(reply); int code = responseJson.getInt("code"); if (code == 200) { authToken = responseJson.getString("authToken"); // 切换到主线程返回成功结果 handler.post(() -> callback.onSuccess(authToken)); } else { handler.post(() -> callback.onFailure(String.valueOf(code))); } } catch (Exception e) { e.printStackTrace(); handler.post(() -> callback.onFailure("请求异常:" + e.getMessage())); } }); } }
步骤3:在MainActivity中调用
LedGo client = new LedGo(); boolean connected = client.connect("192.168.1.4",1304,cf,()->{},()->{ Snackbar.make(view,"Couldn't connect to server",Snackbar.LENGTH_SHORT) .setAnchorView(R.id.fab) .show(); }); // 调用异步认证方法,通过回调获取结果 client.genAuthToken("userName","passwd", new LedGo.AuthTokenCallback() { @Override public void onSuccess(String authToken) { // 主线程处理成功结果:存储Token、更新UI等 Log.d("Auth", "获取Token成功:" + authToken); } @Override public void onFailure(String errorCode) { Snackbar.make(view,"认证失败:"+errorCode,Snackbar.LENGTH_SHORT) .setAnchorView(R.id.fab) .show(); } @Override public void onNotConnected() { Snackbar.make(view,"未连接到服务器",Snackbar.LENGTH_SHORT) .setAnchorView(R.id.fab) .show(); } });
方案2:Future(适合后台任务间依赖场景)
如果需要在后台线程中等待结果(而非直接更新UI),可以用ExecutorService.submit()返回Future对象,通过它获取异步任务的结果。
修改通信类的genAuthToken方法
public Future<String> genAuthToken(String userName, String passwd) { if (!connected) { // 返回空结果的Future return CompletableFuture.completedFuture(null); } // 提交任务并返回Future return executor.submit(() -> { try { JSONObject json = new JSONObject(); json.put("func", "genAuthToken"); json.put("userName", userName); json.put("passwd", passwd); pw.println(json); String reply = scan.nextLine(); JSONObject responseJson = new JSONObject(reply); int code = responseJson.getInt("code"); if (code == 200) { authToken = responseJson.getString("authToken"); return authToken; } else { return String.valueOf(code); } } catch (Exception e) { e.printStackTrace(); return "请求异常:" + e.getMessage(); } }); }
在MainActivity中使用(必须在后台线程调用future.get())
LedGo client = new LedGo(); boolean connected = client.connect("192.168.1.4",1304,cf,()->{},()->{ Snackbar.make(view,"Couldn't connect to server",Snackbar.LENGTH_SHORT) .setAnchorView(R.id.fab) .show(); }); // 必须在后台线程处理Future,避免阻塞主线程 client.executor.execute(() -> { Future<String> future = client.genAuthToken("userName","passwd"); try { String result = future.get(); // 阻塞当前后台线程,等待结果 if (result == null) { handler.post(() -> Snackbar.make(view,"未连接到服务器",Snackbar.LENGTH_SHORT).show()); } else if (result.equals(authToken)) { handler.post(() -> Log.d("Auth", "Token获取成功:" + result)); } else { handler.post(() -> Snackbar.make(view,"认证失败:"+result,Snackbar.LENGTH_SHORT).show()); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); handler.post(() -> Snackbar.make(view,"请求出错:"+e.getMessage(),Snackbar.LENGTH_SHORT).show()); } });
方案3:Kotlin协程(现代Android首选)
如果项目支持Kotlin,协程可以更简洁地实现异步任务+主线程切换,代码可读性更高。
通信类(Kotlin实现)
class LedGo { private val executor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private var connected = false private var authToken: String? = null // 原connect方法保持不变 fun connect(ip: String, port: Int, cf: SSLContext, onSuccess: ()->Unit, onFailure: ()->Unit): Boolean { // 原有连接逻辑 return connected } suspend fun genAuthToken(userName: String, passwd: String): String? { return withContext(executor) { if (!connected) return@withContext null try { val json = JSONObject().apply { put("func", "genAuthToken") put("userName", userName) put("passwd", passwd) } pw.println(json) val reply = scan.nextLine() val responseJson = JSONObject(reply) val code = responseJson.getInt("code") if (code == 200) { authToken = responseJson.getString("authToken") authToken } else { code.toString() } } catch (e: Exception) { e.printStackTrace() "请求异常:${e.message}" } } } }
MainActivity中调用(使用lifecycleScope自动绑定生命周期)
lifecycleScope.launch { val client = LedGo() val connected = client.connect("192.168.1.4",1304,cf, {}, { Snackbar.make(view,"Couldn't connect to server",Snackbar.LENGTH_SHORT) .setAnchorView(R.id.fab) .show() }) if (connected) { val authToken = client.genAuthToken("userName","passwd") when { authToken == null -> Snackbar.make(view,"未连接到服务器",Snackbar.LENGTH_SHORT).show() authToken == client.authToken -> Log.d("Auth", "Token获取成功:$authToken") else -> Snackbar.make(view,"认证失败:$authToken",Snackbar.LENGTH_SHORT).show() } } }
关键注意事项
- 禁止主线程执行网络操作:所有Socket读写必须放到
ExecutorService或协程的后台线程中,否则会触发NetworkOnMainThreadException。 - 主线程更新UI:所有UI操作必须通过
Handler、lifecycleScope等切换到主线程执行,避免线程安全问题。 - 多请求安全:以上三种方案均为每个请求独立处理结果,不会出现公共变量被多请求覆盖的问题。
内容的提问来源于stack exchange,提问作者Balpreet Singh
相关产品推荐
相关产品推荐

