AsyncTask中的Http请求导致多个屏幕旋转的OutofMemory错误

时间:2014-02-14 08:13:51

标签: android multithreading android-fragments android-asynctask rotation

我是一名新的Android程序员。我的第一个主要任务是创建一个Http Post请求,该请求在片段中的AsyncTask中运行。

我花了一百多个小时来学习AsyncTask的细节及其许多缺陷和怪癖。我已阅读了数百页,执行了数百次Google搜索并阅读了数万个单词以尝试解决此问题。我已经检查了每一个。单。小。方面。我的代码,删除和替换它的小块,通常逐行,并调试,试图弄清楚这里发生了什么。我所发现的只是对Android及其背后的方法的厌恶。

我创建了一个活动。让我们称之为“登录”。然后我创建一个片段。我们称之为“LoginTaskFragment”。当用户点击按钮时,片段运行AsyncTask,AsyncTask执行对url的请求。如果响应需要很长时间(我在服务器端的脚本中添加一个睡眠来模拟这个),并且用户反复旋转屏幕,在某些时候,应用程序将崩溃,返回Out of Memory错误

如果我在无UI片段或UI片段中运行任务,就会发生这种情况。即使我调用setRetainInstance(true)也会发生这种情况。我已经检查过,并且我100%肯定AsyncTask不会在屏幕旋转时重新运行。现在,如果我完全删除Http Post请求,我就没有问题(或者内存泄漏太小而无法产生效果,即使经过一百多次轮换,我已经尝试过)。即使我在onStop中调用asyncTask.cancel(false)...或true ...,问题仍然存在。使用在许多教程中经常与AsyncTasks一起提到的WeakReference技巧没有任何区别。

我还在活动中使用savedInstanceState Bundle来保存活动本身的某些相关数据,我不完全清楚这是否会影响片段的asynctask生存期。但我的理解不是。

老实说,我觉得这很荒谬,不应该成为一个问题。完全没有。由于Android在屏幕旋转时会愉快地破坏你的对象,所以它也应该对GC负责,但当然,我们都知道,事实并非如此。这似乎是一个错综复杂,乐队主义的笑话。

以下是我在活动中初始化片段的一些代码:

LoginTaskFragment loginFieldsTask = new LoginTaskFragment();
getSupportFragmentManager().beginTransaction().add(loginFieldsTask,"loginFieldsTask").commit();

这是相关的片段代码。如果我删除所有内容,只是离开这个,我就会遇到问题,无论我的代码中是否还有其他内容:

...
protected JsonObject doInBackground(Void... params) {

String postUrl="<some nice url here>";

ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("username", "un"));
nameValuePairs.add(new BasicNameValuePair("password", "pw"));

HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 10000);
HttpConnectionParams.setSoTimeout(httpParameters, 30000);

DefaultHttpClient httpClient = new DefaultHttpClient(httpParameters);
HttpPost httpPost = new HttpPost(postUrl);
try {

     httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
     HttpResponse response = httpClient.execute(httpPost);
     httpClient.getConnectionManager().shutdown();

     BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
     String json = reader.readLine();

     JsonElement jElement = new JsonParser().parse(json);
     JsonObject jObject = jElement.getAsJsonObject();

     return jObject;

} catch (ClientProtocolException e) {
     httpClient.getConnectionManager().shutdown();
} catch (IOException e) {
     httpClient.getConnectionManager().shutdown();
} catch (Exception e) {
     httpClient.getConnectionManager().shutdown();
}

return null;

}

我知道如果我将套接字超时设置为较低的数字,它似乎会杀死该线程,我可以旋转到我心中的内容。但是我想在上面的代码中将超时保留在30秒,以避免出现较旧的电话网络连接问题。

我的理解是,由于线程在一个片段中,所以当屏幕旋转时它不应该被重新播放。这似乎是真的。 onCreate绝对不会被轮换。

当然,这是所有事情都依赖的界限:

HttpResponse response = httpClient.execute(httpPost);

我简直不敢相信没有人遇到过这个问题,但是经过一百多个小时的搜索并将我的头撞在墙上,我已经受够了。我需要一个比我更好的人来告诉我发生了什么。

编辑:添加android:configChanges =“orientation | keyboardHidden | screenSize”不是一个好主意,并且非常不鼓励将此用作不良做法。请参阅[在此处理配置更改]。事实上,这是首先使用无UI片段的重点。在内部任务完成之前,不会销毁AsyncTask片段。因此,尽管潜在的活动被一遍又一遍地重建,但它从未被重新创建。

2 个答案:

答案 0 :(得分:0)



您可以使用 getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
在您的片段类中限制旋转,以便在旋转发生时不会重新执行代码。

答案 1 :(得分:0)

如果你不想在每次轮换(配置更改)中重新创建异步任务,那么在清单文件中

  

机器人:configChanges = “取向| keyboardHidden |屏幕尺寸”

<activity
            android:name="com.app.MainActivity"
            android:configChanges="orientation|keyboardHidden|screenSize" >

OR

使用dinamic片段时,必须使用帧布局,并将片段放入其中。

 <com.myproyect.TopMenu
        android:id="@+id/menu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    />

    <!-- this was the problem
    <fragment 
        android:id="@+id/fragment"
        android:layout_width="match_parent" 
        android:layout_height="match_parent"
        class="com.myproyect.HomeFragment"
        android:layout_below="@id/menu"
    />  -->

    <FrameLayout android:id="@+id/fragment"
            android:layout_width="match_parent" android:layout_height="match_parent" 
            android:layout_below="@id/menu"/>

    <LinearLayout
        ...
    >
    </LinearLayout>
</RelativeLayout>

并在FrameLayout中实例化片段。

fragmentTransaction.replace(R.id.fragment, f);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();