getActivity () ส่งคืนค่า null ในฟังก์ชัน Fragment


192

ฉันมีแฟรกเมนต์ (F1) ด้วยวิธีสาธารณะเช่นนี้

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

และใช่เมื่อฉันเรียกมันว่า (จากกิจกรรม) มันเป็นโมฆะ ...

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

ต้องเป็นสิ่งที่ฉันทำผิดมาก แต่ฉันไม่รู้ว่ามันคืออะไร


ผมไม่แน่ใจว่าถ้ามีเพียงข้อผิดพลาดเมื่อคุณวางมันลงไปในโพสต์นี้ getActivity()แต่คุณต้องวงเล็บหลัง นอกจากนี้คุณจะยกตัวอย่างชิ้นส่วนได้อย่างไร คุณมีมันใน layout.xml ของคุณหรือไม่
CaseyB

ส่วนย่อยที่สองของรหัสอยู่ที่ไหน ไปที่ oncreate () - วิธีการของกิจกรรม? และคุณได้เรียกใช้ setContentView () แล้วหรือยัง
Franziskus Karsunke

R.id.upperPar เป็นองค์ประกอบในเลย์เอาต์ดังนั้นจึงควรแทนที่ด้วยแฟรกเมนต์ แต่นั่นไม่ใช่ปัญหาของฉัน ฉันไม่เข้าใจว่าทำไมฉันถึงได้รับ null เมื่อฉันเรียกใช้ getActivity () ในวิธีการแยกส่วนที่กำหนดเองให้พูดใน onActivityCreated วิธี getActivity เป็นกิจกรรมจริงไม่ใช่ null
Lukap

ปัญหามันไม่ได้อยู่ในเลย์เอาต์แอปทำงานได้ดี แต่ทำไมฉันถึงได้รับโมฆะสำหรับ getActivity? btw ทุกองค์ประกอบรวมถึงส่วนที่มันแสดงผลเหมือนไม่ควรออกที่นี่
Lukap

1
คุณควรเรียกวิธีนี้: f1.asd (); ในเมธอด onActivityCreated ซึ่งจะถูกเขียนทับในคลาสแฟรกเมนต์ของคุณ
Namrata Bagerwal

คำตอบ:


164

commit กำหนดเวลาการทำธุรกรรมนั่นคือมันไม่ได้เกิดขึ้นทันที แต่ถูกกำหนดให้ทำงานในเธรดหลักในครั้งถัดไปที่เธรดหลักพร้อมใช้งาน

ฉันขอแนะนำให้เพิ่ม

onAttach(Activity activity)

วิธีการที่คุณและวางจุดพักกับมันและเห็นเมื่อมันถูกเรียกว่าเทียบกับการเรียกร้องให้Fragment asd()คุณจะเห็นว่ามันถูกเรียกหลังจากวิธีที่คุณโทรasd()ออกไป onAttachโทรที่Fragmentถูกแนบไปกับกิจกรรมและจากจุดนี้getActivity()จะกลับไม่ใช่ null (nb นอกจากนี้ยังมีonDetach()การโทร)


5
ฉันไม่เข้าใจว่าคุณจะแก้ปัญหาของคุณได้อย่างไร ถ้า getActivity () ของฉันยังไม่พร้อมฉันจะรับการอ้างอิงของวัตถุ FragmentActivity ได้อย่างไร
CeccoCQ

2
@Vivek ฉันไม่ทราบว่าสิ่งที่คุณต้องการบรรลุ หากคุณจำเป็นต้องมีส่วนในการแสดงโต้ตอบทันทีแล้วมีมันทำในสิ่งที่ต้องทำในการสร้างเช่นในของมันonCreateViewหรือonActivityCreatedวิธีการ ฉันถามว่าทำไมต้องเรียก asd () เมื่อมีการโพสต์คำถาม
PJL

3
onAttach เลิกใช้แล้ว
abbasalim

6
onAttach (กิจกรรม mActivity) ดูเหมือนว่าจะคิดค่าเสื่อมราคา .. วิธีแก้ปัญหาใด ๆ สำหรับเรื่องนี้
ashish.n

4
API 24 ได้รับการแนะนำcommitNow()
Nicolas

92

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

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

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

34
เราควรตั้ง mActivity = null onDetach () หรือไม่
Oliver Pearmain

5
@OliverPearmain หากคุณจะทำใน onDetach () จากนั้นจะไม่มีผลกำไร คุณต้องลบล้างมันใน onDestory () นอกจากนี้คุณต้องถือมันไว้ใน WeakRefernce
Kirill Popov

