Android Fragment getActivity () บางครั้งส่งคืนค่าว่าง


194

ในรายงานข้อผิดพลาดคอนโซลนักพัฒนาซอฟต์แวร์บางครั้งฉันเห็นรายงานที่มีปัญหา NPE ฉันไม่เข้าใจสิ่งที่ผิดปกติกับรหัสของฉัน ในอีมูเลเตอร์และแอปพลิเคชันอุปกรณ์ของฉันทำงานได้ดีโดยไม่ต้องใช้แรงกดอย่างไรก็ตามผู้ใช้บางคนได้รับ NullPointerException ในคลาสแฟรกเมนต์เมื่อเรียกเมธอด getActivity ()

กิจกรรม

pulic class MyActivity extends FragmentActivity{

    private ViewPager pager; 
    private TitlePageIndicator indicator;
    private TabsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        pager = (ViewPager) findViewById(R.id.pager);
        indicator = (TitlePageIndicator) findViewById(R.id.indicator);
        adapter = new TabsAdapter(getSupportFragmentManager(), false);

        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
        indicator.notifyDataSetChanged();
        adapter.notifyDataSetChanged();

        // push first task
        FirstTask firstTask = new FirstTask(MyActivity.this);
        // set first fragment as listener
        firstTask.setTaskListener((TaskListener) adapter.getItem(0));
        firstTask.execute();
    }

    indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener()  {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
    });
}

คลาส AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

    @Override
    protected void onPostExecute(T result) {
        ... 
        taskListener.onTaskComplete(result);
    }   
}

คลาสแฟรกเมนต์

public class FirstFragment extends Fragment immplements Taskable, TaskListener{

    public FirstFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.first_view, container, false);
    }

    @Override
    public void executeTask() {
        FirstTask firstTask = new FirstTask(MyActivity.this);
        firstTask.setTaskListener(this);
        firstTask.execute();
    }

    @Override
    public void onTaskComplete(T result) {
        // NPE is here 
        Resources res = getActivity().getResources();
        ...
    }
}

อาจเกิดข้อผิดพลาดนี้เมื่อแอปพลิเคชันทำงานต่อจากพื้นหลัง ในกรณีนี้ฉันจะจัดการกับสถานการณ์นี้อย่างเหมาะสมได้อย่างไร


ฉันพบปัญหา แต่ไม่สามารถแก้ไขได้ ฉันไม่รู้ว่าทำไม แต่ชิ้นส่วนกลับมาทำกิจกรรมก่อนหน้านี้ต่อ และสิ่งนี้จะเกิดขึ้นก็ต่อเมื่อแอปของฉันอยู่ในตำแหน่งล่าสุดในรายการแอพล่าสุดดูเหมือนว่าระบบจะทำลายแอปพลิเคชันของฉัน
Georgy Gobozov

1
เมื่อฉันเรียกคืนแอปพลิเคชันของฉันจากพื้นหลัง Fragmetn onCreate เรียกใช้ onResume ก่อนกิจกรรมบนเมธอด onCreate / onResume ดูเหมือนว่าชิ้นส่วนที่แยกออกมาบางส่วนยังคงมีชีวิตอยู่และพยายามดำเนินการต่อ
Georgy Gobozov

1
ในสตริงนี้ firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) ส่งคืน
แฟรกเมนต์

9
กิจกรรมที่ยอดเยี่ยมโดยวิธี :) คำถามถามความคิดเห็นด้านซ้ายและคำตอบที่ได้รับ - ทั้งหมดทำโดยคนเดียว! +1 สำหรับสิ่งเหล่านี้
Prizoff

บันทึกบริบท (getActivity ()) ใน onCreateView () เช่นนี้จะถูกเรียกว่าเมื่อมีการสร้างมุมมองในกรณีพื้นหลัง
หม่า

คำตอบ:


123

ดูเหมือนว่าฉันพบวิธีแก้ไขปัญหาของฉัน คำอธิบายที่ดีมากที่จะได้รับที่นี่และที่นี่ นี่คือตัวอย่างของฉัน:

