如何在Fragment中处理网络调用

9
我有以下情况:
我有一个托管ViewPager的Activity,并且我有4个Fragments;
开始时,ViewPager包含Fragment A,
当用户在ViewPager上滑动时,Fragment B进入ViewPager,然后是Fragment C和Fragment D等等。
现在,只要FragmentPagerAdapter被实例化,至少会创建2个Fragments。
这带来了两个问题:
1.每个Fragment都需要执行网络调用,但我不想做不必要的操作(如果用户从未切换到Fragment B,则不想为其进行网络调用);
2.与1类似,当Fragment执行网络调用时,我需要显示ProgressDialog,但如果用户从未访问Fragment B,则不想从该Fragment显示对话框...
请问在这种情况下应该使用什么样的模式?
活动
public class PagerActivity extends ActionBarActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_layout);

ViewPager pager=(ViewPager)findViewById(R.id.pager);
TabPageIndicator tabs=(TabPageIndicator)findViewById(R.id.titles);

pager.setAdapter(buildAdapter());
tabs.setViewPager(pager);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}

FragmentPagerAdapter

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {


@Override
public int getCount() {
    return (4);
}

@Override
public Fragment getItem(int position) {


    if (position == 1) {
        if (dashbardFragment == null)
            dashbardFragment = DashBoardFragment.newInstance(position);
        return dashbardFragment;
    }
    if (position == 0) {
        if (listOfParticipantFragment == null)
            listOfParticipantFragment = ListOfParicipantsFragment
            .newInstance(position);
        return listOfParticipantFragment;
    }

}

1 碎片

public class ListOfParicipantsFragment extends Fragment {

public static ListOfParicipantsFragment newInstance(int position) {
    ListOfParicipantsFragment frag = new ListOfParicipantsFragment();
    return (frag);
}

public static String getTitle(Context ctxt, int position) {
    return myApplication.getContext().getResources().getString(R.string.list_of_participants_fragment_title);
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View result = inflater.inflate(R.layout.guest_list_fragment_layout,
            container, false);

    return (result);
}

1
我不明白你的问题想要什么。如果有标准方法,它应该已经在文档中了,不是吗? - user
使用其中一个异步请求框架,比如 RoboSpice 等。Fragments 可以发布请求、取消请求并且即使由于方向变化等原因被重新创建后也可以重新收集请求。 - S.D.
糟糕的设计会导致用户必须等待屏幕更新,而这本可以在屏幕外完成。 - danny117
1
@danny117 不,这不是一个糟糕的设计。这是一个非常普遍的问题。考虑你有一个应用程序,有5个选项卡,在每个选项卡中都有一个ListView,其中每行都有一个图像,类似于Google Play、Appstore或许多其他应用程序。你什么时候加载这些选项卡的数据?“它可以在屏幕外完成”,你能告诉我吗?因为我刚刚创建了一个这样的应用程序,我必须采取这种方法,即在每个真正对用户可见的片段中下载相关的listview图像和数据。 - mmlooloo
我也认为这是一个糟糕的设计,原因如下:当使用ViewPager时,预期行为是使下一个和上一个片段准备就绪。即使UI行为设置为用户可以查看下一个/上一个项目,并且他们期望数据在那里。我确实同意你的特殊情况可能需要这种不寻常的行为,但再次强调,您不应该使用ViewPager,因为您正在打破它的预期模式。考虑使用fm.replace(fragment)代替。 - Igor Čordaš
显示剩余2条评论
7个回答

8

尝试这个,在每个fragmentoverride方法中,当它可见时调用你的函数:

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(isVisible()){
        if(isVisibleToUser){
            Log.d("MyTag","My Fragment is visible");
        }else{
            Log.d("MyTag","My Fragment is not visible");
        }
    }
}

编辑

注意:仅在使用FragmentPagerAdapterFragmentStatePagerAdapter时才有用。


如果你想使用 getActivity,你应该先检查片段是否有活动。你可以像我的第一个 if 语句一样做,或者用 isResumed()(getActivity() != null) 替换它。祝你好运! - mmlooloo
不能依赖于 setUserVisibleHint - Paul Burke
@PaulBurke 为什么?Google添加了这个功能,正是为了应对这些情况。您可以阅读文档以获取更多信息。 - mmlooloo
1
我的错误。setUserVisibleHint 默认为 true,但是 FragmentPagerAdapter 会更新此字段。因此,虽然 setUserVisibleHint 本质上是无用的,但在分页适配器中变得有用。 - Paul Burke
@PaulBurke所说的是正确的,使用FragmentPagerAdapter时,setUserVisibleHint不会默认为true,因此可以使用它(无需使用默认为true的isVisible())。 - Lisa Anne
显示剩余2条评论

1

让我向您介绍我的想法:

  • getCurrentItem()
    ViewPager 的方法。
  • getItem(int position)
    FragmentPagerAdapter 的方法。返回与指定位置相关联的 Fragment。

您可以定义一个包含网络 I/O 方法的接口,例如:

public Interface INetworkOnFragment{
 void handle(){
         //...
           }  
}

在您的片段上实现它,并处理它们自己的业务逻辑(网络调用)。
在主活动中,像这样设置ViewPager对象上的ViewPager.OnPageChangeListener:
    pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener(){
public void onPageScrollStateChanged(int state){
//donothing
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
//donothing
}
public void onPageSelected(int position){
       INetworkOnFragment interface =(INetworkOnFragment) (pager.getAdapter().getItem(position));//get the current fragment and call handle method on it,dont need to care about whichever fragment it is . 
interface.handle()
}
});