ฉันลบล้างมันทั้งในonDestroy()และonDetach()เพราะonDestroy()ไม่รับประกันว่าจะเรียก
โมฮัมเหม็ดอาลี

8
เรารั่วActivityถ้าเราไม่ลบล้างมันonDestroy()?
Mohammed Ali

3
ตามdeveloper.android.com/intl/zh-tw/guide/components/… , onAttach () จะถูกเรียกใช้ก่อนที่จะเรียกใช้ onCreateView () แต่ฉันยังคงได้รับ NullPointerException ขณะที่ฉันโทรหา getActivity () ใน onCreateView () มันเกิดขึ้นได้อย่างไร?
Kimi Chiu

82

สิ่งนี้เกิดขึ้นเมื่อคุณโทรgetActivity()ในเธรดอื่นที่เสร็จสิ้นหลังจากที่แฟรกเมนต์ถูกลบ กรณีทั่วไปคือการโทรgetActivity()(เช่นสำหรับ a Toast) เมื่อคำขอ HTTP เสร็จสิ้น ( onResponseตัวอย่างเช่น)

เพื่อหลีกเลี่ยงนี้คุณสามารถกำหนดชื่อฟิลด์และใช้มันแทนmActivity getActivity()ฟิลด์นี้สามารถเริ่มต้นได้ในวิธี onAttach () ของ Fragment ดังนี้:

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

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

ในโครงการของฉันฉันมักจะกำหนดคลาสพื้นฐานสำหรับชิ้นส่วนทั้งหมดของฉันด้วยคุณสมบัตินี้:

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

การเข้ารหัสที่มีความสุข


19
เราจะตั้ง mActivity = null; ใน onDetach ()?
Bharat Dodeja

อย่างไรก็ตาม, วิธีการประกาศ mActivity แบบคงที่สำหรับแอพกิจกรรมเดียว?
iamthevoid

ฉันไม่เห็นเหตุผลในการเข้าถึงActivityจากเธรดอื่นเลย คุณไม่สามารถทำอะไรกับมันได้เลยแม้แต่จะไม่แสดง Toast ดังนั้นคุณควรโอนงานไปที่เธรดหลักก่อนหรือไม่ใช้กิจกรรมเลย
Dzmitry Lazerka

4
@BharatDodeja เราควรตั้ง mActivity = null บนเดชอุดม คุณรู้หรือไม่?
SLearner

5
สิ่งนี้กำลังจะรั่วไหลโดยไม่ทำให้กิจกรรมเป็นโมฆะ
Darek Deoniziak

30

คำตอบอื่น ๆ ที่แนะนำให้อ้างอิงถึงกิจกรรมใน onAttach เป็นเพียงการแนะนำ bandaid กับปัญหาจริง เมื่อ getActivity ส่งคืนค่า null หมายความว่าแฟรกเมนต์ไม่ได้เชื่อมต่อกับกิจกรรม สิ่งนี้มักเกิดขึ้นเมื่อกิจกรรมได้หายไปเนื่องจากการหมุนหรือกิจกรรมเสร็จสิ้นแล้ว แต่ Fragment ยังมีการรับฟังการติดต่อกลับบางประเภท เมื่อผู้ฟังได้รับการเรียกถ้าคุณต้องการทำบางสิ่งบางอย่างกับกิจกรรม แต่กิจกรรมนั้นหายไปคุณก็ไม่สามารถทำอะไรได้มาก ในรหัสของคุณคุณควรตรวจสอบgetActivity() != nullและถ้ามันไม่อยู่ตรงนั้นอย่าทำอะไรเลย หากคุณอ้างอิงถึงกิจกรรมที่หายไปคุณกำลังขัดขวางกิจกรรมจากการรวบรวมขยะ ผู้ใช้จะไม่เห็นสิ่ง UI ใด ๆ ที่คุณพยายามทำ ฉันสามารถจินตนาการถึงสถานการณ์บางอย่างที่ในผู้ฟังรายการโทรกลับคุณอาจต้องการให้มีบริบทสำหรับสิ่งที่ไม่ใช่ UI ที่เกี่ยวข้องในกรณีเหล่านั้นอาจเหมาะสมกว่าที่จะรับบริบทของแอปพลิเคชัน โปรดทราบว่าเหตุผลเดียวที่onAttachกลอุบายไม่ใช่การรั่วไหลของหน่วยความจำขนาดใหญ่นั้นเป็นเพราะปกติหลังจากที่ผู้ฟังรายการโทรกลับดำเนินการแล้วจะไม่จำเป็นอีกต่อไปและสามารถเก็บขยะพร้อมกับแฟรกเมนต์บริบทของมุมมองและกิจกรรมทั้งหมด ถ้าคุณsetRetainInstance(true) มีโอกาสที่ใหญ่กว่าของหน่วยความจำรั่วเนื่องจากฟิลด์กิจกรรมจะยังคงอยู่ แต่หลังจากการหมุนที่อาจเป็นกิจกรรมก่อนหน้านี้ไม่ใช่กิจกรรมปัจจุบัน


