ใช้บริบทของแอปพลิเคชันทุกที่หรือไม่


476

ในแอพ Android มีอะไรผิดปกติกับวิธีการต่อไปนี้:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

และส่งผ่านทุกที่ (เช่น SQLiteOpenHelper) ที่จำเป็นต้องใช้บริบท (และไม่รั่วแน่นอน)?


23
เพียงแค่ทำอย่างละเอียดเพื่อให้ผู้อื่นดำเนินการนี้แล้วคุณสามารถปรับเปลี่ยน<application>โหนดของไฟล์ AndroidManifest.xml android:name="MyApp"ของคุณให้มีความหมายแอตทริบิวต์ต่อไปนี้: MyApp จะต้องอยู่ภายใต้แพคเกจเดียวกันกับที่อ้างอิงของคุณ
Matt Huggins

6
วิธีที่ยอดเยี่ยมในการแก้ไขปัญหาของการจัดหาบริบทให้กับ SQLiteOpenHelper !! ฉันใช้ซิงเกิล "SQLiteManager" และติดอยู่ที่ "ฉันจะเอาบริบทไปใช้กับซิงเกิลตันได้อย่างไร"
ใครสักคนที่ไหนสักแห่ง

8
เพื่อให้คุณรู้ว่าคุณกำลังส่งคืนแอปพลิเคชันของคุณโดยหนึ่งในอินเทอร์เฟซที่ยอดเยี่ยมดังนั้นหากคุณให้วิธีการเพิ่มเติมภายใน MyApp คุณจะไม่สามารถใช้งานได้ getContext () ของคุณควรมีประเภทการส่งคืนของ MyApp และวิธีที่คุณสามารถใช้วิธีการที่เพิ่มเข้ามาในภายหลังเช่นเดียวกับวิธีการทั้งหมดใน ContextWrapper และบริบท

5
ดูเพิ่มเติมgoo.gl/uKcFn - เป็นคำตอบอื่นที่เกี่ยวข้องกับโพสต์ที่คล้ายกัน ตั้งค่าตัวแปรสแตติกให้ดีขึ้นใน onCreate และไม่ใช่ c'tor
AlikElzin-kilaka

1
@ChuongPham หากเฟรมเวิร์กทำลายแอปของคุณจะไม่มีสิ่งใดที่เข้าถึงบริบทว่าง ...
Kevin Krumwiede

คำตอบ:


413

มีปัญหาที่อาจเกิดขึ้นได้สองสามประการด้วยวิธีการนี้แม้ว่าในหลาย ๆ กรณี (เช่นตัวอย่างของคุณ) มันจะทำงานได้ดี

โดยเฉพาะอย่างยิ่งคุณควรจะระมัดระวังเมื่อต้องรับมือกับสิ่งที่เกี่ยวข้องกับที่ต้องใช้GUI Contextตัวอย่างเช่นหากคุณส่งบริบทของแอปพลิเคชันไปที่LayoutInflaterคุณจะได้รับการยกเว้น โดยทั่วไปวิธีการของคุณเป็นเลิศ: มันเป็นวิธีที่ดีที่จะใช้Activity's ContextภายในActivityและApplication Contextเมื่อผ่านบริบทเกินขอบเขตของนั้นActivityเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ

นอกจากนี้ในฐานะที่เป็นทางเลือกให้กับรูปแบบของคุณคุณสามารถใช้ทางลัดของการเรียกgetApplicationContext()บนContextวัตถุ (เช่นกิจกรรม) ที่จะได้รับการประยุกต์ใช้บริบท


22
ขอบคุณสำหรับคำตอบที่สร้างแรงบันดาลใจ ฉันคิดว่าฉันจะใช้วิธีนี้เฉพาะกับเลเยอร์การคงอยู่ (เพราะฉันไม่ต้องการไปกับผู้ให้บริการเนื้อหา) สงสัยว่าอะไรคือแรงจูงใจที่อยู่เบื้องหลังการออกแบบ SQLiteOpenHelper ในแบบที่คาดว่าบริบทจะได้รับการจัดหาแทนการรับจากแอปพลิเคชันเอง ป.ล. และหนังสือของคุณยอดเยี่ยมมาก!
yanchenko