最重要的是onPageSelected(int position),在回调函数中获取当前片段并调用其处理方法,不需要关心是哪个片段。
请记住,处理方法在Activity中调用而不是在片段中调用。所有网络调用都是接口的实现,这使得在Activity中处理它们变得容易。

1
@Luksprog 给我详细解释一下为什么我无法获取 Fragment http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html#getItem%28int%29 - Andrew Carl
无论如何,在适配器中创建一个方法来获取Fragment是很容易的。 - Lisa Anne
这个解决方案非常合理,加一分,但这是标准解决方案吗?我不认为我是唯一面临这个问题的人,难道没有处理它的“标准”方法吗? - Lisa Anne
3
在我看来,我建议您给自己更多时间来思考最佳的应用程序设计模式,但不要花太多时间寻找可能不存在的那种“标准”方法。经过多次重构,您将获得比以往任何时候都要多的成果。 - Andrew Carl
如果你调用了 onIntoView 接口,你就可以开始使用它来启动动画等操作。我还会添加一些内容来确保在调用该方法之前,片段已经实现了该接口。可以通过 OnIntoView.class.isAssignableFrom(fragment.getClass()) 来进行判断,以避免出现异常情况。 - danny117
显示剩余2条评论

1
创建一个页面进入视图方法,用于 FragmentStatePagerAdapter,当片段进入视图时调用片段上的方法。在片段中实现 OnPageIntoView 接口。
public class SomethingDifferent extends Fragment implements OnPageIntoView {
...
/*
 * Called when this page comes into view
 * 
 * @see com.gosylvester.bestrides.SettingFragmentPagerSupport.MyAdapter.
 * OnPageIntoView#onPageIntoView()
 */
@Override
public void onPageIntoView() {
    // this is just some random example code
    // that does some heavy lifting it only runs when the fragment
    // frist comes into view
    if (fragmentActivity != null) {
        if (lrc == null) {
            lrc = new ClientServiceLocationRecorder(
                    new WeakReference<Context>(
                            fragmentActivity.getApplicationContext()),
                    lrcCallback);
        }

        // get a status message from the location recorder
        lrc.sndMessageToLocationRecorder(ServiceLocationRecorder.MSG_RECORD_STATUS);
    }
}

创建一个自定义的FragmentStatePagerAdapter,重写setPrimaryItem方法,如果该对象可以转换为接口,则只调用一次该接口。
public static class MyAdapter extends FragmentStatePagerAdapter {

    public interface OnPageIntoView {
        public void onPageIntoView();
    }

    private Fragment mCurrentFragment;

    //bonus method to get the current fragment
    public Fragment getCurrentFragment() {
        return mCurrentFragment;
    }

    static int lastPosition = -1;

    @Override
    public void setPrimaryItem(ViewGroup container, int position,
            Object object) {
        //quickly determine if the primary item has changed
        //and one time only call through interface
        if (position != lastPosition) {
            lastPosition = position;
            //determine if this is fragment it should be but lets avoid 
            //class cast exceptions
            if (Fragment.class.isAssignableFrom(object.getClass())) {
                mCurrentFragment = ((Fragment) object);
                //determine if the onPageIntoView interface has
                //been implemented in the fragment
                //if so call the onPageIntoView
                if (OnPageIntoView.class.isAssignableFrom(mCurrentFragment
                        .getClass())) {
                    ((OnPageIntoView) mCurrentFragment).onPageIntoView();
                }
            }
        }
        super.setPrimaryItem(container, position, object);
    }
}

测试过,完美运行,可以将繁重的工作推迟到绝对需要的时候再进行。 - danny117

1

查看我的答案这里的解决方案

1)创建LifecycleManager接口,该接口将具有两个方法(onPauseFragmentonResumeFragment),每个ViewPager的Fragment都将实现它
2)让每个Fragment实现接口
3)在每个Fragment中实现接口方法 - 在onResumeFragment中启动AsyncTask
4)在ViewPager页面更改时调用接口方法,您可以在ViewPager上设置OnPageChangeListener,并在每次ViewPager显示另一页时获取回调
5)实现OnPageChangeListener以调用自定义生命周期方法


我是Android编程的新手,但这看起来对我来说是最好的方式! - Puran

1

基本上,你想做的是找出在滑动时当前正在查看的片段,然后进行网络调用。

你可以利用ViewPager监听器,在用户滑动到新页面时收到通知。文档:http://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html#onPageSelected(int)

这将给出视图的位置。但我假设你想要的是实际的片段,这有点棘手。

但是,在这里已经回答过了:Is it possible to access the current Fragment being viewed by a ViewPager?

