简体   繁体   English

ASyncTask,隐藏片段,保留实例和屏幕方向更改

[英]ASyncTask, hidden fragments, retaining instances, and screen orientation changes

My setup is as follows. 我的设置如下。

I have a FragmentPagerAdapter called from my Activity which loads two fragments. 我有一个从我的Activity调用的FragmentPagerAdapter,它加载了两个片段。 This is setup within onCreate. 这是在onCreate中设置的。

In onResume I call an ASyncTask which loads data from a database, and then calls a callback in my activity onLoadComplete via a load data listener. 在onResume中,我调用ASyncTask从数据库加载数据,然后通过加载数据监听器在我的活动onLoadComplete中调用回调。

    @Override
public void onLoadComplete(JSONArray data) {
    // TODO Auto-generated method stub


    LocalFragment fragmentB = (LocalFragment)getSupportFragmentManager().findFragmentByTag(ListTag);
    fragmentB.setList(data);

    LMapFragment fragmentA = (LMapFragment)getSupportFragmentManager().findFragmentByTag(MapTag);

    GoogleMap our_map = fragmentA.getMap();
    fragmentA.plotP(myLocation,data);

}

The fragments are initialized by the Pager, and within each fragments code I set the respective tag eg in LocalFragment 片段由寻呼机初始化,并且在每个片段代码中,我设置相应的标签,例如在LocalFragment中

    @Override
public void onAttach(Activity activity) {
    // TODO Auto-generated method stub
    super.onAttach(activity);

    String myTag = getTag();



        ((PagerTest) activity).setListTag(myTag);
        Log.d("what",myTag);


}

This allows me to access the fragment, call a function within it which populates a list or populates a map. 这允许我访问片段,调用其中填充列表或填充地图的函数。 It works. 有用。

What I am now trying to do is account for screen orientation changes.. If while the ASyncTask is running the orientation is changed, the app crashes. 我现在要做的是考虑屏幕方向的变化。如果ASyncTask正在运行时方向已更改,则应用程序崩溃。

As suggested here: Hidden Fragments I have been trying to implement a hidden fragment which saves the state of my ASyncTask. 正如这里所建议的: Hidden Fragments我一直在尝试实现一个隐藏的片段,它保存了我的ASyncTask的状态。 So what I have done is set it up so in onResume of my Activity i call a function 所以我所做的就是设置它,所以在我的Activity的onResume中调用一个函数

    static LoadDataFromURL the_data = null;
static JSONArray pub_data = null;
private static final String TAG = "RetainFragment";

public RetainFragment() {}

public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {

    RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
    if (fragment == null) {
        fragment = new RetainFragment();
        fm.beginTransaction().add(fragment, TAG).commit(); // add this
    }
    return fragment;
}

which essentially saves my data. 这基本上保存了我的数据。

Basically what this means is that if i rotate my screen i dont call my ASyncTask again.. the screen just rotates.. it works perfectly. 基本上这意味着,如果我旋转我的屏幕,我不会再次调用我的ASyncTask ..屏幕只是旋转..它完美地工作。

If however I go back to the main menu and then click on the activity again the screen returns blank (but does not crash). 但是,如果我返回主菜单然后再次单击活动,屏幕将返回空白(但不会崩溃)。 My understanding is that the data is retained as an object in the fragment, but on reloading the activity afresh the data needs to be set again.. IE onLoadComplete needs to be called to populate the list/map.. 我的理解是数据作为片段中的对象保留,但重新重新加载活动时,需要再次设置数据。需要调用IE onLoadComplete来填充列表/映射。

So i concluded that if initially after the ASyncTask completes i save the returned data in my hidden fragment onRetainInstance, then i could simply call onLoadComplete and pass it.. 所以我总结说,如果最初在ASyncTask完成之后我将返回的数据保存在我的隐藏片段onRetainInstance中,那么我可以简单地调用onLoadComplete并传递它。

The problem is, in this situation seemingly the fragment has not been called yet, as such the tags are null, and calling the callbacks within onLoadComplete crashes the app. 问题是,在这种情况下,似乎尚未调用片段,因此标记为null,并且在onLoadComplete中调用回调会使应用程序崩溃。