pulic class MyActivity extends FragmentActivity{

private ViewPager pager; 
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;

 @Override
public void onCreate(Bundle savedInstanceState) {

    .... 
    this.savedInstanceState = savedInstanceState;
    pager = (ViewPager) findViewById(R.id.pager);;
    indicator = (TitlePageIndicator) findViewById(R.id.indicator);
    adapter = new TabsAdapter(getSupportFragmentManager(), false);

    if (savedInstanceState == null){    
        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
    }else{
        Integer  count  = savedInstanceState.getInt("tabsCount");
        String[] titles = savedInstanceState.getStringArray("titles");
        for (int i = 0; i < count; i++){
            adapter.addFragment(getFragment(i), titles[i]);
        }
    }


    indicator.notifyDataSetChanged();
    adapter.notifyDataSetChanged();

    // push first task
    FirstTask firstTask = new FirstTask(MyActivity.this);
    // set first fragment as listener
    firstTask.setTaskListener((TaskListener) getFragment(0));
    firstTask.execute();

}

private Fragment getFragment(int position){
     return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}

private String getFragmentTag(int position) {
    return "android:switcher:" + R.id.pager + ":" + position;
}

 @Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tabsCount",      adapter.getCount());
    outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}

 indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
 });

แนวคิดหลักในรหัสนี้คือในขณะที่ใช้งานแอปพลิเคชันของคุณตามปกติคุณจะสร้างแฟรกเมนต์ใหม่และส่งไปยังอะแดปเตอร์ เมื่อคุณเริ่มต้นตัวแยกส่วนแอปพลิเคชันของคุณต่อไปจะมีอินสแตนซ์ของแฟรกเมนต์นี้อยู่แล้วและคุณต้องรับมันจากตัวจัดการแฟรกเมนต์และส่งต่อไปยังอะแด็ปเตอร์

UPDATE

นอกจากนี้เป็นวิธีปฏิบัติที่ดีเมื่อใช้แฟรกเมนต์เพื่อตรวจสอบ isAdded ก่อนที่จะเรียก getActivity () สิ่งนี้จะช่วยหลีกเลี่ยงข้อยกเว้นตัวชี้ null เมื่อแฟรกเมนต์ถูกแยกออกจากกิจกรรม ตัวอย่างเช่นกิจกรรมอาจมีส่วนที่ผลักงาน async เมื่องานเสร็จสิ้นผู้ฟัง onTaskComplete จะถูกเรียกใช้

@Override
public void onTaskComplete(List<Feed> result) {

    progress.setVisibility(View.GONE);
    progress.setIndeterminate(false);
    list.setVisibility(View.VISIBLE);

    if (isAdded()) {

        adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
        list.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }

}

ถ้าเราเปิดแฟรกเมนต์ให้กดภารกิจจากนั้นกดกลับอย่างรวดเร็วเพื่อกลับไปที่กิจกรรมก่อนหน้าเมื่องานเสร็จแล้วมันจะพยายามเข้าถึงกิจกรรมใน onPostExecute () โดยเรียกเมธอด getActivity () หากกิจกรรมถูกถอดออกแล้วและไม่มีการตรวจสอบนี้:

if (isAdded()) 

แอปพลิเคชันจึงขัดข้อง


56
แม้ว่ามันจะน่ารำคาญเพราะต้องโทรisAdded()ก่อนเข้าถึงแต่ละครั้ง ... ทำให้โค้ดน่าเกลียด
Ixx

25
ดูเหมือนจะไม่มีความแตกต่างระหว่างการมีif(isAdded())หรือif(getActivity() != null)
StackOverflow

19

ตกลงฉันรู้ว่าคำถามนี้ได้รับการแก้ไขจริง แต่ฉันตัดสินใจที่จะแบ่งปันทางออกของฉันสำหรับเรื่องนี้ ฉันได้สร้างคลาส parent หลักสำหรับฉันFragment:

public abstract class ABaseFragment extends Fragment{

    protected IActivityEnabledListener aeListener;

    protected interface IActivityEnabledListener{
        void onActivityEnabled(FragmentActivity activity);
    }

