AsyncTask *偶尔*遇到连接超时/错误问题、潜在的内存泄漏或 Android Studio 错误?

Posted

技术标签:

【中文标题】AsyncTask *偶尔*遇到连接超时/错误问题、潜在的内存泄漏或 Android Studio 错误?【英文标题】:AsyncTask *occasionally* getting stuck with connection timeout/error problems, potential memory leak or Android Studio bug? 【发布时间】:2015-04-05 15:58:04 【问题描述】:

我是 Android 和 Java 开发的新手,我整理了一个简单的演示应用来开始学习。它是由:

扩展ActionBarActivity的主要活动,其中 ViewPager 被实例化,它有 一个FragmentPagerAdapter负责出现... ...Fragment 在给定时间三分之二,并且 其中一个片段在创建时,只是为了尝试一下,执行一个AsyncTask(由另一个类定义),它在完成时触发一个HTTP request(onPostExecute)... 在触发它的 Fragment 中填充 TableLayout

我也在努力保持与旧版 android 平台的兼容性,因此我在必要时使用了支持库。

我看到的问题,或者我应该说我看到(继续阅读..),是不时强>我明白了

org.apache.http.conn.HttpHostConnectException:连接到http://123.123.123.123:80/notes.json被拒绝

在大约 40 秒的长时间等待后出现超时错误消息。 当然已经添加了 Internet 权限。

这是随机发生的,通常在垃圾收集器首次运行后发生。

花了几天的时间尝试调试后,我终于重新启动了系统,这种行为完全消失了。

但是,考虑到我花了这么多时间(认为我严重泄露了一些东西),我仍然想:

    了解发生了什么 了解我是否正确分析内存泄漏

1:了解发生了什么:

启动应用程序时显示的屏幕是调用负责更新片段 UI 本身的 AsyncTask 的片段(它被分配给第一个操作栏选项卡)。

应用一启动,我就开始不断旋转屏幕,看看内存会发生什么变化。下面的截图来自 Android Studio 内存分析器。

第一次连接超时错误通常发生在第一次运行垃圾收集器之后,在我通过多次旋转设备填满所有可用内存之后。

此时,AsyncTask 更新片段 UI 失败(在处理显然无响应的连接时卡住了)。 Web 服务器根本不会收到 HTTP 请求,即使我再次旋转屏幕——为了让 Activity 和 Fragment 重新启动——后续的 AsyncTask 也不起作用,也不会发出新的 HTTP 请求。

当然,我一直在捕获所有异常,并且在 onPostExecute() 开始时,我必须执行if (arrayOfJSONobjs == null) return; 以避免将空对象输入到后续片段 UI 的构建方法中。

那么,您认为会发生什么事情才能使连接以这样的方式工作?重启后我怎么看不到了? 我已尝试禁用防病毒软件、防火墙,并检查路由器或网络服务器是否为连续请求过多应用某种保护。(我的设备连接到网络服务器来自互联网,使用我的公共 IP)。没有任何效果,除了重新启动。我唯一剩下的想法是.. 可能Android Studio 中的某个错误有时会在请求中间出现?

2:我是否正确理解了内存分配和 GC?

查看代码,您认为在某些地方我可能会泄漏上下文吗? 我在内存分析器屏幕截图中看到的是非泄漏应用程序的预期良好行为吗?我的意思是,即使没有泄漏,我是否应该看到内存被填满(前提是它会被垃圾收集)?

我不知道如何更好地表达这一点,但是当一切顺利时,我是否应该看到这种图表?如您所见,第一次 GC 仅在内存完全填满时调用,但随后 GC 触发更快,此时仍有一些可用内存(尽管我仍在旋转设备)。这是正常吗?


尽管存在上述错误(但仍可能与它们相关,以防内存泄漏实际发生):我不确定是否必须将视图和上下文都传递给 AsyncTask 对象。我可以通过只有其中一个并从中推断出另一个,以便尽可能减少我传递的参考?
清理:关于 TableLayout 是否适合我正在尝试构建的布局的子问题已移至 another question。

代码:

MainActivity.java

package com.mydom.demoapp;

import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import com.mydom.demoapp.adapter.TabsPagerAdapter;

