getActivity () trả về null trong hàm Fragment


191

Tôi có một đoạn (F1) với một phương thức công khai như thế này

public void asd() {
    if (getActivity() == null) {
        Log.d("yes","it is null");
    }
}

và có khi tôi gọi nó (từ Hoạt động), nó là null ...

FragmentTransaction transaction1 = getSupportFragmentManager().beginTransaction();
F1 f1 = new F1();
transaction1.replace(R.id.upperPart, f1);
transaction1.commit();
f1.asd();

Đó phải là điều mà tôi đang làm rất sai, nhưng tôi không biết đó là gì


Tôi không chắc chắn nếu có lỗi khi bạn dán nó vào bài viết này, nhưng bạn cần dấu ngoặc đơn sau getActivity(). Ngoài ra, làm thế nào bạn bắt đầu các mảnh? Bạn có nó trong layout.xml của bạn không?
CaseyB

Đoạn mã thứ hai thuộc về đâu? Để phương thức oncreate () - của Hoạt động? Và bạn đã gọi setContentView () chưa?
Franziskus Karsunke

R.id.upperPar là một thành phần trong bố cục, vì vậy nó được thay thế bằng đoạn, nhưng đó không phải là vấn đề của tôi. Tôi không hiểu tại sao tôi nhận được null khi tôi gọi getActivity () trong các phương thức phân đoạn tùy chỉnh, giả sử trong phương thức onActivityCreated getActivity là hoạt động thực tế không null
Lukap

vấn đề nó không nằm trong bố trí, các ứng dụng hoạt động tốt nhưng tại sao tôi nhận được null cho getActivity, btw tất cả các yếu tố trong đó có đoạn nó được trả lại như nó không nên vấn đề ở đây?
Lukap

1
Bạn nên gọi phương thức này: f1.asd (); trong phương thức onActivityCreated sẽ được ghi đè trong lớp phân mảnh của bạn.
Namrata Bagerwal

Câu trả lời:


164

commit lên lịch giao dịch, tức là nó không xảy ra ngay lập tức nhưng được lên lịch làm việc trên luồng chính vào lần tiếp theo luồng chính sẵn sàng.

Tôi muốn đề nghị thêm một

onAttach(Activity activity)

phương thức của bạn Fragmentvà đặt một điểm dừng trên đó và xem khi nào nó được gọi tương đối với cuộc gọi của bạn asd(). Bạn sẽ thấy rằng nó được gọi theo phương thức mà bạn thực hiện cuộc gọi để asd()thoát. Cuộc onAttachgọi là nơi Fragmentgắn liền với hoạt động của nó và từ thời điểm getActivity()này sẽ trả về giá trị không null (nb cũng có một onDetach()cuộc gọi).


5
Tôi không hiểu làm thế nào bạn có thể giải quyết vấn đề của bạn. Nếu getActivity () của tôi chưa sẵn sàng, làm thế nào tôi có thể nhận được tham chiếu của đối tượng FragmentActivity?
CeccoCQ

2
@Vivek Tôi không biết bạn muốn đạt được điều gì. Nếu bạn cần Fragment để hiển thị một hộp thoại ngay lập tức thì hãy để nó làm những gì nó cần làm khi tạo, ví dụ như trong phương thức onCreateViewhoặc onActivityCreatedphương thức của nó . Tôi đặt câu hỏi tại sao asd () cần được gọi khi nó được đăng trong câu hỏi.
PJL

3
onAttach không dùng nữa
abbasalim

6
onAttach (Hoạt động mActivity) dường như bị khấu hao .. bất kỳ cách giải quyết nào cho việc này
ashish.n

4
Giới thiệu API 24commitNow()
Nicolas

92

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(activity);
    mContext = context;
}

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

34
Chúng ta có nên đặt mActivity = null onDetach () không?
Oliver Pearmain

5
@OliverPearmain nếu bạn sẽ làm điều đó trong onDetach () thì sẽ không có lợi nhuận. Bạn phải vô hiệu hóa nó trong onDestory (). Hơn nữa, bạn phải giữ nó trong WeakRefernce.
Kirill Popov

Tôi vô hiệu hóa nó trong cả hai onDestroy()onDetach()bởi vì onDestroy()không được đảm bảo để được gọi.
Mohammed Ali

8
Có phải chúng ta đang rò rỉ Activitynếu chúng ta không vô hiệu hóa nó onDestroy()?
Mohammed Ali

