我在Android开发教程中有3个选项卡
现在我想要做的事情非常简单,我在每个页面上都使用Fragments。 我想在每个页面上显示来自RSS源的不同内容。
问题是当我去到下一个选项卡它运行前一个片段的AsyncTask(这是在onCreateView)。
所以,你从第一页开始就把内容加载好了。 然后当你去到页面2再次运行页面片段的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:
在onPostExecute()
之前尝试使用isAdded()
onPostExecute()
。 如果片段当前被添加到其活动,则isAdded()
返回true。
http://developer.android.com/reference/android/app/Fragment.html#isAdded()
@Override protected void postExecute(){ if(isAdded()){ //perform Display Changes } }
移动你的
startNewAsyncTask();
到onActivityCreated()
我假设你的ViewPager
使用FragmentPagerAdapter
。
要启用平滑的animation, ViewPager
默认保持当前片段和两个邻居处于恢复状态。 所以onCreateView
不是启动AsyncTask
的最佳地方。
相反,你需要创build一个自定义的监听器接口。 ViewPager
的fragments
应该实现它,并从ViewPager's
OnPageChangeListener
调用新的接口。
看看我对这个问题的回答,或者你可以在这里阅读整个教程。
这里棘手的一点是,在你进入下一个片段( Fragment 2
)之后,来自Fragment 1
AsyncTask
仍在运行。 你可能会想:“好吧,在这种情况下,我会停止onDestroyView
AsyncTask
”,但另一个棘手的问题是,在调用.cancel方法之后, AsyncTask
不会立即停止。 所以,问题是:在切换到Fragment 2
后, onPostExecute
引用了不存在的资源。 让我们来看看ArrayAdapter:310 :
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
很明显, context
是null
。 让我们深入一下,看看getActivity方法 – 它只是返回mActivity
字段的值。
在这种情况下为什么它是null
? 因为一旦Fragment
被移除, FragmentManager
调用initState ,它会显式地移除到Activity
对象的链接:
void initState() { ... mActivity = null; ... }
如何保护您的NPE
? 总是检查onPostExecute
为null
的getActivity
的结果。
你得到这个exception,因为你太早调用getActivity()
。 你应该在onActivityCreated()
之后做(见下图 )
在后台执行onCreateView()
是好的,实际上是默认的行为。 问题是, ViewPager
已经过优化,可以在后台加载邻居不可见页面的内容来提高用户体验。 你可以这样做: mViewPager.setOffscreenPageLimit(2);
(默认值为1)一次加载所有3个页面(1加载为当前可见,其他2为优化)。 或将其设置为0来禁用此行为,但这不是最好的主意。
一般来说,您应该为您的已加载数据取现,并且不要通过尽可能使您的片段的生命周期方法再次加载。 2的页面限制为3页是好的,但如果你有例如10页,限制9是太多了。
如果我已经理解你的问题,我认为你需要每个片段的独特内容吗?
尝试使用execute方法的variables参数。 例如:
yourTask.execute(<some-unique-URL>, parameter, one-more-parameter);
通过这种方式,您可以为每个片段表单传递一个唯一的URL,您可以获取您的内容。 我觉得你已经有了这个 doInBackground方法具有URL列表。 你只需要在执行方法中传递这些信息,并在doInBackground中使用它。
希望这可以帮助!
由于ViewPager
+ PagerAdapter
组合可以加载当前,上一个和下一个Fragment
,所以它从相邻Fragments
运行AsyncTask
是正常的。
你应该关注问题不要停止运行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();
ViewPager将为相邻页面中的片段创build视图,并销毁与当前页面不相邻的片段的视图。 因此,如果您从page1-> page2-> page3-> page2导航,则页面1的onCreateView
将被调用。 您可以使用ViewPager.setOffscreenPageLimit
让viewpager在内存中保留更多的页面。
fragmentPagerAdapter保留片段对象。 只有意见被破坏。 因此,当viewpage重新创buildpage1的视图时,fragment对象是一样的。 因此,片段中的所有字段将被保留。 与大多数没有实时数据的应用程序一样,每次创build片段视图时不需要加载数据,您可以在加载后将数据存储在片段对象中。 然后,在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(); } }
我build议你使用加载器来加载一个片段的数据。 出于您的目的,您可以configuration一个加载器来只加载一次数据。 这是一个很好的装载机教程。 http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html
在本教程中,加载器被configuration为立即返回以前的数据(如果可用),然后在后台获取数据并在获取完成后返回数据。 因此,新的数据被下载后,UI将会被更新,但是同时它会在下载发生的时候显示之前的数据。
您可以使用其他活动 – 该活动将运行asynctask,然后移至与片段相关的活动。 通过这种方式,它只应该调用一次。
如果您需要使用此AsyncTask更新Fragment UI,则使用静态方法通过AsyncTask进行调用。