public class MainActivity extends ActionBarActivity implements ActionBar.TabListener 

    private ViewPager viewPager;
    private TabsPagerAdapter mAdapter;
    private ActionBar actionBar;

    private String[] tabs =  "Music", "Movies", "News";

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        viewPager = (ViewPager) findViewById(R.id.pager);
        mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(mAdapter);

        actionBar = getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(false);
        actionBar.setHomeButtonEnabled(true);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        // Adding Tabs
        for (String tab_name : tabs) 
            actionBar.addTab(actionBar.newTab().setText(tab_name).setTabListener(this));
        

        // on swiping the viewpager make respective tab selected
        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() 

            @Override
            public void onPageSelected(int position) 
                actionBar.setSelectedNavigationItem(position);
            

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) 
            

            @Override
            public void onPageScrollStateChanged(int arg0) 
            
        );
    

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        // Handle action bar item clicks here
        int id = item.getItemId();

        if (id == R.id.action_settings) 
            return true;
        

        if (item.getItemId() == android.R.id.home) 
            NavUtils.navigateUpFromSameTask(this);
            return true;
        

        return super.onOptionsItemSelected(item);
    

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) 
    

    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) 
        // on tab selected show appropriate fragment
        viewPager.setCurrentItem(tab.getPosition());
    

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) 
    

    @Override
    public void onStop()
        super.onStop();
        Log.d("mytag", "MainActivity: onStop entered");
    


TabsPageAdapter.java
package com.mydom.demoapp.adapter;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import com.mydom.demoapp.MusicFragment;
import com.mydom.demoapp.MoviesFragment;
import com.mydom.demoapp.NewsFragment;

// This adapter provides fragment views to tabs.

public class TabsPagerAdapter extends FragmentPagerAdapter

    public TabsPagerAdapter(FragmentManager fm) 
        super(fm);
    

    @Override
    public Fragment getItem(int index) 

        switch (index) 
            case 0:
                return new MusicFragment();
            case 1:
                return new MoviesFragment();
            case 2:
                return new NewsFragment();
        
        return null;
    

    @Override
    public int getCount() 
        // get item count - equal to number of tabs
        return 3;
    



MusicFragment.java(负责实例化和启动 AsyncTask)

package com.mydom.demoapp;

import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.mydom.demoapp.async_task.AsyncTaskRunner;


public class MusicFragment extends Fragment 

    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;

    private OnFragmentInteractionListener mListener;

    public static MusicFragment newInstance(String param1, String param2) 
        MusicFragment fragment = new MusicFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    


    public MusicFragment() 
        // Required empty public constructor
    

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        if (getArguments() != null) 
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        

        Log.d("mytag", "MusicFragment: onCreate entered");

    

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 

        // Inflate the layout for this fragment
        Log.d("janfry", "MusicFragment: onCreateView entered");
        return inflater.inflate(R.layout.fragment_music, container, false);
    


    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) 

        super.onViewCreated(view, savedInstanceState);

        Log.d("mytag", "MusicFragment: onViewCreated entered");

        runner = new AsyncTaskRunner();
        runner.execute(this.getActivity(), view);               
        // I am passing it the context (by getting the activity) and the view so that it will know where to update the UI.

    

    public void onActivityCreated(Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);
    

    // TODO: Rename method, update argument and hook method into UI event
    public void onButtonPressed(Uri uri) 
        if (mListener != null) 
            mListener.onFragmentInteraction(uri);
        
    

    public interface OnFragmentInteractionListener 
        // TODO: Update argument type and name
        public void onFragmentInteraction(Uri uri);
    

    @Override
    public void onStop()
        super.onStop();
        Log.d("mytag", "MusicFragment: onStop entered");
     


AsyncTaskRunner.java

package com.mydom.demoapp.async_task;

import android.content.Context;
import android.graphics.Color;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import com.mydom.demoapp.R;
import com.mydom.demoapp.Utils;
import org.apache.http.HttpResponse;
// import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
// import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
// import org.json.JSONException;
import org.json.JSONObject;

// import java.io.IOException;
// import java.net.SocketTimeoutException;
// import java.util.concurrent.TimeoutException;