7
การใช้บริบทของแอปพลิเคชั่นLayoutInflatorทำงานได้ดีสำหรับฉัน จะต้องมีการเปลี่ยนแปลงในช่วงสามปีที่ผ่านมา
Jacob Phillips

5
@JacobPhillips การใช้ LayoutInflator โดยไม่มีบริบทของกิจกรรมจะทำให้พลาดสไตล์ของกิจกรรมนั้น ดังนั้นมันจะใช้งานได้ในแง่หนึ่ง แต่ไม่ใช่ในแง่อื่น
Mark

1
@MarkCarter คุณหมายถึงการใช้บริบทของแอปพลิเคชันจะพลาดสไตล์ของกิจกรรมหรือไม่
Jacob Phillips

1
@JacobPhillips ใช่บริบทของแอปพลิเคชันไม่สามารถใส่สไตล์ได้เนื่องจากกิจกรรมทุกอย่างอาจมีรูปแบบแตกต่างกันไป
Mark

28

จากประสบการณ์ของฉันวิธีนี้ไม่จำเป็น หากคุณต้องการบริบทสำหรับทุกสิ่งที่คุณมักจะได้รับผ่านการเรียกView.getContext ()และการใช้ที่Contextได้รับคุณสามารถเรียกContext.getApplicationContext ()เพื่อรับApplicationบริบท ถ้าคุณกำลังพยายามที่จะได้รับApplicationบริบทนี้จากActivityคุณสามารถเรียกActivity.getApplication ()ซึ่งควรจะสามารถที่จะถูกส่งผ่านเป็นสิ่งจำเป็นสำหรับการโทรออกไปยังContextSQLiteOpenHelper()

โดยรวมมีไม่ดูเหมือนจะมีปัญหากับวิธีการของคุณสำหรับสถานการณ์นี้ แต่เมื่อต้องรับมือกับContextเพียงให้แน่ใจว่าคุณไม่ได้รั่วไหลได้ทุกที่หน่วยความจำตามที่อธิบายไว้อย่างเป็นทางการนักพัฒนา Google Android บล็อก


13

บางคนถามว่า: ซิงเกิลสามารถกลับตัวชี้โมฆะได้อย่างไร ฉันกำลังตอบคำถามนั้น (ฉันไม่สามารถตอบในความคิดเห็นเพราะฉันต้องโพสต์รหัส)

มันอาจกลับมาเป็นโมฆะในระหว่างสองเหตุการณ์: (1) โหลดคลาสและ (2) วัตถุของคลาสนี้ถูกสร้างขึ้น นี่คือตัวอย่าง:

class X {
    static X xinstance;
    static Y yinstance = Y.yinstance;
    X() {xinstance=this;}
}
class Y {
    static X xinstance = X.xinstance;
    static Y yinstance;
    Y() {yinstance=this;}
}

public class A {
    public static void main(String[] p) {
    X x = new X();
    Y y = new Y();
    System.out.println("x:"+X.xinstance+" y:"+Y.yinstance);
    System.out.println("x:"+Y.xinstance+" y:"+X.yinstance);
    }
}

ลองเรียกใช้รหัส:

$ javac A.java 
$ java A
x:X@a63599 y:Y@9036e
x:null y:null

ที่สองแสดงให้เห็นว่าเส้นY.xinstanceและX.yinstanceเป็นnull ; พวกเขาเป็นโมฆะเพราะตัวแปรX.xinstanceและY.yinstanceถูกอ่านเมื่อพวกเขาเป็นโมฆะ

สามารถแก้ไขได้หรือไม่ ใช่,

class X {
    static Y y = Y.getInstance();
    static X theinstance;
    static X getInstance() {if(theinstance==null) {theinstance = new X();} return theinstance;}
}
class Y {
    static X x = X.getInstance();
    static Y theinstance;
    static Y getInstance() {if(theinstance==null) {theinstance = new Y();} return theinstance;}
}

