当用户按下返回按钮时取消正在进行的连接

4

我一直在搜索相关信息,但是没有找到我想要的答案。我知道使用AskyncTask可以通过.cancel(true)来取消任务,但前提是我需要一个循环来检查值isCanceled()

但是我的问题是,如果用户按下返回键,该如何取消被卡在httpclient.execute()中的AsyncTask?如果用户离开当前Activity并进入另一个Activity,我不希望有未受控制的AsyncTask在运行,因为这可能会导致内存问题。用户可能会来回切换并创建不确定数量的任务,所以我想关闭它们。有人知道方法吗?以下是我用于连接的代码:

public class Test extends Activity {

@Override
protected void onStart() {
    super.onStart();

    new ConnectionTask().execute("https://www.mywebserver.com/webservice.php?param1=test");
}

private class ConnectionTask extends AsyncTask<String, Void, String>{

    @Override
    protected String doInBackground(String... params) {
        try {

            HttpClient httpclient = DefaultHttpClient(params,clientConnectionManager);
            HttpPost httpPost = new HttpPost(params[0]);
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            if(httpEntity != null)
                return EntityUtils.toString(httpEntity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;        
    }

  }

}

你知道我在onStop()里应该添加什么来取消正在进行的httpClient.execute()函数吗?有时会陷入几乎永久的卡顿状态。

非常感谢你的帮助,提前感谢。

更新

如果我关闭ConnectionManager,我需要重新进行https握手,对吗?看一下我创建httpClient的代码,我用这个来处理https:

HttpClient httpclient = DefaultHttpClient(params,clientConnectionManager);

感谢大家快速回复并提供的各种解决方案。我将尝试使用超时(以免等待过久)+ cancel()函数来避免处理onPostExecute。如果结果符合预期,我会告诉大家的!非常感谢大家!


你尝试过取消正在运行的线程吗? - Nikola Despotoski
当您尝试取消AsyncTask时,它只会将isCancelled()设置为true,我不知道有什么方法可以销毁AsyncTask,.cancel(true)无效。 - jesbomar
尝试使用此 httpclient.getConnectionManager().shutdown(); - Lalit Poptani
我写的是http,但实际上应该是https。这段代码是简化版,因为我想保留connectionmanager和httpParams以避免进一步的握手,有没有办法避免关闭connectionManager? - jesbomar
6个回答

4
根据HttpClient文档,如果请求超时或客户端同时发出太多的请求而导致HTTP请求不能在预期时间内完成,你可以使用HttpUriRequest#abort()来终止请求。HttpClient执行的HTTP请求可以在任何执行阶段通过调用HttpUriRequest#abort()方法来中止。该方法是线程安全的,可以从任何线程调用。当HTTP请求被中止时,即使当前正在阻塞I/O操作的执行线程也会通过抛出InterruptedIOException异常而保证解除阻塞。

1
onPause()onBackButtonPressed() 方法中,调用您的任务上的 cancel()。在 doInBackground() 方法中,在右侧执行。

HttpResponse httpResponse = httpClient.execute(httpPost);

检查 isCanceled(),如果为真,则立即返回。

当然,您仍然有多个任务运行的风险,但由于此操作是UI驱动的(即由用户交互启动的任务),因此在同一时间最多只应该有几个任务正在运行,前提是HttpClient的超时设置是合理的。


更新

一旦确定需要取消任务,您也可以关闭连接管理器。请参阅文档

这将关闭套接字并导致从execute()中立即返回。连接管理器在创建DefaultHttpClient时设置。


我该如何设置超时时间呢?因为目前没有发生超时,我可能需要等待一两分钟以上才能得到响应。 - jesbomar
实际上,你可能应该只关闭连接管理器。我已经添加了更新。 - Alex Gitelman
抱歉造成困惑,我只是写了一个更简单的代码来避免问题中出现过多的代码。我使用https,并希望保持连接管理器,因此关闭它会让我再次进行握手。我想我将不得不使用超时。 - jesbomar

1

cancel方法只是将一个变量设置为true,而不是真正关闭AsyncTask,它被认为是循环要求的,然后在每次迭代中检查值是否已取消。但实际上并不存在这样的循环,因此我无法检查isCancelled()。 - jesbomar
cancel() 将尝试取消任务的执行。如果您希望在取消时尽快完成任务,则需要检查 isCancelled 并在为 true 时返回。您误解了。http://developer.android.com/reference/android/os/AsyncTask.html#cancel%28boolean%29 - Ron
1
但是请看这个:“任务可以随时通过调用cancel(boolean)来取消。调用此方法将导致后续对isCancelled()的调用返回true。在调用此方法之后,doInBackground(Object [])返回后,将调用onCancelled(Object),而不是onPostExecute(Object)。 ”我们仍然需要doInBackground返回,这就是问题所在,它永远不会返回,因为它被卡在httpClient.execute()中,我错了吗? :S - jesbomar
你在你的上下文中是正确的。我更新了我的答案。你可以设置连接超时。 - Ron

1

我的理解是 httpClient.execute() 是阻塞的,所以没有代码在运行来检查 isCancelled() 的值。而且您不想关闭连接管理器。

这可能有点巧妙,但是如果没有更好的解决方案,如果你在 httpClient.execute() 阻塞时调用线程的 Thread.interrupt() 会发生什么?

快速测试可以验证这一点,只需在 ConnectionTask 定义中添加一个私有实例变量,类型为 Thread,将其设置为 doBackground() 顶部的 Thread.currentThread(),并添加一个公共方法,在其中对其调用 .interrupt()

如果幸运的话,这将导致 httpClient.execute() 立即退出并抛出异常。您可以捕获它,并在方法调用结束和 AsyncTask 自然结束之前进行任何必要的清理。


好主意!我会尝试并回来分享结果的! - jesbomar
没有产生任何结果...我尝试按照您的建议进行操作,但是并没有中断。将AsyncTask更改为Thread + Handler是否有帮助?AsyncTask{ private Thread thisThread; @Override protected String doInBackground(String... params) { thisThread = Thread.currentThread(); [...] } public void cancelConnection(){ thisThread.interrupt(); } } - jesbomar
哦,所以你正在从另一个线程调用cancelConnection(),而httpClient.execute()仍然只是坐在那里?我曾经担心过这一点,这就是为什么我说“如果你很幸运”...从Android API文档中,它并没有说execute()会抛出InterruptedException,因此不能保证它会。既然它不遵守这个规定,我看不到任何停止它直到超时的方法,抱歉。虽然值得一试。不,我不认为Handler会有任何区别,execute()仍然会挂起Handler的线程,直到超时。 - NeilS
非常感谢你的帮助。我已经尝试了很多次,都没有成功。如 userSeven7s 所说,我将不得不为.execute()实现一个超时机制。再次感谢你;) - jesbomar

0

对于这个问题,我有一个快速解决方案,我在我的情况下使用了它。可能不是正确的方法,因为当您按返回键时,应用程序将立即响应,但在后台网络操作将继续(如果您设置超时,则会一直持续到超时),而不会阻塞应用程序:

将所有网络操作都放在一个新的服务中,例如NSERV。使NSERV扩展Thread类,并在run方法中执行所有网络操作。为了更清晰地了解您的代码,请确保启动NSERV的活动/服务也扩展Thread类,并从其run方法启动NSERV。

然后使用静态字段或单例从NSERV访问活动/服务中的变量。

例如:

public class NSERV  extends Service implements Runnable{
public int onStartCommand (Intent intent, int flags, int startId)
{ 
        Thread t = new Thread (this, "abc");
        t.start();          
        return super.onStartCommand(intent, flags, startId);            
}

public void run() 
{
//NETWORK OPERATION...   
onDestroy();
}

public void onDestroy() 
{       
        stopSelf();
        super.onDestroy();
}
}

0
你可以重写活动的 onPause() 方法,在那里编写停止连接的代码。如果选项是特定于返回按钮,则可以重写 onBackButtonPressed() 方法以停止执行。

你知道如何停止连接吗?从两个不同的线程访问httpClient是否安全?我已经尝试过了,但是没有取消或中止连接的方法...你有什么想法吗? - jesbomar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接