    protected void getAvailableActivity(IActivityEnabledListener listener){
        if (getActivity() == null){
            aeListener = listener;

        } else {
            listener.onActivityEnabled(getActivity());
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) activity);
            aeListener = null;
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) context);
            aeListener = null;
        }
    }
}

อย่างที่คุณเห็นฉันได้เพิ่มผู้ฟังดังนั้นเมื่อใดก็ตามที่ฉันจะต้องได้รับFragments Activityมากกว่ามาตรฐานgetActivity()ฉันจะต้องโทรหา

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });

คำตอบที่ดี! ควรทำเครื่องหมายว่าถูกต้องเนื่องจากแก้ไขปัญหาจริง: ในกรณีของฉันไม่เพียงพอที่จะตรวจสอบว่า getActivity () ไม่ใช่โมฆะเพราะฉันต้องทำงานให้เสร็จไม่ว่าจะเกิดอะไรขึ้น กำลังใช้สิ่งนี้และทำงานได้อย่างสมบูรณ์
Hadas Kaminsky

18

สิ่งที่ดีที่สุดในการกำจัดสิ่งนี้คือการเก็บการอ้างอิงกิจกรรมเมื่อonAttachมีการโทรและใช้การอ้างอิงกิจกรรมในทุกที่ที่ต้องการเช่น

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    mContext = context;
}

@Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}

แก้ไขเนื่องจากonAttach(Activity)ถูกคิดค่าเสื่อมราคาและตอนนี้onAttach(Context)กำลังใช้งานอยู่


9
แฟรกเมนต์จะเก็บการอ้างอิงกิจกรรมของกิจกรรมหลักเสมอและทำให้คุณพร้อมใช้งานด้วยเมธอด getActivity () ที่นี่เรายังคงใช้การอ้างอิงเดียวกัน
Pawan Maheshwari

8
Google แนะนำให้ทำเช่นนี้หากคุณต้องการให้ส่วนของคุณแบ่งปันกิจกรรมกับกิจกรรม developer.android.com/guide/components/fragments.html (มองหา "การสร้างการโทรกลับไปยังกิจกรรม")
Vering

6
คุณอาจต้องการเพิ่มวิธี onDetach ซึ่งลบล้างการอ้างอิงกิจกรรม
เที่ยงคืน

2
ใช่เริ่มต้น mActivity = null ในวิธี onDetach เพื่อลบล้างการอ้างอิงกิจกรรมนั้น
Pawan Maheshwari

19
ไม่เคยทำอย่างนั้น คุณกำลังทำกิจกรรมทั้งหมดของคุณ (และกับแผนผังเลย์เอาต์ทั้งหมดพร้อม drawable และเช่นนั้น) หากgetActivity()ส่งคืนค่า null อาจเป็นเพราะคุณไม่ได้อยู่ในกิจกรรมแล้ว นี่เป็นวิธีแก้ปัญหาที่สกปรก
njzk2

10

อย่าเรียกวิธีการภายใน Fragment ที่ต้องการ getActivity () จนกระทั่ง onStart ในกิจกรรมหลัก

private MyFragment myFragment;


public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    myFragment = new MyFragment();

    ft.add(android.R.id.content, youtubeListFragment).commit();

    //Other init calls
    //...
}


@Override
public void onStart()
{
    super.onStart();

    //Call your Fragment functions that uses getActivity()
    myFragment.onPageSelected();
}

ที่จริงฉันมีปัญหาที่คล้ายกันเพราะฉันเริ่มงานในตัวสร้างชิ้นส่วน ขอบคุณมาก.
Supreme Dolphin

4

ฉันต่อสู้กับปัญหาแบบนี้มาระยะหนึ่งแล้วและฉันคิดว่าฉันคิดวิธีแก้ปัญหาที่เชื่อถือได้

มันค่อนข้างยากที่จะทราบว่าthis.getActivity()จะไม่กลับมาnullอีกต่อFragmentไปโดยเฉพาะถ้าคุณต้องรับมือกับพฤติกรรมเครือข่ายใด ๆ ที่ให้รหัสของคุณมีเวลาเพียงพอในการถอนActivityการอ้างอิง