public class A {
    public static void main(String[] p) {
    System.out.println("x:"+X.getInstance()+" y:"+Y.getInstance());
    System.out.println("x:"+Y.x+" y:"+X.y);
    }
}

และรหัสนี้ไม่แสดงความผิดปกติ:

$ javac A.java 
$ java A
x:X@1c059f6 y:Y@152506e
x:X@1c059f6 y:Y@152506e

แต่นี่ไม่ใช่ตัวเลือกสำหรับApplicationวัตถุAndroid : โปรแกรมเมอร์ไม่ได้ควบคุมเวลาเมื่อมันถูกสร้างขึ้น

อีกครั้ง: ความแตกต่างระหว่างตัวอย่างแรกและตัวอย่างที่สองคือตัวอย่างที่สองสร้างอินสแตนซ์ถ้าตัวชี้คงที่เป็นโมฆะ แต่โปรแกรมเมอร์ไม่สามารถสร้างวัตถุประยุกต์ใช้ Android ก่อนที่ระบบจะตัดสินใจที่จะทำมัน

UPDATE

nullตัวอย่างหนึ่งที่ทำให้งงมากขึ้นที่เริ่มต้นเขตข้อมูลแบบคงที่เกิดขึ้นจะเป็น

Main.java :

enum MyEnum {
    FIRST,SECOND;
    private static String prefix="<", suffix=">";
    String myName;
    MyEnum() {
        myName = makeMyName();
    }
    String makeMyName() {
        return prefix + name() + suffix;
    }
    String getMyName() {
        return myName;
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println("first: "+MyEnum.FIRST+" second: "+MyEnum.SECOND);
        System.out.println("first: "+MyEnum.FIRST.makeMyName()+" second: "+MyEnum.SECOND.makeMyName());
        System.out.println("first: "+MyEnum.FIRST.getMyName()+" second: "+MyEnum.SECOND.getMyName());
    }
}

และคุณจะได้รับ:

$ javac Main.java
$ java Main
first: FIRST second: SECOND
first: <FIRST> second: <SECOND>
first: nullFIRSTnull second: nullSECONDnull

โปรดทราบว่าคุณไม่สามารถย้ายการประกาศตัวแปรแบบคงที่หนึ่งบรรทัดบนโค้ดจะไม่คอมไพล์


3
ตัวอย่างที่มีประโยชน์ มันดีที่จะรู้ว่ามันมีรู สิ่งที่ฉันนำออกไปจากสิ่งนี้คือสิ่งหนึ่งที่ควรหลีกเลี่ยงการอ้างถึงตัวแปรแบบคงที่ในระหว่างการเริ่มต้นแบบคงที่ของคลาสใด ๆ
ToolmakerSteve

10

แอพลิเคชันชั้น:

import android.app.Application;
import android.content.Context;

public class MyApplication extends Application {

    private static Context mContext;

    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
    }

    public static Context getAppContext() {
        return mContext;
    }

}

ประกาศแอปพลิเคชันใน AndroidManifest:

<application android:name=".MyApplication"
    ...
/>

การใช้งาน:

MyApplication.getAppContext()

1
มีแนวโน้มที่จะรั่วไหลของหน่วยความจำ คุณไม่ควรทำเช่นนี้
Dragas

9

คุณกำลังพยายามสร้าง wrapper เพื่อรับบริบทของแอปพลิเคชันและมีความเป็นไปได้ที่มันอาจกลับมาnullชี้ ""

ตามความเข้าใจของผมผมคิดว่าวิธีการที่ดีในการ call- ใด ๆ ของ 2 หรือContext.getApplicationContext() Activity.getApplication()


13
เมื่อใดที่ควรจะคืนค่าว่าง
ติด

25
Context.getApplicationContext () ไม่มีวิธีการคงที่ฉันรู้ ฉันพลาดอะไรไปรึเปล่า?
dalcantara

ฉันยังใช้วิธีการเดียวกันในแอปพลิเคชันของฉัน แต่เมื่อโทรใน SQLiteOpenHelper มันจะส่งคืนพอยน์เตอร์พอยน์เตอร์ คำตอบสำหรับสถานการณ์ประเภทนี้ใด ๆ
Ashutosh

