Android. Fragment getActivity () đôi khi trả về null


194

Trong các báo cáo lỗi bảng điều khiển dành cho nhà phát triển đôi khi tôi thấy các báo cáo có vấn đề về NPE. Tôi không hiểu điều gì sai với mã của tôi. Trên trình giả lập và ứng dụng thiết bị của tôi hoạt động tốt mà không bị tịch thu, tuy nhiên một số người dùng nhận được NullPulumException trong lớp phân đoạn khi phương thức getActivity () được gọi.

Hoạt động

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

Lớp AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

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

Lớp mảnh

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

Có thể lỗi này xảy ra khi các ứng dụng được nối lại từ nền. Trong trường hợp này tôi nên xử lý tình huống này như thế nào cho đúng?


Tôi đã tìm ra một vấn đề, nhưng không phải là giải pháp. Tôi không biết tại sao nhưng mảnh lại tiếp tục hoạt động trước đó. Và điều này chỉ xảy ra khi ứng dụng của tôi ở vị trí cuối cùng trong danh sách các ứng dụng gần đây, có vẻ như hệ thống sẽ phá hủy ứng dụng của tôi.
Georgy Gobozov

1
Khi tôi tiếp tục ứng dụng của mình từ nền Fragmetn onCreate một onResume được gọi trước khi hoạt động phương thức onCreate / onResume. Có vẻ như một số mảnh vỡ tách ra vẫn còn sống và cố gắng tiếp tục.
Georgy Gobozov

1
trong chuỗi này FirstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) trả lại đoạn cũ, bộ điều hợp không loại bỏ các đoạn chính xác
Georgy Gobozov

9
Bằng cách này, hoạt động tuyệt vời :) câu hỏi được hỏi, ý kiến ​​để lại và câu trả lời được đưa ra - tất cả được thực hiện bởi một người duy nhất! +1 cho những điều này.
Giải thưởng

lưu bối cảnh (getActivity ()) trong onCreateView () vì điều này được gọi khi chế độ xem được tạo lại trong trường hợp nền.
sha

Câu trả lời:


123

Có vẻ như tôi đã tìm thấy một giải pháp cho vấn đề của mình. Giải thích rất tốt được đưa ra ở đâyở đây . Đây là ví dụ của tôi:

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

Ý tưởng chính trong mã này là, trong khi chạy ứng dụng của bạn một cách bình thường, bạn tạo các đoạn mới và chuyển chúng cho bộ điều hợp. Khi bạn tiếp tục trình quản lý phân đoạn ứng dụng của bạn đã có phiên bản phân đoạn này và bạn cần lấy nó từ trình quản lý phân đoạn và chuyển nó đến bộ điều hợp.

CẬP NHẬT

Ngoài ra, đó là một cách thực hành tốt khi sử dụng các đoạn để kiểm tra isAdded trước khi getActivity () được gọi. Điều này giúp tránh một ngoại lệ con trỏ null khi đoạn bị tách ra khỏi hoạt động. Ví dụ: một hoạt động có thể chứa một đoạn đẩy một tác vụ không đồng bộ. Khi tác vụ kết thúc, trình nghe onTaskComplete được gọi.

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

}

Nếu chúng ta mở đoạn, đẩy một tác vụ, rồi nhanh chóng nhấn trở lại để quay lại hoạt động trước đó, khi tác vụ kết thúc, nó sẽ cố gắng truy cập hoạt động trong onPostExecute () bằng cách gọi phương thức getActivity (). Nếu hoạt động đã được tách ra và kiểm tra này không có:

if (isAdded()) 

sau đó ứng dụng gặp sự cố.


56
Mặc dù vậy, điều này thật khó chịu, phải gọi isAdded()trước mỗi lần truy cập ... làm cho mã trở nên xấu xí.
Ixx

25
Dường như không có nhiều sự khác biệt giữa việc có if(isAdded())hoặcif(getActivity() != null)
StackOverflow

19

Ok, tôi biết rằng câu hỏi này thực sự đã được giải quyết nhưng tôi đã quyết định chia sẻ giải pháp của mình cho việc này. Tôi đã tạo lớp cha mẹ trừu tượng cho tôi 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;
        }
    }
}

Như bạn có thể thấy, tôi đã thêm một người nghe vì vậy, bất cứ khi nào tôi cần nhận được Fragments Activitythay vì tiêu chuẩn getActivity(), tôi sẽ cần gọi

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

Câu trả lời chính xác! nên được đánh dấu là đúng vì nó giải quyết được vấn đề thực sự: Trong trường hợp của tôi, việc kiểm tra getActivity () không phải là không đủ vì tôi phải hoàn thành nhiệm vụ của mình không có vấn đề gì. Đang sử dụng này và nó hoạt động hoàn hảo.
Hadas Kaminsky

18

Cách tốt nhất để thoát khỏi điều này là giữ tham chiếu hoạt động khi onAttachđược gọi và sử dụng tham chiếu hoạt động bất cứ khi nào cần, ví dụ:

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

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

Đã chỉnh sửa, vì đã onAttach(Activity)khấu hao & hiện onAttach(Context)đang được sử dụng


9
Các đoạn luôn giữ tham chiếu hoạt động chính của nó và làm cho bạn có sẵn với phương thức getActivity (), ở đây chúng tôi đang giữ cùng một tham chiếu.
Pawan Maheshwari