I have been banging my head over this for ages. 多年来我一直在为此而奋斗。

My ASyncTask is in a seperate class: LoadDataFromURL What i want to achieve is as follows - a fragmentviewpager whereby on screen rotate the ASyncTask is retained on rotate/attached to the new activity, and if it has completed before it shouldn't be run again.. 我的ASyncTask是一个单独的类:LoadDataFromURL我想要实现的是如下 - 一个fragmentviewpager,在屏幕上旋转ASyncTask保留在旋转/附加到新活动,如果它已经完成,它不应该再次运行..

Could anyone advise. 任何人都可以建议。

Many Thanks 非常感谢

EDIT 编辑

Having changed the variables in my secret fragment to public variables, everything has seemingly come together.. BUT because im not 100% sure how/when things are called, I dont fully understand WHY it works.. 将我的秘密片段中的变量更改为公共变量后,所有内容似乎都聚集在一起。但是因为我不是100%确定如何/何时调用事物,我不完全理解为什么它起作用..

So.. I call findOrCreateRetainFragment and it either creates a new 'secret' fragment or returns the current instance. 所以..我调用findOrCreateRetainFragment,它或者创建一个新的'secret'片段或者返回当前实例。

If it is returning a current instance, i dont call my async task again. 如果它返回当前实例,我不会再次调用我的异步任务。 If it is not, I call my asynctask and load the data. 如果不是,我调用我的asynctask并加载数据。

With this setup, when i load the activity and rotate the screen, it rotates as expected woop. 使用此设置,当我加载活动并旋转屏幕时,它会按预期旋转。

Now, when i go back to the main menu and then click the activity again, it calls the async task. 现在,当我返回主菜单然后再次单击活动时,它会调用异步任务。

My understanding is that on rotate the async task is not called again, and the viewpager is somehow saving the fragments. 我的理解是,在旋转时,不会再次调用异步任务,并且viewpager以某种方式保存片段。

On the other hand, when i go back my activity is destroyed, as is my secret fragment, and as such when i click on it again it loads the data. 另一方面,当我回去时,我的活动被破坏,就像我的秘密片段一样,当我再次点击它时,它会加载数据。 THis is essentially what i want.. 这基本上就是我想要的......

Have i understood this correctly? 我理解正确吗?

Thanks 谢谢

There are a few issues here that you're experiencing (I think). 这里有一些你遇到的问题(我认为)。

First of all, the reason your callbacks crash is because they're attached to an old Activity that no longer "exists" after a screen orientation and/or Activity push. 首先,回调崩溃的原因是因为它们附加到屏幕方向和/或Activity推送后不再“存在”的旧Activity If you use onAttach() to attach a callback to your fragment, you must use onDetach() to detach that callback when the Fragment is removed from the Activity . 如果使用onAttach()将回调附加到片段,则必须使用onDetach()在从Activity删除片段时分离该回调。 Then, whenever you call the callback, check for a null so you don't send data to a dead object. 然后,每当您调用回调时,请检查null,以便不将数据发送到死对象。

Basically, the logic you're trying to use here is: 基本上,你在这里尝试使用的逻辑是:

  1. Start Activity. 开始活动。

  2. Check if your Fragment exists. 检查您的碎片是否存在。 If it does, grab it. 如果是的话,抓住它。 Else, create it. 否则,创造它。

  3. Retrieve the data if it exists. 检索数据(如果存在)。 If not, wait for the callback. 如果没有,请等待回调。

Because of the nature of callbacks (depending on your implementation), you will not receive data until the event fires. 由于回调的性质(取决于您的实现),在事件触发之前您不会收到数据。 However, if the Activity is gone and the event has already fired, the callback won't execute. 但是,如果Activity已经消失并且事件已经触发,则不会执行回调。 Thus, you have to retrieve the data manually. 因此,您必须手动检索数据。 When using setRetainInstance() , it's helpful to think of it as this entity detatched from your Activity. 使用setRetainInstance() ,将其视为从您的Activity中分离出来的实体是有帮助的。 It will exist as long as you don't pop the current Activity or push a new Activity . 只要您不弹出当前Activity或推送新Activity它就会存在。 However, your current Activity will be destroyed upon screen orientation changes while the Fragment won't. 但是,当屏幕方向更改时,您的当前Activity将被销毁,而Fragment则不会。 As such, the Fragment shouldn't rely on the existence of the Activity . 因此, Fragment不应该依赖于Activity的存在。