希望能有所帮助。


0

对我来说似乎很容易,你需要的是Fragments onResume() 方法。只有当您的片段对用户可见时才会调用此方法。

在这里,您可以添加逻辑以启动网络调用。 它保证您的片段处于可见模式

看这个

enter image description here

然而,您可以使用 LoaderManagerAsyncTaskLoader 模式优化网络调用逻辑。
加载器负责处理屏幕方向更改缓存数据,以便对于相同的操作不会再次发起网络调用。
来自 Android 文档。

Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:

They are available to every Activity and Fragment.
They provide asynchronous loading of data.
They monitor the source of their data and deliver new results 

when the content changes. They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data.

您可以使用任何异步HTTP库进行网络调用,例如Retrofit

我在this link找到了一个AsyncTaskLoader和LoaderManager的教程,以下是教程中的一些引用

加载器并不是微不足道的,那么为什么要首先使用它们呢?嗯,在大多数情况下,您会在与您一直使用AsyncTasks的相同场景中使用它们;实际上,一些加载器子类扩展了AsyncTask。正如AsyncTasks用于执行任何长时间运行的操作,这些操作将占用用户线程并最终抛出可怕的应用程序无响应(ANR)一样,加载器以相同的方式以相同的目的执行。主要区别在于,加载器专门用于加载数据。因此,加载器提供了许多效率和便利性方面的好处。


“它保证您的片段处于可见模式。”很抱歉,在这种情况下,这是不正确的。因为当您使用FragmentPagerAdapterFragmentStatePagerAdapter时,两个相邻的片段处于“恢复”模式,如果您检查isVisible,它总是返回true。您无法防止适配器加载相邻的片段,因为这会影响用户体验。这意味着setOffscreenPageLimit(0);根本不起作用。 - mmlooloo
确实,@mmlooloo所说的是正确的,我刚刚做了实验。 - Lisa Anne

0
@Andrew Carl提供了很好的想法。我在我的项目中也使用了类似的方法。我认为这更加通用。
创建一个接口:
public interface ViewPagerFragment {
    void onSelected();

    void onDeselected();
}

还有这个常用的辅助函数:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;

public class ViewPagerHelper implements ViewPager.OnPageChangeListener {
    private final FragmentManager mFragmentManager;
    private final ViewPager mViewPager;
    private int mSelectedPage;

    public ViewPagerHelper(FragmentManager fragmentManager, ViewPager viewPager) {
        mFragmentManager = fragmentManager;
        mViewPager = viewPager;
        mSelectedPage = -1;
    }

    @Override
    public void onPageSelected(int position) {
        Fragment previous = findViewPagerChildFragment(mFragmentManager, mViewPager, mSelectedPage);
        if (previous instanceof ViewPagerFragment) {
            ((ViewPagerFragment) previous).onDeselected();
        }

        Fragment current = findViewPagerChildFragment(mFragmentManager, mViewPager, position);
        if (current instanceof ViewPagerFragment) {
            ((ViewPagerFragment) current).onSelected();
        }

        mSelectedPage = position;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        // empty
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        // empty
    }

    public static Fragment findViewPagerChildFragment(FragmentManager manager, ViewPager pager, int position) {
        if (pager == null) {
            return null;
        }

        String tag = "android:switcher:" + pager.getId() + ":" + position;
        return manager.findFragmentByTag(tag);
    }
}

现在你可以将它们用于任何目的:

片段:

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

public class MyFragment extends Fragment implements ViewPagerFragment {
    private boolean mSelected;

    public static MyFragment newInstance(int position) {
        Bundle args = new Bundle();
        args.putInt("position", position);

        MyFragment result = new MyFragment();
        result.setArguments(args);
        return result;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        TextView result = new TextView(inflater.getContext());
        result.setText("Position: " + getPosition());
        return result;
    }

    private int getPosition() {
        return getArguments().getInt("position");
    }

    @Override
    public void onSelected() {
        mSelected = true;
        start();
    }

    @Override
    public void onDeselected() {
        mSelected = false;
    }

    private void start() {
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Activity activity = getActivity();
                if (activity == null) {
                    return;
                }

                if (!mSelected) {
                    Toast.makeText(activity, "Fragment #" + getPosition() + " stopped", Toast.LENGTH_SHORT).show();
                    return;
                }

                TextView textView = (TextView) activity.findViewById(R.id.text);
                if (textView != null) {
                    textView.setText("Fragment #" + getPosition() + " works: " + System.nanoTime() % 10000);
                }

                handler.postDelayed(this, 150);
            }
        }, 150);
    }
}

活动:

import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;

public class MainActivity extends ActionBarActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
        viewPager.setOnPageChangeListener(new ViewPagerHelper(getSupportFragmentManager(), viewPager));
        viewPager.setAdapter(new MyAdapter(getSupportFragmentManager()));
    }
}

适配器:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class MyAdapter extends FragmentPagerAdapter {
    public MyAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(position);
    }

    @Override
    public int getCount() {
        return 42;
    }
}

请查看在 Github 上的完整演示。


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