【问题标题】:Android Development - Faulty implementation of AsyncTask in Fragment?Android 开发 - 片段中 AsyncTask 的错误实现?
【发布时间】:2014-03-10 11:58:45
【问题描述】:

我最近开始尝试在我的 Android 项目的 Fragments 中实现 AsyncTasks,并立即遇到了配置更改导致重大问题的常见错误。我在网上看到的方法并不容易融入我的 ViewPager 设置中,我利用我的知识将自己处理配置更改。

我的问题是:我的方法有什么危险吗?最大的担忧是内存泄漏,但我确保在 onDetach() 方法上将每个 created_View 都清空。

实施总结:

  • 将 Fragment 设置为 true,这样就不必重新创建它,也不会丢失重要数据。

  • 在 onCreateView() 中,当必须重新创建 Fragment 的视图时始终调用的代码部分,应用程序将检查其 AsyncTask 是否正在运行。如果是这样,则显示一个 IndeterminateProgressBar,并在它完成 onPostExecute 后,将其可见性更改为 GONE。

  • onDetach() 中,确保created_view View 设置为null,这样就不会出现与最初使用的Activity 相关的内存泄漏

  • 在配置更改之前的onAttach

代码

public class RosterFragment extends Fragment 
{

List<RosterMember> dataforroster = new ArrayList<RosterMember>(); //List that will hold the Roster objects retrieved from Parse database,
                                 //and later passed in to constructor for the RosterCustomArrayAdapter.
List<ParseUser> retrieved_list = new ArrayList<ParseUser>(); //List that will hold values retrieved from ParseUser Query. 
View createdView; //View that will be passed back with built RosterFragment
private ProgressBar roster_progress; //The indeterminate ProgressBar that will be displayed until the AsyncTask is finished downloading the roster.
boolean running_task;
private RosterAsyncTask get_roster;

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    get_roster = new RosterAsyncTask(); //Create new RosterAsyncTask instance.
    get_roster.execute();
    running_task = true;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) 
{
    createdView = inflater.inflate(R.layout.rosterfragment, container, false); //Inflate the fragment using the specific layout
    roster_progress = (ProgressBar) createdView.findViewById(R.id.loadingroster); //Find the ProgressBar in layout and set it to roster_progress.
    if(running_task == true)
    {
        roster_progress.setVisibility(View.VISIBLE);
    }
    else
    {
        fill_roster();
    }
    return createdView;
}

@Override
public void onDetach()
{
    super.onDetach();
    createdView = null;
}


public void fill_roster()
{
    if(!dataforroster.isEmpty())
    {
    //Get reference ListView in the inflated layout.
    ListView the_Roster = (ListView) createdView.findViewById(R.id.rostercoachofficers);
    //Create an instance of the RosterCustomArrayAdapter using the dataforroster List.
    RosterCustomArrayAdapter roster_Adapter = new RosterCustomArrayAdapter(getActivity(), dataforroster);
    //Sort the roster_Adapter so elements in ListView will be sorted alphabetically by first name.
    roster_Adapter.sort(new RosterComparator());    
    //Attach adapter to the ListView to populate its data.
    the_Roster.setAdapter(roster_Adapter);
    }

}


//AsyncTask responsible for downloading roster in background thread.
private class RosterAsyncTask extends AsyncTask<Void, Void , List<RosterMember>>
{


    //The operations to perform in the AsyncTask background thread. The results(the roster data downloaded) will be passed to onPostExecute.
    @Override
    protected List<RosterMember> doInBackground(Void... params)
    {
        SystemClock.sleep(10000);
        ParseQuery<ParseUser> query = ParseUser.getQuery(); //Get specific ParseQuery for ParseUsers.

        try 
        {
        retrieved_list = query.find(); //Initiate query.
        for(ParseUser current_user: retrieved_list) //For every ParseUser returned from query, create a new RosterMember using the ParseUser
                               //data and then add it to the dataforroster List.
            {
                RosterMember current_member = new RosterMember();
                current_member.username = current_user.getUsername();
                ParseFile parse_ByteArray = (ParseFile)current_user.get("profile_picture");
                Bitmap profile_Picture = BitmapFactory.decodeByteArray(parse_ByteArray.getData(), 0, parse_ByteArray.getData().length);
                current_member.profile_Picture = profile_Picture;
                current_member.title = current_user.getString("title");
                dataforroster.add(current_member);
            }




        } 

        //If problem occurred in query execution, use Toast to display error message.
        catch (ParseException e) 
        {
            Toast.makeText(getActivity(), "Error, " + e.getMessage(), Toast.LENGTH_LONG).show();
        }
        return dataforroster;
   }



  //Code to run in main UI thread once the doinBackground method is finished.
  @Override
  protected void onPostExecute(List<RosterMember> dataforroster) 
  {    
      running_task = false; 
      fill_roster();
      roster_progress.setVisibility(View.GONE);

  }



}

}    

【问题讨论】:

  • 您面临的问题是什么?
  • 感谢 Raghunandan 的快速回复。这不是一个真正的问题,应用程序已经与我在 AsyncTask 运行时尝试过的配置更改(屏幕方向)一起工作。但我想确保我设计的大纲是高效的,并且不会导致内存泄漏。这个内存泄漏是一个问题,因为我想验证旧的活动没有被排除在垃圾收集之外,因为仍然有对它们的引用。

标签: android android-fragments android-asynctask fragment