A much more elegant solution to the problem that you may want to look in to is implementing the Android Loader API. 您可能希望查看的问题的更优雅的解决方案是实现Android Loader API。 Loaders are handy tools that are handled by the system that work is roughly the same way but are more in-tune with asynchronously retrieving data. 加载程序是由系统处理的方便工具,其工作方式大致相同,但与异步检索数据更加协调。 They work effectively the same way. 他们以同样的方式有效地工作。 You simply start your loader and the system with either create one if it doesn't exist or re-use one that already exists. 您只需启动您的加载器和系统,如果它不存在则创建一个或重新使用已存在的系统。 It will remain in the system by the LoaderManager upon configuration changes. 配置更改后, LoaderManager将保留在系统中。

EDIT: 编辑:

To answer your edit, I guess I'll explain what's happening. 要回答你的编辑,我想我会解释发生了什么。 It's convoluted, so just tell me if anything needs clarification. 这是令人费解的,所以只要告诉我是否需要澄清。

Fragments aren't technically speaking part of your currently running Activity . 技术上讲 ,碎片不是您当前运行的Activity When you create an instance of the Fragment, you have to call beginTransation() and commit() on the FragmentManager. 创建Fragment的实例时,必须在FragmentManager上调用beginTransation()commit() The FragmentManager is a singleton that exists within the realm of your application. FragmentManager是一个存在于应用程序领域内的单例。 The FragmentManager holds on to the instance of the Fragment for you. Fragment为您保留Fragment的实例。 The FragmentManager then attaches the Fragment to your Activity (see onAttach() ). 然后FragmentManager将Fragment附加到您的Activity(请参阅onAttach() )。 The Fragment then exists within the FragmentManager which is why you never really have to hold a reference to it within your application. Fragment然后存在于FragmentManager中,这就是为什么你永远不必在你的应用程序中持有对它的引用。 You can just call findFragmentByTag/Id() to retrieve it. 您可以调用findFragmentByTag/Id()来检索它。

Under normal circumstances, when your Activity is being destroyed, the FragmentManager will detach the instance of your Fragment (see onDetach() ) and just let it go. 在正常情况下,当您的Activity被销毁时,FragmentManager将分离Fragment的实例(请参阅onDetach() )并让它继续。 The Java garbage collect will detect that no reference to your Fragment exists and will clean it up. Java垃圾收集器将检测到没有对Fragment的引用,并将清理它。

When you call setRetainInstace() , you're telling the FragmentManager to hold on to it. 当你调用setRetainInstace() ,你告诉FragmentManager保持它。 Thus, when your Activity is being destroyed on a configuration change, the FragmentManager will hold on to the reference of your Fragment. 因此,当您的Activity在配置更改时被销毁时,FragmentManager将保留Fragment的引用。 Thus when your Activity is rebuilt, you can call findFragmentByTag/Id() to retrieve the last instance. 因此,重建Activity时,可以调用findFragmentByTag/Id()来检索最后一个实例。 So long as it didn't keep any context of the last Activity, there shouldn't be any problems. 只要它没有保留最后一个Activity的任何上下文,就不应该有任何问题。

Traditionally, one would use it to keep references to long standing data (as you are) or to keep connection sockets open so a phone flip doesn't delete it. 传统上,人们会用它来保持对长期数据的引用(就像你一样)或保持连接套接字打开,这样手机翻转就不会删除它。

Your ViewPager has nothing to do with this. 您的ViewPager与此无关。 How it retrieves the Fragments is completely dependent on how you implement that Adapter that it's attached to. 它如何检索Fragments完全取决于您如何实现它附加到的Adapter。 Usually, retained Fragments don't have Views themselves because Views hold Context data of the Activity they were created in. You would just basically want to make it a data bucket to hold on to the data for the Views to pull from when they're being inflated. 通常,保留的片段本身没有视图,因为视图保存了创建它们的活动的上下文数据。您基本上只想使它成为一个数据桶,以保持视图从它们被拉出时的数据被夸大了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM