Android. Fragmen getActivity () terkadang menghasilkan null

194

Dalam laporan kesalahan konsol pengembang terkadang saya melihat laporan dengan masalah NPE. Saya tidak mengerti apa yang salah dengan kode saya. Pada emulator dan aplikasi perangkat saya berfungsi dengan baik tanpa forceclose, namun beberapa pengguna mendapatkan NullPointerException di kelas fragmen ketika metode getActivity () dipanggil.

Aktivitas

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

Kelas AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

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

Kelas fragmen

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

Mungkin kesalahan ini terjadi ketika aplikasi dilanjutkan dari latar belakang. Dalam hal ini bagaimana saya harus menangani situasi ini dengan benar?

Georgy Gobozov
sumber
Saya menemukan masalah, tetapi bukan solusi. Saya tidak tahu mengapa tetapi fragmen melanjutkan aktivitas sebelumnya. Dan ini hanya terjadi ketika aplikasi saya pada posisi terakhir dalam daftar aplikasi baru-baru ini, tampaknya sistem menghancurkan aplikasi saya.
Georgy Gobozov
1
Ketika saya melanjutkan aplikasi saya dari latar belakang fragmetn onCreate onResume dipanggil sebelum aktivitas metode onCreate / onResume. Tampaknya beberapa fragmen terpisah masih hidup dan mencoba untuk melanjutkan.
Georgy Gobozov
1
dalam string ini firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) mengembalikan fragmen lama, adaptor tidak menghapus fragmen dengan benar
Georgy Gobozov
9
Aktivitas hebat dengan cara :) pertanyaan yang diajukan, komentar yang tersisa dan jawaban yang diberikan - semua dilakukan oleh satu orang! +1 untuk ini.
Prizoff
simpan Konteks (getActivity ()) di onCreateView () karena ini disebut ketika tampilan dibuat kembali dalam kasus latar belakang.
sha

Jawaban:

123

Tampaknya saya menemukan solusi untuk masalah saya. Penjelasan yang sangat bagus diberikan di sini dan di sini . Ini contoh saya:

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

Gagasan utama dalam kode ini adalah, saat menjalankan aplikasi Anda secara normal, Anda membuat fragmen baru dan meneruskannya ke adaptor. Ketika Anda melanjutkan manajer fragmen aplikasi Anda sudah memiliki instance fragmen ini dan Anda perlu mendapatkannya dari manajer fragmen dan meneruskannya ke adaptor.

MEMPERBARUI

Juga, ini adalah praktik yang baik ketika menggunakan fragmen untuk memeriksa isAdded sebelum getActivity () dipanggil. Ini membantu menghindari pengecualian penunjuk nol ketika fragmen terlepas dari aktivitas. Misalnya, suatu aktivitas bisa berisi fragmen yang mendorong tugas async. Ketika tugas selesai, pendengar onTaskComplete dipanggil.

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

}

Jika kami membuka fragmen, mendorong tugas, dan kemudian dengan cepat tekan kembali untuk kembali ke aktivitas sebelumnya, ketika tugas selesai, ia akan mencoba mengakses aktivitas di onPostExecute () dengan memanggil metode getActivity (). Jika aktivitas sudah terlepas dan pemeriksaan ini tidak ada:

if (isAdded()) 

kemudian aplikasi macet.

Georgy Gobozov
sumber
56
Ini menjengkelkan, harus menelepon isAdded()sebelum setiap akses ... membuat kode jelek.
Ixx
25
Tampaknya tidak ada banyak perbedaan antara memiliki if(isAdded())atauif(getActivity() != null)
StackOverflows
19

Oke, saya tahu bahwa pertanyaan ini sebenarnya sudah diselesaikan tetapi saya memutuskan untuk membagikan solusi saya untuk ini. Saya telah membuat kelas induk abstrak untuk 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;
        }
    }
}

Seperti yang Anda lihat, saya telah menambahkan pendengar jadi, setiap kali saya harus mendapatkan Fragments Activityalih-alih standar getActivity(), saya harus menelepon

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });
Paul Freez
sumber
Jawaban bagus! harus ditandai sebagai yang benar karena itu memecahkan masalah nyata: Dalam kasus saya itu tidak cukup untuk memeriksa bahwa getActivity () tidak nol karena saya harus menyelesaikan tugas saya tidak peduli apa. Saya menggunakan ini dan berfungsi dengan baik.
Hadas Kaminsky
18

Cara terbaik untuk menghilangkannya adalah dengan menyimpan referensi aktivitas saat onAttachdipanggil dan menggunakan referensi aktivitas di mana pun dibutuhkan, misalnya

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

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

Diedit, karena onAttach(Activity)didepresiasi & sekarang onAttach(Context)sedang digunakan

