本文档介绍了如何在Android应用内部署HTTP服务器,并在接收到请求时通过AlertDialog与用户交互。通过监听AlertDialog的按钮点击事件,服务器能够根据用户的选择向客户端发送响应。同时,提供了一种简单的阻塞方法,确保用户交互完成后再发送响应,并讨论了潜在的性能影响。
在Android应用中集成HTTP服务器,并根据用户在AlertDialog中的选择来响应客户端请求,这涉及到多线程、UI交互和网络编程。以下将详细介绍实现步骤和注意事项。
创建HTTP服务器
首先,需要创建一个HTTP服务器。可以使用Java自带的HttpServer类来实现。
import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpExchange; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URI; import java.util.HashMap; import java.util.Map; import android.os.Handler; import android.os.Looper; import android.app.AlertDialog; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import androidx.appcompat.widget.AppCompatButton; import android.widget.LinearLayout; import android.util.Log; public class HttpServerManager { private static final String TAG = "HttpServerManager"; private HttpServer httpServer; private AlertDialog adminRegisterDialog; private View adminRegisterView; private static HttpServerManager instance; public static HttpServerManager getInstance() { if (instance == null) { instance = new HttpServerManager(); } return instance; } private HttpServerManager() { try { InetSocketAddress address = new InetSocketAddress(8080); httpServer = HttpServer.create(address, 0); httpServer.createContext("/getDeviceRegister", new EchoGetHandlerForDeviceRegister()); httpServer.setExecutor(null); // Use the default executor httpServer.start(); Log.i(TAG, "HttpServer Start"); } catch (Exception e) { e.printStackTrace(); } } static void parseQuery(String query, Map<String, Object> parameters) { if (query != null) { String pairs[] = query.split("[&]"); for (String pair : pairs) { String param[] = pair.split("[=]"); if (param.length > 1) { parameters.put(param[0], param[1]); } } } } class EchoGetHandlerForDeviceRegister implements HttpHandler { @Override public void handle(HttpExchange he) throws IOException { // parse request Map<String, Object> parameters = new HashMap<>(); URI requestedUri = he.getRequestURI(); String query = requestedUri.getRawQuery(); parseQuery(query, parameters); // Flag to control the while loop final boolean[] isClick = {false}; final AppCompatButton[] allowButtonHolder = {null}; new Handler(Looper.getMainLooper()).post(() -> { //SHOW DIALOG HERE AdminRegisterDialogResult result = showAdminRegisterDialog(he.getRemoteAddress()); allowButtonHolder[0] = result.getAllowButton(); //ALERT DILOG CLICK LISTENER result.getAllowButton().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG,"allowButton"); isClick[0] = true; adminRegisterDialog.dismiss(); } }); result.getNotAllowButton().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG,"not allowButton"); isClick[0] = true; adminRegisterDialog.dismiss(); } }); }); // This is a busy-wait loop. Consider alternatives. while (!isClick[0]) { Log.i(TAG, "in while loop"); try { Thread.sleep(100); // Avoid busy-waiting, but be careful with sleep in a handler } catch (InterruptedException e) { e.printStackTrace(); } } Log.i(TAG, "out while loop"); // send response String response = "<h1>Alert Dialog Clicked</h1>"; for (String key : parameters.keySet()) response += key + " = " + parameters.get(key) + "n"; he.sendResponseHeaders(200, response.length()); OutputStream os = he.getResponseBody(); os.write(response.toString().getBytes()); os.close(); } } public class AdminRegisterDialogResult { private AppCompatButton allowButton; private AppCompatButton notAllowButton; public AdminRegisterDialogResult(AppCompatButton allowButton, AppCompatButton notAllowButton) { this.allowButton = allowButton; this.notAllowButton = notAllowButton; } public AppCompatButton getAllowButton() { return allowButton; } public AppCompatButton getNotAllowButton() { return notAllowButton; } } public AdminRegisterDialogResult showAdminRegisterDialog(InetSocketAddress clientAdress){ Log.i(TAG, "showAdminRegisterDialog()"); if (adminRegisterDialog != null) adminRegisterDialog.cancel(); Context context = MainActivity.instance(); AlertDialog.Builder builder = new AlertDialog.Builder(context); LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); adminRegisterView = li.inflate(R.layout.register_dialog, null); builder.setView(adminRegisterView); builder.setCancelable(false); TextView deviceNameText = adminRegisterView.findViewById(R.id.deviceNameText); TextView infoText = adminRegisterView.findViewById(R.id.infoText); deviceNameText.setText(clientAdress.toString()); infoText.setText(R.string.register_admin_allow_text); AppCompatButton allowButton = adminRegisterView.findViewById(R.id.allowButton); AppCompatButton notAllowButton = adminRegisterView.findViewById(R.id.notAllowButton); adminRegisterDialog = builder.create(); adminRegisterDialog.show(); adminRegisterDialog.getWindow().setLayout(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); return new AdminRegisterDialogResult(allowButton, notAllowButton); } public void stop() { if (httpServer != null) { httpServer.stop(0); Log.i(TAG, "HttpServer Stop"); } } }
代码解释:
- HttpServerManager 类负责创建和管理 HTTP 服务器实例。
- EchoGetHandlerForDeviceRegister 类实现了 HttpHandler 接口,用于处理 /getDeviceRegister 路径的请求。
- parseQuery 方法用于解析 URL 中的查询参数。
- showAdminRegisterDialog 方法用于显示 AlertDialog,等待用户交互。
- isClick 标志位用于控制 while 循环,确保用户点击按钮后才发送响应。
显示AlertDialog并等待用户交互
在 EchoGetHandlerForDeviceRegister 的 handle 方法中,使用 Handler 将显示 AlertDialog 的操作切换到主线程。这非常重要,因为UI操作必须在主线程中进行。
new Handler(Looper.getMainLooper()).post(() -> { //SHOW DIALOG HERE AdminRegisterDialogResult result = showAdminRegisterDialog(he.getRemoteAddress()); //ALERT DILOG CLICK LISTENER result.getAllowButton().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG,"allowButton"); isClick[0] = true; adminRegisterDialog.dismiss(); } }); result.getNotAllowButton().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG,"not allowButton"); isClick[0] = true; adminRegisterDialog.dismiss(); } }); });
使用while循环阻塞直到用户交互
为了确保在用户点击AlertDialog上的按钮后再发送响应,可以使用一个 while 循环进行阻塞。
final boolean[] isClick = {false}; while (!isClick[0]) { Log.i(TAG, "in while loop"); try { Thread.sleep(100); // Avoid busy-waiting, but be careful with sleep in a handler } catch (InterruptedException e) { e.printStackTrace(); } } Log.i(TAG, "out while loop");
注意: 这种方法使用了忙等待(busy-waiting),会消耗CPU资源。虽然添加了Thread.sleep(100)来降低CPU占用,但仍然不是最佳实践。在实际应用中,应该考虑使用更高效的线程同步机制,如 CountDownLatch 或 Semaphore。
发送响应
在 while 循环结束后,表示用户已经进行了交互,此时可以构建并发送响应。
// send response String response = "<h1>Alert Dialog Clicked</h1>"; for (String key : parameters.keySet()) response += key + " = " + parameters.get(key) + "n"; he.sendResponseHeaders(200, response.length()); OutputStream os = he.getResponseBody(); os.write(response.toString().getBytes()); os.close();
替代方案:使用 CountDownLatch
CountDownLatch 是一种更优雅的线程同步方式,可以避免忙等待。
import java.util.concurrent.CountDownLatch; class EchoGetHandlerForDeviceRegister implements HttpHandler { @Override public void handle(HttpExchange he) throws IOException { // parse request Map<String, Object> parameters = new HashMap<>(); URI requestedUri = he.getRequestURI(); String query = requestedUri.getRawQuery(); parseQuery(query, parameters); final CountDownLatch latch = new CountDownLatch(1); new Handler(Looper.getMainLooper()).post(() -> { //SHOW DIALOG HERE AdminRegisterDialogResult result = showAdminRegisterDialog(he.getRemoteAddress()); //ALERT DILOG CLICK LISTENER result.getAllowButton().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG,"allowButton"); latch.countDown(); // Signal that the button has been clicked adminRegisterDialog.dismiss(); } }); result.getNotAllowButton().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG,"not allowButton"); latch.countDown(); // Signal that the button has been clicked adminRegisterDialog.dismiss(); } }); }); try { latch.await(); // Wait for the latch to be counted down } catch (InterruptedException e) { e.printStackTrace(); } // send response String response = "<h1>Alert Dialog Clicked</h1>"; for (String key : parameters.keySet()) response += key + " = " + parameters.get(key) + "n"; he.sendResponseHeaders(200, response.length()); OutputStream os = he.getResponseBody(); os.write(response.toString().getBytes()); os.close(); } }
代码解释:
- CountDownLatch latch = new CountDownLatch(1); 创建一个计数器,初始值为1。
- 在按钮的 OnClickListener 中,调用 latch.countDown(); 将计数器减1,当计数器为0时,latch.await(); 会立即返回。
- latch.await(); 会阻塞当前线程,直到计数器为0。
总结与注意事项
- 在Android应用中使用HTTP服务器需要处理多线程和UI交互的问题。
- 确保UI操作在主线程中进行。
- 避免使用忙等待,考虑使用 CountDownLatch 或 Semaphore 等线程同步机制。
- 合理处理异常,例如 InterruptedException。
- 仔细评估性能影响,避免阻塞主线程。
- 在应用退出时,停止HTTP服务器以释放资源。
本教程提供了一种在Android应用内实现HTTP服务器并与AlertDialog交互的方法。通过选择合适的线程同步机制,可以有效地管理并发,并提供良好的用户体验。
评论(已关闭)
评论已关闭