AsyncTask 在 ViewPager 的每个页面上运行

Posted

技术标签:

【中文标题】AsyncTask 在 ViewPager 的每个页面上运行【英文标题】:AsyncTask runs on each page of the ViewPager 【发布时间】:2014-03-07 11:16:10 【问题描述】:

我有 3 个标签,例如 android development tutorial

现在我想做的很简单,我在每个页面上使用 Fragments。我想在每个页面上显示来自 rss 提要的不同内容。

问题是当我转到下一个选项卡时,它会运行上一个 Fragment 的 AsyncTask(位于 onCreateView 中)。

所以你从第 1 页开始,它会很好地加载内容。然后,当您转到第 2 页时,再次运行第 1 页的 Fragment 的 onCreateView。并且显然给出了 NullException。关键是它根本不应该在第 2 页运行第 1 页的 AsyncTask。

我认为不需要任何示例代码,如果需要告诉我您需要查看哪个部分。然后我将编辑我的问题。

ListFragment 中的 AsyncTask :

public class MyAsyncTask extends AsyncTask<List<String>, Void, List<String>>

    // List of messages of the rss feed
    private List<Message> messages;
    private volatile boolean running = true;

    @SuppressWarnings("unused")
    private WeakReference<NieuwsSectionFragment> fragmentWeakRef;

    private MyAsyncTask(NieuwsSectionFragment fragment)
    
        this.fragmentWeakRef = new WeakReference<NieuwsSectionFragment>(fragment);
    
    @Override
    protected void onPreExecute() 
    
        super.onPreExecute();

        mProgress.show();
       //  progress.setVisibility(View.VISIBLE); //<< set here
    
    @Override
    protected void onCancelled()
    
        Log.w("onCancelled", "now cancelled");
        running = false;
    
    @Override
    protected List<String> doInBackground(List<String>... urls)
    
        FeedParser parser = FeedParserFactory.getParser();
        messages = parser.parse();
        List<String> titles = new ArrayList<String>(messages.size());
        for (Message msg : messages)
        
            titles.add(msg.getTitle());
            // Log.w("doInBackground", msg.getTitle());
        
        return titles;
    

    @Override
    protected void onPostExecute(List<String> result)
    
        super.onPostExecute(result);            
        mProgress.dismiss();
        if (result != null)
        
            PostData data = null;
            listData = new PostData[result.size()];
            for (int i = 0; i < result.size(); i++)
            
                data = new PostData();                  
                data.postTitle = result.get(i);
                data.postThumbUrl = "http://igo.nl/foto/app_thumb/28991-Taxi-vast-na-poging-tot-nemen-van-sluiproute.jpg";                  
                listData[i] = data;
                Log.w("onPostExecute", "" + listData[i].postTitle);
            
            adapter = new PostItemAdapter (getActivity(), android.R.layout.simple_list_item_1, listData);               
            setListAdapter(adapter);
            adapter.notifyDataSetChanged(); 
        
    

它在一个方法内部被调用,该方法在 ListFragment 的 onCreateView 内部执行:

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

    startNewAsyncTask();
    View rootView = inflater.inflate(R.layout.fragment_section_nieuws, container, false);
    return rootView;


@SuppressWarnings("unchecked")
public void startNewAsyncTask()
       
    MyAsyncTask asyncTask = new MyAsyncTask(this);
    this.asyncTaskWeakRef = new WeakReference<MyAsyncTask>(asyncTask);
    asyncTask.execute();

LogCat:

【问题讨论】:

请发布您的代码并记录 cat ! 我不知道您希望看到代码的哪一部分,因为内容的加载是从第一页开始的。当 logcat 在不应该运行它的页面上运行 AsyncTask 时,它只会给出 NullPointerException,这是合乎逻辑的。 发布你的异步任务和从它调用的代码片段..连同你的日志猫错误。错误必须指出出现空指针的行。也发布该部分 在哪一行出现空指针异常? 它指向的行是适配器初始化的地方。我认为这是因为列表项不再可访问,但我可能错了。当我转到该页面时停止运行 AsyncTask 时,该错误应该消失了。 【参考方案1】:

尝试在onPostExecute() 之前使用isAdded()。如果片段当前已添加到其活动中,isAdded() 返回 true。

http://developer.android.com/reference/android/app/Fragment.html#isAdded()

@Override
protected void postExecute()
    if(isAdded())
        //perform Display Changes
    

【讨论】:

这正是我想要的。 (y)【参考方案2】:

移动你的

startNewAsyncTask(); 

到 onActivityCreated()

【讨论】:

在运行AsyncTask的下一个选项卡时会发生同样的事情。【参考方案3】:

我假设您使用 FragmentPagerAdapterViewPager