1
นี่เป็นปัญหาของฉันอย่างแน่นอน ฉันมีแฟรกเมนต์ที่ใช้กระบวนการ -> จากนั้นโฆษณาจะแสดง -> และจากนั้นผู้ดำเนินการจะดำเนินการต่อ ในอุปกรณ์บางอย่างหลังจากกลับมาจากโฆษณา (ผ่านผู้ฟังเหตุการณ์โฆษณา) getActivity () เป็นโมฆะ แต่ฉันต้องดำเนินการในส่วนอื่น ๆ ของงานเพื่อให้งานสำเร็จ คุณหมายถึงไม่มีวิธีแก้ปัญหานี้ใช่ไหม
Notbad

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

นี่น่าจะเป็นคำตอบทั่วไปที่ถูกต้องสำหรับคำถาม SO 100s ในหัวข้อนี้
Manuel

คำตอบที่ดีที่สุด มีโซลูชั่น bandaid มากมายสำหรับ Android ใน SO
maxbeaudoin

ดังนั้นถ้าฉันต้องการที่จะดำเนินการบางอย่างฉันจะดำเนินการหลังจาก getActivity () สามารถใช้ได้ (ถ้ามันเป็น)
Sreekanth Karumanaghat

17

ตั้งแต่ Android API ระดับ 23, onAttach (กิจกรรมกิจกรรม) ได้ถูกคัดค้าน คุณต้องใช้ onAttach (บริบทบริบท) http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)

กิจกรรมเป็นบริบทดังนั้นหากคุณสามารถตรวจสอบบริบทเป็นกิจกรรมและใช้งานได้ถ้าจำเป็น

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

    Activity a;

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

}

วิธีใช้งาน
ARR.s

10

PJL ถูกต้อง ฉันใช้คำแนะนำของเขาและนี่คือสิ่งที่ฉันทำ:

  1. ตัวแปรโกลบอลที่กำหนดสำหรับแฟรกเมนต์:

    private final Object attachingActivityLock = new Object();

    private boolean syncVariable = false;

  2. การดำเนินการ

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

