【问题标题】:How to handle network calls inside a Fragment如何处理片段内的网络调用
【发布时间】:2014-11-20 21:44:39
【问题描述】:

我有以下情况:

我有一个Activity 承载一个ViewPager,我有4 个Fragments

开头的ViewPager包含片段A

当用户在ViewPager上滑动时片段B进入ViewPager,然后片段C片段D..等等……

现在,一旦 FragmentPagerAdapter 被实例化,至少会创建 2 个片段。

这带来了两个问题:

  1. 每个Fragment都需要进行网络调用,但我不想做不必要的(如果用户从不滑动到Fragment B,我不想为Fragment B进行网络调用 );
  2. 类似于 1.),我需要在 Fragment 执行网络调用时显示 ProgessDialog,但如果用户从未访问过 Fragment B,我不想显示来自 Fragment B 的对话框...

请问在这种情况下我应该使用什么样的模式?

活动

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);
}

【问题讨论】:

  • 我不明白您在寻找什么问题。如果文档中已经有标准方式,不是吗?
  • 使用RoboSpice等异步请求框架之一。片段可以发布请求、取消请求和重新收集请求,即使它们由于方向等变化而重新创建。
  • 失败的糟糕设计你的用户将不得不等待屏幕更新,因为它可以在屏幕外完成。
  • @danny117 不,这不是一个糟糕的设计。这是一个非常普遍的问题。假设您有一个有 5 个选项卡的应用程序,并且在每个选项卡中都有一个 ListView,每行都有一个图像,例如 google play、appstore 或许多其他应用程序。你打算什么时候加载这些标签的数据? “它可以在屏幕外完成”你能告诉我吗,因为我刚刚创建了这样一个,我必须采用这种方法,在每个片段中对用户真正可见我必须下载相关的列表视图图像和数据。
  • 我也认为这实际上是一个糟糕的设计,原因如下: 使用 ViewPager 时,预期行为是准备好下一个和上一个片段供使用。甚至设置了 UI 行为,因此用户可以查看下一个/上一个项目,并且他们希望数据在那里。我同意你的特殊情况可能需要这种不寻常的行为,但你不应该再次使用 ViewPager,因为你打破了它的预期模式。考虑改用 fm.replace(fragment)。

标签: android


【解决方案1】:

试试这个,在每个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) 祝你好运!
  • 不能依赖setUserVisibleHint
  • @PaulBurke 为什么? google 正是因为这些情况而添加了这个。您可以阅读文档以获取更多信息。
  • 我的错误。 setUserVisibleHint 默认为 true,但是,FragmentPagerAdapter 会更新此字段。因此,虽然setUserVisibleHint 本质上是无用的,但在寻呼机适配器中它变得有用。
  • @mmlooloo 实际上在尝试它似乎 isVisible() 总是返回 false ......奇怪
【解决方案2】:

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

您可以利用 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?

希望对你有帮助

【讨论】:

    【解决方案3】:

    让我向你介绍我的想法:

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

    您可以定义一个接口来保存网络 I/O 的方法,例如

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

    并在您的片段上实现它并处理它们自己的业务逻辑(网络调用)。

    在主 Activity 中,在 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),在回调里面获取当前的fragment并调用handle方法,不管是哪个fragment都不需要关心。

    记住handle方法是在Activity中调用的,而不是在fragment中调用的。所有的Network调用都是Interface的实现,这样在Activity中处理很容易。

    【讨论】:

    • 您当前在 onPageSelected() 中的代码将失败。简单地在适配器上调用getItem() 不会检索适配器使用的片段。
    • @Luksprog 详细说明为什么我无法获得 Fragment developer.android.com/reference/android/support/v4/app/…
    • 无论如何,在适配器中创建一个提供片段的方法很容易。
    • 解决方案很合理,+1,但这是标准解决方案吗?我不认为我是唯一一个面临这个问题的人,是否可能没有“标准”的方法来处理它?
    • 从我的角度来看,我建议您可以给自己更多的时间来考虑您的应用程序的最佳设计模式,但不要花太多时间寻找可能没有的“标准”方法不存在。经过一次又一次的重构,你会取得比以往更多的成就。
    【解决方案4】:

    从我的回答here中查看解决方案

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

    【讨论】:

    • 我是 Android 编程新手,但在我看来这是最好的方法!
    【解决方案5】:

    为 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);
        }
    }
    

    【讨论】:

    • 经过测试可以完美地推迟繁重的工作,直到绝对需要。
    【解决方案6】:

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

    您可以在此处添加逻辑来启动您的网络调用。 It guarantees that your fragment is in visible mode.

    看到这个

    但是,您可以使用 LoaderManagerAsyncTaskLoader 模式来优化网络调用逻辑。

    加载程序负责屏幕方向更改并为您缓存数据。这样网络调用就不会为相同的操作启动两次。

    来自 Android 文档

    在 Android 3.0 中引入,加载器使异步加载变得容易 活动或片段中的数据。装载机具有以下特点:

    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 
    

    当内容改变时。 当它们自动重新连接到最后一个加载器的光标时 在配置更改后重新创建。 因此,他们不需要重新查询他们的数据。

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

    我找到了一个 AsyncTaskLoader 和 LoaderManager @this link 的教程,下面是 一些来自教程的引用

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

    【讨论】:

    • "它保证你的片段处于可见模式。"对不起,但在这种情况下是不正确的。因为当您使用FragmentPagerAdapterFragmentStatePagerAdapter 时,两个相邻片段处于Resume 模式,如果您检查isVisible,则始终返回true。由于 UX,您无法阻止适配器加载邻居片段。这意味着setOffscreenPageLimit(0); 根本不起作用。
    • 确实@mmlooloo 说的是对的,刚刚实验过
    【解决方案7】:

    @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上查看完整的演示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-01
      • 2017-06-02
      • 1970-01-01
      • 2016-11-05
      相关资源
      最近更新 更多