8
Google thực sự khuyến nghị điều này nếu bạn cần đoạn của mình để chia sẻ các sự kiện với hoạt động. developer.android.com/guide/components/fragments.html (tìm kiếm "Tạo cuộc gọi lại sự kiện cho hoạt động")
Vering

6
bạn có thể muốn thêm phương thức onDetach, vô hiệu hóa tham chiếu hoạt động
nửa đêm

2
yeah khởi tạo mActivity = null trên phương thức onDetach để vô hiệu hóa tham chiếu hoạt động đó.
Pawan Maheshwari

19
không bao giờ làm điều đó bạn đang rò rỉ hoạt động hoàn chỉnh của mình (và với nó là toàn bộ cây bố cục, với các hình vẽ và như vậy). Nếu getActivity()trả về null, đó là vì bạn không còn hoạt động nữa. Đây là một cách giải quyết bẩn.
njzk2

10

Đừng gọi các phương thức trong Fragment yêu cầu getActivity () cho đến khi bắt đầu trong Hoạt động chính.

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

Trên thực tế, tôi đã có một vấn đề tương tự vì tôi đang bắt đầu nhiệm vụ trong trình xây dựng phân đoạn. Cảm ơn rất nhiều.
Cá heo tối cao

4

Tôi đã chiến đấu với loại vấn đề này trong một thời gian và tôi nghĩ rằng tôi đã đưa ra một giải pháp đáng tin cậy.

Nó khá khó để biết chắc chắn rằng this.getActivity()sẽ không trở lại nullcho một Fragment, đặc biệt là nếu bạn đang làm việc với bất kỳ loại hành vi mạng mà cung cấp cho mã của bạn thời gian dư dật để rút Activitytài liệu tham khảo.

Trong giải pháp dưới đây, tôi khai báo một lớp quản lý nhỏ gọi là ActivityBuffer. Về cơ bản, điều này classliên quan đến việc duy trì một tài liệu tham khảo đáng tin cậy để sở hữu Activityvà hứa sẽ thực thi Runnables trong Activitybối cảnh hợp lệ bất cứ khi nào có sẵn tài liệu tham khảo hợp lệ. Các Runnables được lên lịch để thực hiện trên UI Thread ngay lập tức nếu Contextcó sẵn, nếu không thì thực thi được hoãn lại cho đến khi nó Contextsẵn sàng.

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

}

Về mặt triển khai, chúng ta phải cẩn thận áp dụng các phương pháp vòng đời trùng với hành vi được mô tả ở trên bởi 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;
    }

}

Cuối cùng, trong bất kỳ lĩnh vực nào trong phạm vi của bạn Fragmentmở rộng BaseFragmentrằng bạn không đáng tin cậy về một cuộc gọi đến getActivity(), chỉ cần thực hiện cuộc gọi đến this.getActivityBuffer().safely(...)và tuyên bố một ActivityBuffer.IRunnablenhiệm vụ!

Nội dung của bạn void run(final Activity pActivity)sau đó được đảm bảo thực hiện dọc theo Giao diện người dùng.

Sau ActivityBufferđó có thể được sử dụng như sau:

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

Bạn có thể thêm một ví dụ về việc sử dụng phương thức this.getActivityBuffer (). Safety (...) không.
fahad_sust

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

Bạn có thể vui lòng giải thích thêm câu trả lời của bạn thêm một chút mô tả về giải pháp bạn cung cấp không?
abarisone

1

Tôi biết đây là một câu hỏi cũ nhưng tôi nghĩ rằng tôi phải cung cấp câu trả lời của mình cho nó vì vấn đề của tôi không được người khác giải quyết.

trước hết: tôi đã tự động thêm các đoạn bằng cách sử dụng các phân đoạn. Thứ hai: các đoạn của tôi đã được sửa đổi bằng AsyncT task (truy vấn DB trên máy chủ). Thứ ba: đoạn của tôi không được khởi tạo khi bắt đầu hoạt động Thứ tư: tôi đã sử dụng một đoạn khởi tạo đoạn tùy chỉnh "tạo hoặc tải nó" để lấy biến phân đoạn. Thứ tư: hoạt động được tạo lại vì thay đổi định hướng

Vấn đề là tôi muốn "loại bỏ" đoạn này vì câu trả lời truy vấn, nhưng đoạn đó đã được tạo không chính xác ngay trước đó. Tôi không biết tại sao, có lẽ vì "cam kết" được thực hiện sau đó, đoạn này chưa được thêm vào khi đã đến lúc xóa nó. Do đó getActivity () đã trả về null.

Giải pháp: 1) Tôi phải kiểm tra xem tôi đã cố gắng tìm chính xác đoạn đầu tiên trước khi tạo đoạn mới 2) Tôi phải đặt serRetainInstance (đúng) vào đoạn đó để giữ cho nó thay đổi định hướng (không bị lạc hậu do đó không cần thiết) 3) Thay vì "tái tạo hoặc lấy lại đoạn cũ" ngay trước khi "xóa nó", tôi trực tiếp đặt đoạn đó khi bắt đầu hoạt động. Khởi tạo nó khi bắt đầu hoạt động thay vì "tải" (hoặc khởi tạo) biến phân đoạn trước khi loại bỏ nó đã ngăn chặn các vấn đề getActivity.


0

Trong Kotlin, bạn có thể thử cách này để xử lý điều kiện null getActivity ().

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

        //your code here

   }

Nó sẽ kiểm tra hoạt động có null hay không và nếu không null thì thực thi mã bên trong.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.