2
Theo developer.android.com/intl/zh-tw/guide/components/ Khăn, onAttach () được gọi trước khi gọi onCreateView (). Nhưng tôi vẫn nhận được một NullPulumException trong khi tôi gọi getActivity () trong onCreateView (). Làm thế nào điều đó có thể xảy ra?
Kimi Chiu

81

Điều này xảy ra khi bạn gọi getActivity()một luồng khác kết thúc sau khi đoạn bị xóa. Trường hợp điển hình là gọi getActivity()(ví dụ: a Toast) khi yêu cầu HTTP kết thúc ( onResponseví dụ).

Để tránh điều này, bạn có thể xác định tên trường mActivityvà sử dụng nó thay vì getActivity(). Trường này có thể được khởi tạo trong phương thức Fragment của onAttach () như sau:

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

    if (context instanceof Activity){
        mActivity =(Activity) context;
    }
}

Trong các dự án của mình, tôi thường định nghĩa một lớp cơ sở cho tất cả các Mảnh vỡ của mình với tính năng này:

public abstract class BaseFragment extends Fragment {

    protected FragmentActivity mActivity;

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

    if (context instanceof Activity){
        mActivity =(Activity) context;
    }
}
}

Chúc mừng mã hóa


19
chúng ta sẽ đặt mActivity = null; trong onDetach ()?
Bharat Dodeja

thucnguyen, làm thế nào để khai báo mActivity tĩnh cho ứng dụng hoạt động đơn lẻ?
iamthevoid

Tôi không thấy một lý do để truy cập Activitytừ một chủ đề khác cả. Dù sao thì bạn cũng không thể làm gì với nó, thậm chí không hiển thị Toast. Vì vậy, bạn nên chuyển công việc sang luồng chính trước hoặc không sử dụng Activity.
Dzmitry Lazerka

4
@BharatDodeja chúng ta có nên đặt mActivity = null onDetach không? Bạn đã tìm ra?
SLearner

5
Điều này sẽ bị rò rỉ mà không loại bỏ hoạt động.
Darek Deoniziak

30

Các câu trả lời khác đề nghị giữ một tham chiếu đến hoạt động trong onAttach chỉ là đề xuất một dải băng cho vấn đề thực sự. Khi getActivity trả về null, điều đó có nghĩa là Fragment không được đính kèm với Activity. Thông thường nhất điều này xảy ra khi Hoạt động đã biến mất do xoay vòng hoặc Hoạt động đã kết thúc nhưng Đoạn vẫn có một số loại trình nghe gọi lại được đăng ký. Khi người nghe được gọi nếu bạn cần làm gì đó với Hoạt động nhưng Hoạt động không còn nữa, bạn không thể làm gì nhiều. Trong mã của bạn, bạn chỉ cần kiểm tragetActivity() != nullvà nếu nó không ở đó thì đừng làm gì cả. Nếu bạn giữ một tham chiếu đến Hoạt động đã biến mất, bạn đang ngăn Hoạt động bị thu gom rác. Bất kỳ giao diện người dùng nào bạn có thể cố gắng thực hiện sẽ không được người dùng nhìn thấy. Tôi có thể tưởng tượng một số tình huống trong trình nghe gọi lại mà bạn có thể muốn có một Ngữ cảnh cho một cái gì đó không liên quan đến giao diện người dùng, trong những trường hợp đó có lẽ có ý nghĩa hơn để có được bối cảnh Ứng dụng. Lưu ý rằng lý do duy nhất khiến onAttachmánh khóe không bị rò rỉ bộ nhớ lớn là vì thông thường sau khi người nghe gọi lại thực thi thì nó sẽ không còn cần thiết nữa và có thể là rác được thu thập cùng với Fragment, tất cả bối cảnh của View và Activity. nếu bạnsetRetainInstance(true) có khả năng rò rỉ bộ nhớ lớn hơn vì trường Activity cũng sẽ được giữ lại nhưng sau khi xoay có thể là Activity trước đó không phải là Activity hiện tại.


1
Đây chính xác là vấn đề của tôi. Tôi có một đoạn thực hiện một quá trình -> sau đó một quảng cáo được hiển thị -> và sau đó proess tiếp tục. Trong một số thiết bị sau khi trở về từ quảng cáo (thông qua người nghe đến các sự kiện quảng cáo) getActivity () là null. Nhưng tôi cần tiếp tục làm phần khác của công việc để hoàn thành công việc. Bạn có nghĩa là không có giải pháp cho điều này?
Notbad