ActivityBufferในการแก้ปัญหาด้านล่างผมประกาศการจัดการเรียนขนาดเล็กที่เรียกว่า เป็นหลักclassข้อตกลงนี้กับการรักษาความน่าเชื่อถือการอ้างอิงถึงการเป็นเจ้าของActivityและสัญญาว่าจะดำเนินการRunnableภายในActivityบริบทที่ถูกต้องเมื่อใดก็ตามที่มีการอ้างอิงที่ถูกต้อง Runnables ที่กำหนดไว้สำหรับการดำเนินการในหัวข้อ UI ทันทีถ้าContextใช้ได้มิฉะนั้นการดำเนินการจะเลื่อนไปก่อนจนกว่าที่Contextมีความพร้อม

/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {

    /** A class which defines operations to execute once there's an available Context. */
    public interface IRunnable {
        /** Executes when there's an available Context. Ideally, will it operate immediately. */
        void run(final Activity pActivity);
    }

    /* Member Variables. */
    private       Activity        mActivity;
    private final List<IRunnable> mRunnables;

    /** Constructor. */
    public ActivityBuffer() {
        // Initialize Member Variables.
        this.mActivity  = null;
        this.mRunnables = new ArrayList<IRunnable>();
    }

    /** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
    public final void safely(final IRunnable pRunnable) {
        // Synchronize along the current instance.
        synchronized(this) {
            // Do we have a context available?
            if(this.isContextAvailable()) {
                // Fetch the Activity.
                final Activity lActivity = this.getActivity();
                // Execute the Runnable along the Activity.
                lActivity.runOnUiThread(new Runnable() { @Override public final void run() { pRunnable.run(lActivity); } });
            }
            else {
                // Buffer the Runnable so that it's ready to receive a valid reference.
                this.getRunnables().add(pRunnable);
            }
        }
    }

    /** Called to inform the ActivityBuffer that there's an available Activity reference. */
    public final void onContextGained(final Activity pActivity) {
        // Synchronize along ourself.
        synchronized(this) {
            // Update the Activity reference.
            this.setActivity(pActivity);
            // Are there any Runnables awaiting execution?
            if(!this.getRunnables().isEmpty()) {
                // Iterate the Runnables.
                for(final IRunnable lRunnable : this.getRunnables()) {
                    // Execute the Runnable on the UI Thread.
                    pActivity.runOnUiThread(new Runnable() { @Override public final void run() {
                        // Execute the Runnable.
                        lRunnable.run(pActivity);
                    } });
                }
                // Empty the Runnables.
                this.getRunnables().clear();
            }
        }
    }

    /** Called to inform the ActivityBuffer that the Context has been lost. */
    public final void onContextLost() {
        // Synchronize along ourself.
        synchronized(this) {
            // Remove the Context reference.
            this.setActivity(null);
        }
    }

    /** Defines whether there's a safe Context available for the ActivityBuffer. */
    public final boolean isContextAvailable() {
        // Synchronize upon ourself.
        synchronized(this) {
            // Return the state of the Activity reference.
            return (this.getActivity() != null);
        }
    }

    /* Getters and Setters. */
    private final void setActivity(final Activity pActivity) {
        this.mActivity = pActivity;
    }

    private final Activity getActivity() {
        return this.mActivity;
    }

    private final List<IRunnable> getRunnables() {
        return this.mRunnables;
    }

}

ในแง่ของการดำเนินการเราต้องใช้ความระมัดระวังในการใช้วิธีการวงจรชีวิตให้สอดคล้องกับพฤติกรรมที่อธิบายข้างต้นโดยPawan M :

public class BaseFragment extends Fragment {

    /* Member Variables. */
    private ActivityBuffer mActivityBuffer;

    public BaseFragment() {
        // Implement the Parent.
        super();
        // Allocate the ActivityBuffer.
        this.mActivityBuffer = new ActivityBuffer();
    }

    @Override
    public final void onAttach(final Context pContext) {
        // Handle as usual.
        super.onAttach(pContext);
        // Is the Context an Activity?
        if(pContext instanceof Activity) {
            // Cast Accordingly.
            final Activity lActivity = (Activity)pContext;
            // Inform the ActivityBuffer.
            this.getActivityBuffer().onContextGained(lActivity);
        }
    }