为了启用流畅的动画,ViewPager 默认保持当前片段和两个邻居处于恢复状态。所以onCreateView 不是开始AsyncTask 的最佳位置。

相反,您需要创建一个自定义侦听器接口。 ViewPager 中的fragments 应该实现它,并从ViewPager's OnPageChangeListener 调用新接口。

查看我对this question 的回答,或者您可以阅读整个教程here。

【讨论】:

【参考方案4】:

您收到该异常是因为您调用getActivity() 太早了。您应该在onActivityCreated() 之后执行此操作(请参阅this diagram)

在后台执行onCreateView() 很好,实际上是默认行为。问题是,ViewPager 被优化为在后台加载相邻不可见页面的内容以改善用户体验。你可以这样做: mViewPager.setOffscreenPageLimit(2);(默认值为 1)一次加载所有 3 个页面(1 个加载为当前可见,其他 2 个加载为优化)。或者将其设置为 0 以禁用此行为,但这不是最好的主意。

一般来说,您应该通过使片段的生命周期方法尽可能轻巧来兑现已加载的数据并且不要再次加载它。 2 页限制对于 3 页来说是可以的,但如果您有 10 页,则限制为 9 就太多了。

【讨论】:

【参考方案5】:

如果我对您的问题的理解正确,我认为您需要每个片段都有独特的内容吗?

尝试使用执行方法的可变参数。例如:

yourTask.execute(<some-unique-URL>, parameter, one-more-parameter);

通过这种方式,您可以为每个片段表单传递一个唯一的 URL,您可以获得您的内容。 我觉得你已经有了这个。 doInBackground 方法具有 URL 列表。您只需在 execute 方法中传递该信息并在 doInBackground 中使用它。

希望这会有所帮助!

【讨论】:

【参考方案6】:

从相邻的Fragments 运行AsyncTask 是正常的,因为ViewPager + PagerAdapter 组合可以加载当前、上一个和下一个Fragment

您应该关注问题,而不是阻止 AsyncTask 运行,而是让它运行而不抛出 NullPointerException

应该在onCreateView()里面调用下面的代码

adapter = new PostItemAdapter (getActivity(), android.R.layout.simple_list_item_1, myList);               
setListAdapter(adapter);

然后,onPostExecute()

myList.clear();
myList.addAll(listData);
adapter.notifyDataSetChanged();

【讨论】:

【参考方案7】:

ViewPager 将为相邻页面中的片段创建视图,并为不与当前页面相邻的片段销毁视图。因此,如果您从 page1->page2->page3->page2 导航,第 1 页的 onCreateView 将被调用。您可以使用 ViewPager.setOffscreenPageLimit 让 viewpager 在内存中保留更多页面。

fragmentPagerAdapter 保留片段对象。只有视图被破坏。因此,当 viewpage 重新创建 page1 的视图时,片段对象是相同的。因此,片段中的所有字段都将被保留。 与大多数没有实时数据的应用程序一样,不需要每次创建片段视图时都不加载数据,您可以在加载后将数据存储在片段对象中。然后,在 onCreateView/onActivityCreated 中启动 AsyncTask 之前,检查数据之前是否已经加载。

class PageFragment 

private List<String> mData;

...
void onActivityCreated() 
    if (data == null)  // OR if the data is expired
        startAsyncTask();
     else 
        updateViews();
    


void updateViews() 
    // Display mData in views


class LoadDataTask extends AsyncTask<List<String>, ..., ...> 

    ...
    void onPostExecute(List<String> result) 
        PageFragment.this.mData = result;
        PageFragment.this.updateViews();
    

我建议您使用加载器来加载片段的数据。出于您的目的,您可以将加载程序配置为仅加载一次数据。 这是一个关于加载器的很棒的教程。 http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html

在本教程中,加载器配置为在可用时立即返回先前的数据,然后在后台获取数据并在获取完成后返回。因此,UI 会在下载新数据后更新,但同时,它会在下载发生时显示之前的数据。

【讨论】:

【参考方案8】:

您可以使用另一个活动 - 此活动将运行 asynctask,然后移动到与您的片段相关的活动。这样,它应该只调用一次。

如果您需要使用此 AsyncTask 更新 Fragment UI,请使用静态方法通过 AsyncTask 调用。

【讨论】:

以上是关于AsyncTask 在 ViewPager 的每个页面上运行的主要内容,如果未能解决你的问题,请参考以下文章

如何在将 Tablayout 与 viewpager 一起使用时从另一个片段调用 AsyncTask?

带有适配器“”的 ViewPager 需要 AsyncTask 中的视图 ID

Asynctask 崩溃 ViewPager RecyclerView (Android)

如何从 ViewPager 的 onPageSelected AsyncTask 到 Fragments?

ViewPager的两个片段并行运行

在 AsyncTask 上尝试使用 SQLite 检索数据的奇怪行为