3. ฉันสรุปฟังก์ชั่นของฉันที่ฉันต้องเรียกใช้ getActivity () ในเธรดเพราะถ้ามันจะทำงานบนเธรดหลักฉันจะบล็อกเธรดด้วยขั้นตอนที่ 4 และ onAttach () จะไม่ถูกเรียก

    Thread processImage = new Thread(new Runnable() {

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

4. ในฟังก์ชั่นของฉันที่ฉันต้องเรียกใช้ getActivity () ฉันใช้สิ่งนี้ (ก่อนการเรียกใช้ getActivity ())

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

หากคุณมีการอัปเดต UI โปรดอย่าลืมเรียกใช้งานบนเธรด UI ฉันต้องการอัปเดต ImgeView ดังนั้นฉันจึง:

image.post(new Runnable() {

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

7

ลำดับที่การเรียกกลับถูกเรียกหลังจากกระทำ ():

  1. วิธีใดก็ตามที่คุณโทรด้วยตนเองทันทีหลังจากส่ง ()
  2. onAttach ()
  3. onCreateView ()
  4. onActivityCreated ()

ฉันต้องทำงานที่เกี่ยวข้องกับ Views บางส่วนดังนั้น onAttach () จึงไม่ได้ผลสำหรับฉัน มันล้มเหลว ดังนั้นฉันจึงย้ายส่วนหนึ่งของรหัสของฉันที่ตั้งค่า params ภายในวิธีที่เรียกว่าทันทีหลังจากส่ง () (1) จากนั้นส่วนอื่น ๆ ของรหัสที่จัดการดูภายใน onCreateView () (3. )


3

ฉันใช้ OkHttp และฉันเพิ่งพบปัญหานี้


สำหรับส่วนแรก@thucnguyen อยู่บนเส้นทางที่ถูกต้อง

สิ่งนี้เกิดขึ้นเมื่อคุณเรียกใช้ getActivity () ในเธรดอื่นที่เสร็จสิ้นหลังจากที่แฟรกเมนต์ถูกลบ กรณีทั่วไปกำลังเรียกใช้ getActivity () (เช่น Toast) เมื่อคำขอ HTTP เสร็จสิ้น (เช่น onResponse เป็นต้น)

การเรียก HTTP บางอย่างถูกดำเนินการแม้หลังจากกิจกรรมถูกปิด (เพราะอาจใช้เวลาสักครู่ก่อนที่คำขอ HTTP จะเสร็จสมบูรณ์) จากนั้นผมก็ผ่านการHttpCallbackพยายามที่จะปรับปรุงเขตข้อมูลส่วนบางอย่างและมีข้อยกเว้นเมื่อพยายามที่จะnullgetActivity()

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

IMO วิธีแก้ไขคือป้องกันการโทรกลับเกิดขึ้นเมื่อแฟรกเมนต์ไม่เหลืออีกต่อไป (ซึ่งไม่ใช่แค่ Okhttp)

การแก้ไข: การป้องกัน

หากคุณดูวงจรชีวิตของชิ้นส่วน (ข้อมูลเพิ่มเติมที่นี่ ) คุณจะสังเกตเห็นว่ามีonAttach(Context context)และonDetach()วิธีการต่างๆ สิ่งเหล่านี้ถูกเรียกหลังจากที่แฟรกเมนต์เป็นของกิจกรรมและก่อนที่จะหยุดตามลำดับ

นั่นหมายความว่าเราสามารถป้องกันการโทรกลับนั้นเกิดขึ้นได้ด้วยการควบคุมในonDetachวิธีการ

@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

คุณเรียกใช้ฟังก์ชันนี้ที่ไหน ถ้าคุณเรียกมันในตัวสร้างของมันจะกลับมาFragmentnull

เพียงโทรหาgetActivity()เมื่อวิธีการonCreateView()ดำเนินการ


1

ทำดังนี้ ฉันคิดว่ามันจะเป็นประโยชน์กับคุณ

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

ผู้ที่ยังคงมีปัญหากับ onAttach (กิจกรรมกิจกรรม) มันเพิ่งเปลี่ยนเป็นบริบท -

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

ในกรณีส่วนใหญ่การบันทึกบริบทจะเพียงพอสำหรับคุณตัวอย่างเช่นถ้าคุณต้องการ getResources () คุณสามารถทำได้โดยตรงจากบริบท หากคุณยังต้องทำให้บริบทในกิจกรรมของคุณทำ

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

ตามที่แนะนำโดย user1868713


0

คุณสามารถใช้ onAttach หรือถ้าคุณไม่ต้องการใส่ทุกที่แล้วคุณสามารถใส่เมธอดที่ส่งกลับ ApplicationContext บนคลาสแอพหลัก:

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

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

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

หลังจากนั้นคุณสามารถใช้ซ้ำได้ทุกที่ในทุกโครงการของคุณเช่นนี้:

App.getContext().getString(id)

โปรดแจ้งให้เราทราบหากสิ่งนี้ไม่ได้ผลสำหรับคุณ


0

อีกทางเลือกที่ดีคือการใช้ LiveData ของ Android กับสถาปัตยกรรม MVVM คุณจะกำหนดวัตถุ LiveData ภายใน ViewModel ของคุณและสังเกตในส่วนของคุณและเมื่อมีการเปลี่ยนแปลงค่า LiveData มันจะแจ้งผู้สังเกตการณ์ของคุณ (ส่วนในกรณีนี้) เฉพาะในกรณีที่ชิ้นส่วนของคุณอยู่ในสถานะที่ใช้งานดังนั้นจึงรับประกันได้ว่า จะทำให้ UI ของคุณทำงานและเข้าถึงกิจกรรมเมื่อชิ้นส่วนของคุณอยู่ในสถานะใช้งาน นี่คือข้อดีอย่างหนึ่งที่มาพร้อมกับLiveData

แน่นอนเมื่อคำถามนี้ถูกถามครั้งแรกไม่มี LiveData ฉันทิ้งคำตอบนี้ไว้ที่นี่เพราะอย่างที่ฉันเห็นยังมีปัญหานี้และมันจะเป็นประโยชน์กับใครบางคน


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