    @Deprecated @Override
    public final void onAttach(final Activity pActivity) {
        // Handle as usual.
        super.onAttach(pActivity);
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextGained(pActivity);
    }

    @Override
    public final void onDetach() {
        // Handle as usual.
        super.onDetach();
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextLost();
    }

    /* Getters. */
    public final ActivityBuffer getActivityBuffer() {
        return this.mActivityBuffer;
    }

}

ในที่สุดในพื้นที่ใด ๆ ภายในของคุณFragmentที่ขยายBaseFragmentว่าคุณไม่น่าเชื่อถือเกี่ยวกับการโทรgetActivity()เพียงแค่โทรthis.getActivityBuffer().safely(...)และประกาศActivityBuffer.IRunnableสำหรับงาน!

เนื้อหาของคุณvoid run(final Activity pActivity)จะได้รับการรับรองให้ดำเนินการตาม UI Thread

ActivityBufferนั้นจะสามารถใช้ดังนี้

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);

คุณสามารถเพิ่มตัวอย่างการใช้เมธอด this.getActivityBuffer (). safe (... ) ได้ไหม
fahad_sust

3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}

คุณช่วยอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับโซลูชันที่คุณให้ได้ไหม
abarisone

1

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันคิดว่าฉันต้องตอบคำถามของฉันเพราะคนอื่นไม่ได้แก้ปัญหาของฉัน

ก่อนอื่น: ฉันเพิ่มชิ้นส่วนแบบไดนามิกโดยใช้ FragmentTransactions ที่สอง: ชิ้นส่วนของฉันถูกแก้ไขโดยใช้ AsyncTasks (แบบสอบถาม DB บนเซิร์ฟเวอร์) ที่สาม: ชิ้นส่วนของฉันไม่ได้เริ่มการทำงานทันทีที่เริ่มต้นกิจกรรมที่สี่: ฉันใช้การสร้างอินสแตนซ์ชิ้นส่วนที่กำหนดเอง "สร้างหรือโหลด" เพื่อรับตัวแปรชิ้นส่วน ที่สี่: กิจกรรมถูกสร้างขึ้นใหม่เนื่องจากการเปลี่ยนแปลงการวางแนว

ปัญหาคือว่าฉันต้องการที่จะ "ลบ" ส่วนเพราะคำตอบแบบสอบถาม แต่ส่วนที่ถูกสร้างขึ้นไม่ถูกต้องมาก่อน ฉันไม่ทราบสาเหตุอาจเป็นเพราะการ "ส่ง" ดำเนินการในภายหลังชิ้นส่วนยังไม่ถูกเพิ่มเมื่อถึงเวลาที่จะลบออก ดังนั้น getActivity () จึงส่งคืน null

วิธีแก้ปัญหา: 1) ฉันต้องตรวจสอบว่าฉันพยายามค้นหาอินสแตนซ์แรกของแฟรกเมนต์อย่างถูกต้องก่อนที่จะสร้างอันใหม่ 2) ฉันต้องใส่ serRetainInstance (จริง) ลงในแฟรกเมนต์นั้นเพื่อให้มันเปลี่ยนทิศทาง จึงไม่จำเป็นต้องมีปัญหา) 3) แทนที่จะ "สร้างใหม่หรือรับชิ้นส่วนเก่า" ก่อน "เอาออก" ฉันวางส่วนที่จุดเริ่มต้นของกิจกรรมโดยตรง การสร้างอินสแตนซ์ที่กิจกรรมเริ่มต้นแทนการ "โหลด" (หรือสร้างอินสแตนซ์) ตัวแปรชิ้นส่วนก่อนที่จะลบออกจะป้องกันปัญหา getActivity


0

ใน Kotlin คุณสามารถลองวิธีนี้เพื่อจัดการกับ getActivity () เงื่อนไขว่าง

   activity.let { // activity == getActivity() in java

        //your code here

   }

มันจะตรวจสอบกิจกรรมที่เป็นโมฆะหรือไม่และหากไม่เป็นโมฆะให้รันโค้ดภายใน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.