【解决方案1】:
public class RosterFragment extends Fragment 
{
    List<RosterMember> dataforroster = new ArrayList<RosterMember>(); //List that will hold the Roster objects retrieved from Parse database,
                                     //and later passed in to constructor for the RosterCustomArrayAdapter.
    List<ParseUser> retrieved_list = new ArrayList<ParseUser>(); //List that will hold values retrieved from ParseUser Query. 
    View createdView; //View that will be passed back with built RosterFragment
    private ProgressBar roster_progress; //The indeterminate ProgressBar that will be displayed until the AsyncTask is finished downloading the roster.
    boolean running_task;
    private RosterAsyncTask get_roster;
    private boolean successful_query;
    private String error_message;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        // Retain this fragment across configuration changes.
        setRetainInstance(true);
        get_roster = new RosterAsyncTask(); //Create new RosterAsyncTask instance.
        get_roster.execute();
        running_task = true;

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) 
    {
        createdView = inflater.inflate(R.layout.rosterfragment, container, false); //Inflate the fragment using the specific layout
        roster_progress = (ProgressBar) createdView.findViewById(R.id.loadingroster); //Find the ProgressBar in layout and set it to roster_progress.
        fill_roster();
        return createdView;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        createdView = null;
    }


    public void fill_roster()
    {

            if(running_task == true)
            {
                roster_progress.setVisibility(View.VISIBLE);
            }
            else
            {

                roster_progress.setVisibility(View.GONE);

                if(!dataforroster.isEmpty())//Get reference ListView in the inflated layout.
                {
                    ListView the_Roster = (ListView) createdView.findViewById(R.id.rostercoachofficers);
                    //Create an instance of the RosterCustomArrayAdapter using the dataforroster List.
                    RosterCustomArrayAdapter roster_Adapter = new RosterCustomArrayAdapter(getActivity(), dataforroster);
                    //Sort the roster_Adapter so elements in ListView will be sorted alphabetically by first name.
                    roster_Adapter.sort(new RosterComparator());    
                    //Attach adapter to the ListView to populate its data.
                    the_Roster.setAdapter(roster_Adapter);

                }
                else
                {
                    if(successful_query == false)
                    {
                        Toast.makeText(getActivity(), error_message, Toast.LENGTH_LONG).show();
                    }
                }
            }


    }




    //AsyncTask responsible for downloading roster in background thread.
    private class RosterAsyncTask extends AsyncTask<Void, Void , Void>
    {


        //The operations to perform in the AsyncTask background thread. The results(the roster data downloaded) will be passed to onPostExecute.
        @Override
        protected Void doInBackground(Void... params)
        {
            dataforroster.clear();
            ParseQuery<ParseUser> query = ParseUser.getQuery(); //Get specific ParseQuery for ParseUsers.

            try 
            {
            retrieved_list = query.find(); //Initiate query.
            for(ParseUser current_user: retrieved_list) //For every ParseUser returned from query, create a new RosterMember using the ParseUser
                                   //data and then add it to the dataforroster List.
                {
                    RosterMember current_member = new RosterMember();
                    current_member.username = current_user.getUsername();
                    ParseFile parse_ByteArray = (ParseFile)current_user.get("profile_picture");
                    Bitmap profile_Picture = BitmapFactory.decodeByteArray(parse_ByteArray.getData(), 0, parse_ByteArray.getData().length);
                    current_member.profile_Picture = profile_Picture;
                    current_member.title = current_user.getString("title");
                    dataforroster.add(current_member);
                }


            successful_query = true;        

            } 

            //If problem occurred in query execution, use Toast to display error message.
            catch (ParseException e) 
            {
                successful_query = false;
                error_message = "Error, " + e.getMessage();
            }
            return null;

       }



      //Code to run in main UI thread once the doinBackground method is finished.
      @Override
      protected void onPostExecute(Void ignore) 
      {    

          running_task = false; 

          if(getActivity()!=null)
          {
              fill_roster();
          }

      }



    }   
}

【讨论】:

    【解决方案2】:

    我可以看到的两个主要内容都与您的活动有关。你在 doInBackground() 中的 toast 需要从你的主线程中调用,所以我很确定这会失败。您传递给适配器的活动也与片段分离,因此每当您进行配置更改时将被销毁并且不再有效。

    为帮助防止任何强制关闭,您可能希望将 toast 替换为发送至 logcat 的消息,并在 onPostExecute 执行任何操作之前验证活动和 roster_progress 不为空。

    【讨论】:

    • 感谢 Jon 的快速回复。我将 Toast 从 doInBackground 更改为 onPostExecute。令我困惑的是,尽管我将活动传递给适配器,但目前没有强制关闭或崩溃。我尝试的配置更改是屏幕方向,并在睡眠计时器内进行了测试,没有任何问题。
    • 一切都是时间问题。大多数时候你可能没问题,但如果它在重新创建视图或附加新活动之前捕获它,你将获得 NullPointerException。
    • 我明白你现在在说什么,乔恩。如果我确实确定 onPostExecute 中的活动或 roster_progress 为空,您会建议在该代码段中发生什么?一个while循环继续运行直到这两个值都不为空?
    • 我会在那里忽略它并从 onCreateView 更新它。由于 onPostExecute 是在 UI 线程上运行的,因此您最终会遇到死锁,因为它不能在那里等待并同时创建新片段。
    • 根据我的设计完成方式,使用 onCreateView() 进行更新不一定有效。例如,如果有用户配置更改并且 Asynctask 仍在运行,则 Indeterminate ProgressBar 会与 onCreateView 一起显示,然后返回视图。如果没有发生其他配置更改,一旦 AsyncTask 完成运行,如何更新 View 以更改 ProgressBar 可见性?
    猜你喜欢
    • 1970-01-01
    • 2014-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多