141

在開發者控制檯錯誤報告中,有時我會看到具有NPE問題的報告。我不明白我的代碼有什麼問題。在模擬器和我的設備應用程序沒有強制關閉的情況下工作良好,但是有些用戶在調用getActivity()方法時會在fragment類中得到NullPointerException。Android。片段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(); 
     ... 
    } 
} 

也許當應用程序從後臺恢復這個錯誤發生。在這種情況下,我應該如何正確處理這種情況?

+0

我想出了一個問題,但沒有解決方案。我不知道爲什麼,但片段恢復早期活動。這隻會發生在我的應用程序在最近的應用程序列表中的最後位置時,它似乎會破壞我的應用程序。 – 2012-07-24 15:40:54

+1

當我從後臺fragmetn繼續我的應用程序onCreate一個onResume調用之前activity onCreate/onResume方法。它似乎有些分離的片段仍然活着,並試圖恢復。此字符串中的 – 2012-07-25 09:05:38

+1

firstTask.setTaskListener((TaskListener)adapter.getItem(0)); adapter.getItem(0)返回舊的片段,適配器不會刪除片段correctrly – 2012-07-25 15:19:51

回答

106

看來,我找到了解決我的問題。 非常好的解釋給出herehere。 這是我的例子:

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

而且,使用片段時檢查getActivity前isAdded()被稱爲一個很好的做法。這有助於避免片段與活動分離時出現空指針異常。例如,一個活動可能包含推送異步任務的片段。任務完成後,將調用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(); 
    } 

} 

如果我們打開片段,推一個任務,然後快速按返回,返回到以前的活動,當任務完成後,它會嘗試通過調用getActivity訪問onPostExecute()活動() 方法。如果活動已經分離,並且此檢查不存在:

if (isAdded()) 

然後應用程序崩潰。

+46

雖然這很令人討厭,但在每次訪問之前不得不調用'isAdded()'來使代碼變得醜陋。 – Ixx 2014-02-28 21:59:19

+13

if(isAdded())或if(getActivity()!= null)似乎沒有太大區別' – StackOverflowed 2015-05-29 15:12:44

9

擺脫這種情況的最好方法是在onAttach被調用時保留活動引用,並在需要時使用活動引用,例如,

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

    @Override 
    public void onDetach() { 
     super.onDetach(); 
     mActivity = null; 
    } 
+5

片段始終保持其父活動的引用並使您可以使用getActivity()方法,這裏我們保持相同的參考。 – 2013-09-11 06:31:15

+6

如果你需要你的片段與活動共享事件,Google實際上會推薦這個。 http://developer.android.com/guide/components/fragments.html (查找「爲事件創建事件回調」) – Vering 2013-10-30 11:52:23

+5

您可能想要添加onDetach方法,這會使活動引用無效 – midnight 2014-03-04 14:30:58

8

不要在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(); 
} 
+0

其實,我有一個類似的問題,因爲我正在片段構造函數中啓動任務。非常感謝。 – 2016-06-01 23:36:33

15

好吧,我知道這個問題實際上已經解決了,但我決定分享我的解決方案。我創建抽象父類爲我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; 
     } 
    } 
} 

正如你可以看到,我添加了一個偵聽器,所以,每當我需要得到FragmentsActivity而不是標準getActivity(),我需要調用

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

很好的回答!應該被標記爲正確的,因爲它解決了真正的問題:在我的情況下,僅僅檢查getActivity()是否爲空是不夠的,因爲無論如何我都必須完成我的任務。我正在使用它,它完美的作品。 – 2016-08-05 03:23:19

+0

謝謝。這真的很有幫助。 – TruongHieu 2017-02-27 06:37:47

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

您能詳細解釋一下您提供的解決方案嗎? – abarisone 2016-06-15 06:32:14

1

我知道這是一個老問題,但我想我必須爲我的回答是,因爲我的問題不是由別人來解決。

首先:我使用fragmentTransactions動態添加片段。其次:我的片段使用AsyncTasks(服務器上的DB查詢)進行了修改。 第三:我的片段沒有在活動開始時實例化第四:我使用了一個自定義片段實例「創建或加載它」以獲得片段變量。 第四:活動被重新創建,因爲方向變化

問題是我想「刪除」片段,因爲查詢答案,但片段被錯誤地創建之前。我不知道爲什麼,可能是因爲「提交」在後面完成,這個片段在刪除它時還沒有添加。因此,getActivity()返回null。

解決方法: 1)我必須檢查我是否正確試圖找到片段的第一個實例創建前,一個新的 2)我不得不把serRetainInstance(真)上的片段,以保持它通過方向改變(因此不需要後臺堆棧因此沒有問題) 3)在「移除它」之前,我直接將該片段放在活動開始處,而不是「重新創建或獲取舊片段」。 在活動開始時將其實例化,而不是在移除它之前「加載」(或實例化)片段變量,以防止發生getActivity問題。

2

我一直在battling this kind of problem一段時間,我想我已經想出了一個可靠的解決方案。

這是非常難以確切知道this.getActivity()不會返回nullFragment,尤其是當你處理任何類型的網絡行爲,這使你的代碼足夠的時間來收回Activity引用。

在下面的解決方案中,我聲明瞭一個名爲ActivityBuffer的小型管理類。實質上,這個class涉及維護對擁有Activity的可靠參考,並承諾在有效的參考可用時,在有效的Activity上下文中執行Runnable。如果Context可用,則會立即在UI線程上執行Runnable s,否則將推遲執行,直到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上述行爲一致:你Fragment

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

} 

最後,在任何領域延伸BaseFragment,您對getActivity()的呼叫不值得信賴,只需致電this.getActivityBuffer().safely(...)並聲明即可完成任務!

然後確保您的void run(final Activity pActivity)的內容沿UI線程執行。

相關問題