public class AsyncTaskRunner extends AsyncTask<Object, String, JSONArray> 

    // These will be set by doInBackground() according to what the fragment passed to it
    // I am declaring them as instance variables because I'll need them in the onPostExecute method too, so to have a ref to the frag to update. 
    // By the way, can I infer one from the other someway?

    Context contextRef;
    View viewRef;

    @Override
    protected void onPreExecute()
        Log.d("janfry", "AsyncTaskRunner: onPreExecute entered");
    

    @Override
    protected JSONArray doInBackground(Object... params)

        Log.d("mytag", "AsyncTaskRunner: doInBackground entered");

        contextRef = (Context) params[0]; 
        viewRef = (View) params[1];

        HttpResponse response;
        String str = "";
        JSONArray arrayOfJSONObjects = null;

        final HttpParams httpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
        HttpClient myClient = new DefaultHttpClient(httpParams);
        HttpGet myConnection = new HttpGet("http://123.123.123.123:80/notes.json");

        try 
            response = myClient.execute(myConnection);
            str = EntityUtils.toString(response.getEntity(), "UTF-8");
         catch (Throwable t) 
            t.printStackTrace();
        

        try
            arrayOfJSONObjects = new JSONArray(str);
         catch ( Throwable t)  t.printStackTrace(); 


        try 
            Log.d("mytag", arrayOfJSONObjects.getString(0));
         catch (Throwable t) 
            t.printStackTrace();
        
        return arrayOfJSONObjects;
    


    @Override
    protected void onProgressUpdate(String... notused)

    

    @Override
    protected void onPostExecute(JSONArray arrayOfJSONobjs) 


        Log.d("mytag", "AsyncTaskRunner: onPostExecute entered");

        TableLayout tab_lay = (TableLayout) viewRef.findViewById(R.id.musicTableLayout);
        tab_lay.removeAllViews();

        TextView[] arrayOfTextViews;
        arrayOfTextViews = new TextView[arrayOfJSONobjs.length()];    

        for(int pos = 0; pos < arrayOfJSONobjs.length(); pos++)              
            // and let's populate it with textviews...
            TextView textViewForObjName = new TextView(contextRef);
            try 
                JSONObject oneJsonObj;   // will hold the parsed JSON for one obj
                oneJsonObj = arrayOfJSONobjs.getJSONObject(pos);
                textViewForObjName.setText(oneJsonObj.getString("name"));

             catch (Throwable t) 
                t.printStackTrace();
            

            textViewForObjName.setHeight(Utils.dip(contextRef, 30));
            textViewForObjName.setBackgroundColor(Color.parseColor("#ABCABC"));

            // let's add the text_view we built to the Array
            arrayOfTextViews[pos] = textViewForObjName;  


            // we now have an array of textviews, that has not been added to the UI yet.


        // I want to populate the array with 3 textviews per row.
        // Is it a good idea to use this layout for this way of laying out content?
        // Would you have done that differently?

        TableRow table_row = new TableRow(contextRef);
        int col_counter = 0;
        for (TextView aTextView : arrayOfTextViews) 

            table_row.addView(aTextView);
            col_counter++;

            if (col_counter == 3) 
                tab_lay.addView(table_row);
                table_row = new TableRow(contextRef);
                col_counter = 0;
            
        

    



【问题讨论】:

好吧,我在 HTTP 代码中看不到任何问题。可能是您超出了 Apache 的DefaultHttpClient 或其他地方允许的并发连接的最大限制。您应该将您的 HTTP 客户端实现切换到 HttpUrlConnection,因为这是所有开发的重点。更好的是使用OkHttp library,这在很多地方都是一个改进,实际上是从 KitKat 4.4 开始支持HttpUrlConnection 这里唯一的内存泄漏是AsyncTaskActivity重启后保留的过时Context的临时强引用。您应该重新实现AsyncTask 以松散耦合到视图层,这也将允许它被重用而不是每次都重新启动。您的选择是:1) 使用AsyncTaskLoader(复杂,但语义正确)2) 为其提供对保留Fragment 的引用,这将始终提供最新的Activity 实例引用。 3) 使用事件总线通知视图层,而不是持有对它的引用。 关于您的代码问题:所有视图都包含对 Context 的引用,可以从 getContext() 方法中检索到,因此您无需将显式引用作为参数传递与ViewTableLayout 适用于表格布局。 GridLayout 是另一个更轻量级的替代方案,尽管它不支持权重系统。 垃圾收集除了会在OutOfMemoryError 被抛出之前执行之外没有任何保证。实际的启发式方法取决于实现。 Gingerbread 引入了并发和部分垃圾收集器,显示的行为似乎与此一致。无论如何,我看不出垃圾回收会如何影响 HTTP 连接。 谢谢,为什么不把这个放在答案中呢?它可能不完整,但现在它肯定值得一些 +1 :) 【参考方案1】:

@corsair992 暗示(只是猜测)“可能是您超出了 Apache 的 DefaultHttpClient 或其他地方允许的并发连接的最大限制。

我建议她/他将上述评论放在正确的答案中(连同他/她所做的其他观察),但她/他说这只是一个猜测,而不是一个完整的答案。

对我来说,这仍然是最合理的假设,我认为它应该得到充分的关注和关注,所以我自己将其保存为答案,以便可以进一步评论和扩展(请在我的问题下支持原始的 @corsair992 评论)。

【讨论】:

以上是关于AsyncTask *偶尔*遇到连接超时/错误问题、潜在的内存泄漏或 Android Studio 错误?的主要内容,如果未能解决你的问题,请参考以下文章

nginx常见错误104 110

Android - 为 AsyncTask 设置超时?

USB 连接的“信号量超时期限已过”错误

nas请求网络超时,错误2001

Mysql 数据库偶尔连接不上 怎么回事?

CommunicationException [根异常是ConnectException:连接超时]