2
อาจเป็นกรณีนี้หากคุณเรียกใช้ SQLiteOpenHelper ใน contentprovider ซึ่งโหลดก่อนแอป
Gunnar Bernstein

5

มันเป็นวิธีการที่ดี ฉันใช้มันเองเช่นกัน ฉันขอแนะนำให้แทนที่onCreateการตั้งค่าซิงเกิลตันแทนที่จะใช้ constructor

และเมื่อคุณพูดถึงSQLiteOpenHelper: onCreate ()คุณสามารถเปิดฐานข้อมูลได้เช่นกัน

ส่วนตัวผมคิดว่าเอกสารได้มันผิดในการบอกว่ามีเป็นปกติจำเป็นต้องประยุกต์ใช้ subclass ไม่มี ฉันคิดว่าสิ่งที่ตรงข้ามเป็นจริง: คุณควรใช้คลาสย่อยเสมอ


3

ฉันจะใช้ Application Context เพื่อรับ System Service ใน Constructor วิธีนี้ช่วยลดการทดสอบและประโยชน์จากการจัดองค์ประกอบ

public class MyActivity extends Activity {

    private final NotificationManager notificationManager;

    public MyActivity() {
       this(MyApp.getContext().getSystemService(NOTIFICATION_SERVICE));
    }

    public MyActivity(NotificationManager notificationManager) {
       this.notificationManager = notificationManager;
    }

    // onCreate etc

}

คลาสทดสอบจะใช้ Constructor ที่โอเวอร์โหลด

Android จะใช้ตัวสร้างเริ่มต้น


1

ฉันชอบมัน แต่ฉันขอแนะนำซิงเกิลตัน:

package com.mobidrone;

import android.app.Application;
import android.content.Context;

public class ApplicationContext extends Application
{
    private static ApplicationContext instance = null;

    private ApplicationContext()
    {
        instance = this;
    }

    public static Context getInstance()
    {
        if (null == instance)
        {
            instance = new ApplicationContext();
        }

        return instance;
    }
}

31
การขยาย android.app.application ได้รับประกันซิงเกิลตันอยู่แล้วดังนั้นมันจึงไม่จำเป็น
Vincent

8
ถ้าคุณต้องการ acess จากคลาสที่ไม่ใช่กิจกรรม
Maxrunner

9
คุณไม่ควรnewสมัครด้วยตนเอง (ยกเว้นการทดสอบหน่วยที่เป็นไปได้) ระบบปฏิบัติการจะทำเช่นนั้น คุณไม่ควรมีคอนสตรัคเตอร์ นั่นคือสิ่งที่onCreateมีไว้เพื่อ
Martin

@Vincent: คุณสามารถโพสต์ลิงค์นี้ได้ไหม? รหัสที่ดีกว่า - ฉันกำลังถามที่นี่: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D

@radzio ทำไมเราไม่ควรทำมันในนวกรรมิก?
Miha_x64

1

ฉันใช้วิธีเดียวกันฉันแนะนำให้เขียน singleton ดีกว่า:

public static MyApp getInstance() {

    if (instance == null) {
        synchronized (MyApp.class) {
            if (instance == null) {
                instance = new MyApp ();
            }
        }
    }

    return instance;
}

แต่ฉันไม่ได้ใช้ทุกที่ฉันใช้getContext()และgetApplicationContext()ฉันจะทำยังไงดี!


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

1
ไม่จำเป็นต้องเป็นระบบปฏิบัติการเพื่อให้แน่ใจว่าแอปพลิเคชันนั้นได้รับการยกตัวอย่างทันที หากฉันแนะนำให้ตั้ง Singelton เป็น onCreate ()
Martin

1
วิธีที่ดีในการใช้เธรดที่ปลอดภัยในการเริ่มต้นซิงเกิลทีสันหลังยาว แต่ไม่จำเป็นต้องใช้ที่นี่
naXa

2
ว้าวเพียงเมื่อฉันคิดว่าคนในที่สุดเขาก็หยุดใช้ตรวจสอบการล็อค ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.