Pawan Maheshwari
sumber
9
Fragmen selalu menyimpan referensi aktivitas induknya dan membuat Anda tersedia dengan metode getActivity (), di sini kami menyimpan referensi yang sama.
Pawan Maheshwari
8
Google sebenarnya merekomendasikan ini jika Anda membutuhkan fragmen Anda untuk berbagi acara dengan aktivitas. developer.android.com/guide/components/fragments.html (lihat "Membuat panggilan balik aktivitas ke aktivitas")
Vering
6
Anda mungkin ingin menambahkan metode onDetach, yang membatalkan referensi aktivitas
tengah malam
2
ya inisialisasi mActivity = null pada metode onDetach untuk membatalkan referensi aktivitas itu.
Pawan Maheshwari
19
tidak pernah melakukan itu. Anda membocorkan aktivitas lengkap Anda (dan bersamanya pohon tata letak keseluruhan, dengan drawables dan semacamnya). Jika getActivity()mengembalikan nol, itu karena Anda tidak lagi dalam suatu kegiatan. Ini adalah solusi yang kotor.
njzk2
10

Jangan panggil metode dalam Fragmen yang memerlukan getActivity () hingga onStart di Aktivitas induk.

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();
}
bvmobileapps
sumber
Sebenarnya, saya memiliki masalah yang sama karena saya memulai tugas di konstruktor fragmen. Terima kasih banyak.
Supreme Dolphin
4

Saya telah berjuang melawan masalah semacam ini untuk sementara waktu, dan saya pikir saya telah menemukan solusi yang dapat diandalkan.

Sangat sulit untuk mengetahui pasti bahwa this.getActivity()tidak akan kembali nulluntuk Fragment, terutama jika Anda berurusan dengan segala jenis perilaku jaringan yang memberikan kode Anda cukup waktu untuk menarik Activityreferensi.

Dalam solusi di bawah ini, saya mendeklarasikan kelas manajemen kecil yang disebut ActivityBuffer. Pada dasarnya, ini classberkaitan dengan mempertahankan referensi yang dapat diandalkan untuk memiliki Activity, dan berjanji untuk mengeksekusi Runnabledalam Activitykonteks yang valid setiap kali ada referensi yang valid tersedia. The Runnables dijadwalkan untuk eksekusi pada UI Thread segera jika Contexttersedia, jika tidak eksekusi ditangguhkan sampai yang Contextsiap.

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

}

Dalam hal implementasinya, kita harus berhati-hati untuk menerapkan metode siklus hidup agar sesuai dengan perilaku yang dijelaskan di atas oleh 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;
    }

}

Akhirnya, di area mana pun di dalam diri Anda yang Fragmentmenyatakan BaseFragmentbahwa Anda tidak dapat dipercaya tentang panggilan untuk getActivity(), cukup lakukan panggilan ke this.getActivityBuffer().safely(...)dan nyatakanActivityBuffer.IRunnable untuk tugas itu!

Konten Anda void run(final Activity pActivity)kemudian dijamin untuk dieksekusi di sepanjang UI Thread.

The ActivityBufferkemudian dapat digunakan sebagai berikut:

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);
Mapsy
sumber
Bisakah Anda menambahkan contoh menggunakan metode this.getActivityBuffer (). Safe (...).
fahad_sust
3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
Mohanraj Balasubramaniam
sumber
Bisakah Anda menjelaskan lebih lanjut jawaban Anda dengan menambahkan sedikit deskripsi tentang solusi yang Anda berikan?
abarisone
1

Saya tahu ini adalah pertanyaan lama tetapi saya pikir saya harus memberikan jawaban saya karena masalah saya tidak diselesaikan oleh orang lain.

pertama-tama: saya menambahkan fragmen secara dinamis menggunakan fragmentTransactions. Kedua: fragmen saya dimodifikasi menggunakan AsyncTasks (permintaan DB pada server). Ketiga: fragmen saya tidak dipakai pada aktivitas mulai Keempat: saya menggunakan instantiasi fragmen khusus "buat atau muat" untuk mendapatkan variabel fragmen. Keempat: aktivitas diciptakan kembali karena perubahan orientasi

Masalahnya adalah saya ingin "menghapus" fragmen karena jawaban kueri, tetapi fragmen itu dibuat secara tidak benar sebelumnya. Saya tidak tahu mengapa, mungkin karena "komit" dilakukan nanti, fragmen belum ditambahkan ketika tiba saatnya untuk menghapusnya. Oleh karena itu getActivity () mengembalikan nol.

Solusi: 1) Saya harus memeriksa bahwa saya benar mencoba untuk menemukan contoh pertama dari fragmen sebelum membuat yang baru 2) Saya harus meletakkan serRetainInstance (true) pada fragmen itu agar tetap melalui perubahan orientasi (tidak ada backstack) dibutuhkan karena itu tidak ada masalah) 3) Alih-alih "membuat ulang atau mendapatkan fragmen lama" sebelum "menghapusnya", saya langsung meletakkan fragmen itu di awal kegiatan. Instantiasi saat aktivitas dimulai alih-alih "memuat" (atau instantiasi) variabel fragmen sebelum menghapusnya mencegah masalah getActivity.

Feuby
sumber
0

Di Kotlin Anda dapat mencoba cara ini untuk menangani getActivity () kondisi nol.

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

        //your code here

   }

Ini akan memeriksa aktivitas adalah nol atau tidak dan jika tidak nol maka jalankan kode dalam.

Sachin
sumber