Android UI线程在一个简单的客户端/服务器架构中冻结等待套接字



我认为这是一个很常见的问题,但我仍然没有找到一个满意的答案,所以我要问问自己。

这是一段代码:

// this is insine OnClickView
TextView status = (TextView) findViewById(R.id.status);
status.setText("Trying to connect to the server...");
try {
    // this opens a socket and send a login request to the server.
    int result = CommunicationManager.login(String email, String password);
    switch (result) {
    case CommunicationManager.SUCCESS:
        // login ok, go on with next screen
        break;
    case CommunicationManager.WRONG_EMAIL:
        status.setTextColor(Color.RED);
        status.setText("Wrong Email!");
        break;
    case CommunicationManager.WRONG_PASSWORD:
        status.setTextColor(Color.RED);
        status.setText("Wrong Password!");
        break;
    }
} catch (CommunicationException e) {
    status.setTextColor(Color.RED);
    status.setText("Unable to estabilish a connection!");
} catch (ProtocolException e) {
    status.setTextColor(Color.RED);
    status.setText("Protocol error!");
}

这就是我想要实现的目标:

  1. 用户点击发送按钮
  2. status textview显示"正在尝试连接到服务器…"
  3. UI"等待"通信结束
  4. 状态文本视图相应地显示结果

但是,当用户单击"发送"按钮时,UI会冻结(奇怪的是,在状态文本出现之前),直到通信完成(我试图连接到未知主机)。一个快速的解决方案是设置套接字超时,但我不喜欢这种解决方案:UI仍然冻结,应该设置哪个超时?

我的第一个想法显然是线程,但正如您所看到的,我需要返回一个值,这在线程环境中没有多大意义,因为线程是独立异步运行的。

因此,我需要的是UI等待服务执行,但不会冻结。顺便说一句,在我看来,等待返回值意味着UI必须等待任务结束,我只是不让它冻结。

我遇到了AsyncTask,但我看到了两个主要的缺点:

  1. 在我看来,它与UI的结合过于紧密
  2. 如果我想用Integer、String和Boolean参数执行服务,该怎么办?我应该扩展AsyncTask<Object, Void, Void>

两者都导致了不可延展性。

我能做些什么来实现我的目标?请注意,对该服务的另一个请求将是对尚未准备好的东西的请求,所以我应该每隔几次(比方说十分钟)自动重复一次请求。因此,我可能需要一些可以与TimerTask一起使用的东西,但每次执行该服务时,我仍然需要向UI返回一个值(这样我就可以更新状态文本并让用户知道发生了什么)。

这是处理外部通信(即HTTP调用)时的典型用例。

最好的方法是使用AsyncTask。为您的AsyncTask问题提供答案。

在我看来,它与UI的结合过于紧密;

良好的代码设计将在这里发挥作用。您可以编写自己的回调机制来消除紧密耦合。示例如下。

为WS调用所需的请求和响应创建您的版本。它可以是非常简单的基元类型,也可以是复杂的类型参数。

class Result{
    //Define more para.

}

class Request{
    //Deinf more para.
}

写在回调接口下面。

public interface MyCallBack {
     public void onComplete(Result result);}

创建AsyncTask并在构造函数中获取上面的Interface对象,同一对象可以返回Result对象。

    class LongRunningTask extends AsyncTask<Request, Integer, Long>{
    private MyCallBack callback;
    public LongRunningTask(MyCallBack callback) {
        super();
        this.callback = callback;
    }
    @Override
    protected Long doInBackground(Request... params) {
        // Perform your back ground task.
        return null;
    }
    @Override
    protected void onPostExecute(Long result) {
        super.onPostExecute(result);            
        callback.onComplete(new Result()); //Here result is dummy but in real it should be contructred from doInBackground() method
    }
}

现在是实现接口和调用asynctask的最后一个重要部分。我正在尝试重新使用您的代码以获得更好的清晰度。

public class MainActivity extends Activity implements MyCallBack{
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView status = (TextView) findViewById(R.id.status);
    status.setText("Trying to connect to the server...");
}
private void onClick(){
    //Similer to CommunicationManager.login(String email, String password); in your code.
    LongRunningTask longRunningTask = new LongRunningTask(this);
    longRunningTask.execute(new Request());
}
@Override
public void onComplete(Result result) {     
    try {
        int result = result.getStatus 
        switch (result) {
        case CommunicationManager.SUCCESS:
            // login ok, go on with next screen
            break;
        case CommunicationManager.WRONG_EMAIL:
            status.setTextColor(Color.RED);
            status.setText("Wrong Email!");
            break;
        case CommunicationManager.WRONG_PASSWORD:
            status.setTextColor(Color.RED);
            status.setText("Wrong Password!");
            break;
        }
    } catch (CommunicationException e) {
        status.setTextColor(Color.RED);
        status.setText("Unable to estabilish a connection!");
    } catch (ProtocolException e) {
        status.setTextColor(Color.RED);
        status.setText("Protocol error!");
    }
}

如果我想用Integer、String和Boolean参数执行服务,该怎么办?我应该扩展AsyncTask吗?

第一个参数是任何用户定义的段落。如果您需要传递多个参数,则将它们放入实体形式(即-Class)中。此外,您可以在AsyncTask的构造函数中传递初始配置参数,即-Communication URL。

希望它能有所帮助。

使用多线程,在不同的线程中进行所有通信

使用工作线程或AsyncTask执行长时间运行的操作。

此外,在安卓蜂窝中,如果您在UI线程上执行网络操作,系统会抛出异常。

最新更新