Đây chính xác là những gì tôi đang phải đối mặt. Tôi có một giao diện Activity trong một đoạn mà tôi thực hiện một số việc thanh toán. Sau khi thanh toán xong, tôi muốn sử dụng giao diện để làm một cái gì đó, nhưng giao diện đã bị rỗng.
Freddie

Đây dường như là câu trả lời chung chính xác cho 100 câu hỏi SO cho chủ đề này.
Manuel

Câu trả lời tốt nhất. Có rất nhiều giải pháp thanh toán cho Android trên SO.
maxbeaudoin

Vì vậy, nếu tôi muốn thực hiện một số thao tác, làm thế nào tôi có thể thực thi sau khi getActivity () khả dụng (nếu có).
Saletanth Karumanaghat

17

Vì API Android cấp 23, onAttach (Hoạt động hoạt động) đã không được chấp nhận. Bạn cần sử dụng onAttach (Ngữ cảnh). http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)

Hoạt động là một bối cảnh vì vậy nếu bạn có thể chỉ cần kiểm tra bối cảnh là một Hoạt động và bỏ qua nó nếu cần thiết.

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

    Activity a;

    if (context instanceof Activity){
        a=(Activity) context;
    }

}

Cách sử dụng
ARR.s

10

PJL đã đúng. Tôi đã sử dụng đề nghị của anh ấy và đây là những gì tôi đã làm:

  1. các biến toàn cục được xác định cho đoạn:

    private final Object attachingActivityLock = new Object();

    private boolean syncVariable = false;

  2. thực hiện

@Override
public void onAttach(Activity activity) {
  super.onAttach(activity);
  synchronized (attachingActivityLock) {
      syncVariable = true;
      attachingActivityLock.notifyAll();
  }
}

3. Tôi đã kết thúc chức năng của mình, nơi tôi cần gọi getActivity (), trong luồng, bởi vì nếu nó chạy trên luồng chính, tôi sẽ chặn luồng với bước 4. và onAttach () sẽ không bao giờ được gọi.

    Thread processImage = new Thread(new Runnable() {

        @Override
        public void run() {
            processImage();
        }
    });
    processImage.start();

4 . trong hàm của tôi, nơi tôi cần gọi getActivity (), tôi sử dụng hàm này (trước lệnh gọi getActivity ())

    synchronized (attachingActivityLock) {
        while(!syncVariable){
            try {
                attachingActivityLock.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

Nếu bạn có một số cập nhật UI, hãy nhớ chạy chúng trên luồng UI. Tôi cần cập nhật ImgeView vì vậy tôi đã làm:

image.post(new Runnable() {

    @Override
    public void run() {
        image.setImageBitmap(imageToShow);
    }
});

7

Thứ tự gọi lại được gọi sau commit ():

  1. Bất cứ phương thức nào bạn gọi thủ công ngay sau khi commit ()
  2. trênAttach ()
  3. onCreateView ()
  4. onActivityCreated ()

Tôi cần thực hiện một số công việc liên quan đến một số Chế độ xem, vì vậy onAttach () không hoạt động đối với tôi; Nó gãy. Vì vậy, tôi đã di chuyển một phần mã đang đặt một số tham số bên trong một phương thức được gọi ngay sau commit () (1.), sau đó là một phần khác của mã xử lý khung nhìn bên trong onCreateView () (3.).


3

Tôi đang sử dụng OkHttp và tôi chỉ gặp phải vấn đề này.


Đối với phần đầu tiên @thucnguyen đã đi đúng hướng .

Điều này xảy ra khi bạn gọi getActivity () trong một luồng khác kết thúc sau khi đoạn bị xóa. Trường hợp điển hình là gọi getActivity () (ví dụ: Toast) khi yêu cầu HTTP kết thúc (ví dụ như trong onResponse).

Một số cuộc gọi HTTP đã được thực hiện ngay cả sau khi hoạt động đã bị đóng (vì có thể mất một lúc để yêu cầu HTTP được hoàn thành). Tôi sau đó, thông qua việc HttpCallbackcố gắng cập nhật một số trường Fragment và có một nullngoại lệ khi cố gắng getActivity().

http.newCall(request).enqueue(new Callback(...
  onResponse(Call call, Response response) {
    ...
    getActivity().runOnUiThread(...) // <-- getActivity() was null when it had been destroyed already

IMO giải pháp là ngăn chặn các cuộc gọi lại xảy ra khi đoạn không còn tồn tại nữa (và đó không chỉ với Okhttp).

Cách khắc phục: Phòng ngừa.

Nếu bạn có một cái nhìn về vòng đời của mảnh vỡ (thông tin thêm ở đây ), bạn sẽ nhận thấy rằng có onAttach(Context context)onDetach()phương pháp. Chúng được gọi sau khi Fragment thuộc về một hoạt động và ngay trước khi dừng tương ứng.

Điều đó có nghĩa là chúng ta có thể ngăn chặn cuộc gọi lại đó xảy ra bằng cách kiểm soát nó trong onDetachphương thức.

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

    // Initialize HTTP we're going to use later.
    http = new OkHttpClient.Builder().build();
}

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

    // We don't want to receive any more information about the current HTTP calls after this point.
    // With Okhttp we can simply cancel the on-going ones (credits to https://github.com/square/okhttp/issues/2205#issuecomment-169363942).
    for (Call call : http.dispatcher().queuedCalls()) {
        call.cancel();
    }
    for (Call call : http.dispatcher().runningCalls()) {
        call.cancel();
    }
}

2

Bạn gọi chức năng này ở đâu? Nếu bạn gọi nó trong hàm tạo của Fragmentnó, nó sẽ trả về null.

Chỉ cần gọi getActivity()khi phương thức onCreateView()được thực thi.


1

Làm như sau. Tôi nghĩ nó sẽ hữu ích cho bạn.

private boolean isVisibleToUser = false;
private boolean isExecutedOnce = false;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View root = inflater.inflate(R.layout.fragment_my, container, false);
    if (isVisibleToUser && !isExecutedOnce) {
        executeWithActivity(getActivity());
    }
    return root;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser = isVisibleToUser;
    if (isVisibleToUser && getActivity()!=null) {
        isExecutedOnce =true;
        executeWithActivity(getActivity());
    }
}


private void executeWithActivity(Activity activity){
    //Do what you have to do when page is loaded with activity

}

1

Những người vẫn gặp sự cố với onAttach (Hoạt động hoạt động), nó chỉ thay đổi thành Ngữ cảnh -

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

Trong hầu hết các trường hợp, việc lưu bối cảnh sẽ là đủ cho bạn - ví dụ nếu bạn muốn thực hiện getResource (), bạn có thể thực hiện trực tiếp từ ngữ cảnh. Nếu bạn vẫn cần đặt bối cảnh vào Hoạt động của mình, hãy làm như vậy -

 @Override
public void onAttach(Context context) {
    super.onAttach(context);
    mActivity a; //Your activity class - will probably be a global var.
    if (context instanceof mActivity){
        a=(mActivity) context;
    }
}

Theo đề xuất của người dùng1868713.


0

Bạn có thể sử dụng onAttach hoặc nếu bạn không muốn đặt onAttach ở mọi nơi thì bạn có thể đặt một phương thức trả về ApplicationContext trên lớp Ứng dụng chính:

public class App {
    ...  
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
    }

    public static Context getContext() {
        return context;
    }
    ...
}

Sau đó, bạn có thể sử dụng lại nó ở mọi nơi trong tất cả các dự án của bạn, như thế này:

App.getContext().getString(id)

Xin vui lòng cho tôi biết nếu điều này không làm việc cho bạn.


0

Một giải pháp tốt khác là sử dụng LiveData của Android với kiến ​​trúc MVVM. Bạn sẽ xác định một đối tượng LiveData bên trong ViewModel của mình và quan sát nó trong đoạn của bạn và khi giá trị LiveData bị thay đổi, nó sẽ thông báo cho người quan sát của bạn (đoạn này trong trường hợp này) chỉ khi đoạn của bạn ở trạng thái hoạt động, vì vậy nó sẽ được đảm bảo rằng bạn sẽ làm cho giao diện người dùng của bạn hoạt động và chỉ truy cập hoạt động khi đoạn của bạn ở trạng thái hoạt động. Đây là một lợi thế đi kèm với LiveData

Tất nhiên khi câu hỏi này lần đầu tiên được hỏi, không có LiveData. Tôi để lại câu trả lời này ở đây vì như tôi thấy, vẫn còn vấn đề này và nó có thể hữu ích cho ai đó.


0

Gọi phương thức getActivity () bên trong onActivityCreated ()

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.