ความแตกต่างระหว่างวิธีการต่างๆในการรับบริบทคืออะไร


390

ในส่วนต่าง ๆ ของรหัส Android ฉันได้เห็น:

 public class MyActivity extends Activity {
    public void method() {
       mContext = this;    // since Activity extends Context
       mContext = getApplicationContext();
       mContext = getBaseContext();
    }
 }

อย่างไรก็ตามฉันไม่สามารถหาคำอธิบายที่เหมาะสมซึ่งเป็นที่ต้องการและภายใต้สถานการณ์ที่ควรใช้

ตัวชี้ไปยังเอกสารเกี่ยวกับเรื่องนี้และคำแนะนำเกี่ยวกับสิ่งที่อาจแตกถ้าเลือกผิดจะได้รับการชื่นชมมาก


2
ลิงค์นี้อาจช่วยคุณได้ ผ่านไปนี้ ..
Aju

คำตอบ:


305

ฉันยอมรับว่าเอกสารนั้นกระจัดกระจายเมื่อพูดถึงบริบทใน Android แต่คุณสามารถรวบรวมข้อเท็จจริงบางอย่างจากแหล่งต่าง ๆ ได้

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

ในแอปพลิเคชัน Android ปกติคุณมักจะมีบริบทสองประเภทกิจกรรมและแอปพลิเคชัน

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

ฉันไม่พบอะไรเลยเกี่ยวกับเวลาที่จะใช้ getBaseContext () นอกเหนือจากโพสต์จาก Dianne Hackborn ซึ่งเป็นหนึ่งในวิศวกรของ Google ที่ทำงานบน Android SDK:

อย่าใช้ getBaseContext () เพียงแค่ใช้บริบทที่คุณมี

นั่นคือจากการโพสต์ในกลุ่มข่าวนักพัฒนา androidคุณอาจต้องการพิจารณาถามคำถามของคุณที่นั่นเช่นกันเพราะมีคนจำนวนหนึ่งที่ทำงานบน Android จริง ๆ คอยตรวจสอบว่ากลุ่มข่าวสารและตอบคำถาม

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


13
เมื่อฉันมีกิจกรรม A ซึ่งสามารถเริ่มกิจกรรม B ซึ่งในทางกลับกันสามารถรีสตาร์ท A ด้วยการตั้งค่าสถานะ CLEAR_TOP (และอาจทำซ้ำรอบนี้หลายครั้ง) - บริบทใดที่ฉันควรใช้ในกรณีนี้เพื่อหลีกเลี่ยงการสร้างเส้นทางขนาดใหญ่ บริบทที่อ้างอิง? ไดอาน่ากล่าวว่าการใช้ 'this' แทนที่จะใช้ getBaseContext แต่แล้ว ... ส่วนใหญ่ A จะถูกนำกลับมาใช้ใหม่ แต่มีสถานการณ์เมื่อวัตถุใหม่สำหรับ A จะถูกสร้างขึ้นและจากนั้น A เก่าจะรั่วไหล ดังนั้นดูเหมือนว่า getBaseContext เป็นตัวเลือกที่เหมาะสมที่สุดสำหรับกรณีส่วนใหญ่ Don't use getBaseContext()แล้วก็ไม่ชัดเจนว่าทำไม ใครช่วยอธิบายเรื่องนี้ได้บ้าง
JBM

2
หนึ่งจะเข้าถึงวัตถุบริบทภายในชั้นเรียนที่ไม่ได้ขยายกิจกรรมอย่างไร
โคล

1
@Cole คุณสามารถสร้างคลาสได้ซึ่งเราจะเรียกว่า "ExampleClass" ที่นี่ซึ่ง Constructor ใช้วัตถุ Context และอินสแตนซ์ของคลาสอินสแตนซ์ตัวแปร "appContext" จากนั้นคลาสกิจกรรมของคุณ (หรือคลาสอื่น ๆ สำหรับเรื่องนั้น) สามารถเรียกเมธอด ExampleClass ที่ใช้ประโยชน์จากตัวแปรอินสแตนซ์ ExampleClass '"appContext"
Archie1986

54

นี่คือสิ่งที่ฉันได้พบเกี่ยวกับการใช้context:

1) ภายในActivityตัวเองใช้thisสำหรับพองรูปแบบและเมนูลงทะเบียนเมนูบริบท instantiating เครื่องมือเริ่มต้นกิจกรรมอื่น ๆ ที่สร้างใหม่IntentภายในActivity, อินสแตนซ์ตั้งค่าหรือวิธีการอื่น ๆ Activityที่มีอยู่ใน

เค้าโครงพองตัว:

View mView = this.getLayoutInflater().inflate(R.layout.myLayout, myViewGroup);

เมนูขยาย:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    this.getMenuInflater().inflate(R.menu.mymenu, menu);
    return true;
}

ลงทะเบียนเมนูบริบท:

this.registerForContextMenu(myView);

วิดเจ็ตอินสแตนซ์:

TextView myTextView = (TextView) this.findViewById(R.id.myTextView);

เริ่ม Activity :

Intent mIntent = new Intent(this, MyActivity.class);
this.startActivity(mIntent);

การตั้งค่าอินสแตนซ์:

SharedPreferences mSharedPreferences = this.getPreferenceManager().getSharedPreferences();

2)สำหรับคลาสทั้งแอปพลิเคชันให้ใช้getApplicationContext()ตามบริบทนี้สำหรับอายุแอปพลิเคชัน

ดึงชื่อของแพ็คเกจ Android ปัจจุบัน:

public class MyApplication extends Application {    
    public static String getPackageName() {
        String packageName = null;
        try {
            PackageInfo mPackageInfo = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0);
            packageName = mPackageInfo.packageName;
        } catch (NameNotFoundException e) {
            // Log error here.
        }
        return packageName;
    }
}

ผูกคลาสทั่วทั้งแอปพลิเคชัน:

Intent mIntent = new Intent(this, MyPersistent.class);
MyServiceConnection mServiceConnection = new MyServiceConnection();
if (mServiceConnection != null) {
    getApplicationContext().bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}

3)สำหรับ Listeners และคลาส Android อื่น ๆ (เช่น ContentObserver) ให้ใช้การทดแทนบริบทเช่น:

mContext = this;    // Example 1
mContext = context; // Example 2

ที่ไหน thisหรือcontextเป็นบริบทของชั้นเรียน (กิจกรรม ฯลฯ )

Activity การทดแทนบริบท:

public class MyActivity extends Activity {
    private Context mContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        mContext = this;
    }
}

การทดแทนบริบทผู้ฟัง:

public class MyLocationListener implements LocationListener {
    private Context mContext;
    public MyLocationListener(Context context) {
        mContext = context;
    }
}

ContentObserver การทดแทนบริบท:

public class MyContentObserver extends ContentObserver {
    private Context mContext;
    public MyContentObserver(Handler handler, Context context) {
        super(handler);
        mContext = context;
    }
}

4)สำหรับBroadcastReceiver(รวมถึงผู้รับแบบอินไลน์ / ฝังตัว) ให้ใช้บริบทของผู้รับเอง

ภายนอกBroadcastReceiver:

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (action.equals(Intent.ACTION_SCREEN_OFF)) {
            sendReceiverAction(context, true);
        }
        private static void sendReceiverAction(Context context, boolean state) {
            Intent mIntent = new Intent(context.getClass().getName() + "." + context.getString(R.string.receiver_action));
            mIntent.putExtra("extra", state);
            context.sendBroadcast(mIntent, null);
        }
    }
}

ที่ฝัง / ฝังBroadcastReceiver:

public class MyActivity extends Activity {
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final boolean connected = intent.getBooleanExtra(context.getString(R.string.connected), false);
            if (connected) {
                // Do something.
            }
        }
    };
}

5) สำหรับบริการใช้บริบทของบริการ

public class MyService extends Service {
    private BroadcastReceiver mBroadcastReceiver;
    @Override
    public void onCreate() {
        super.onCreate();
        registerReceiver();
    }
    private void registerReceiver() {
        IntentFilter mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        this.mBroadcastReceiver = new MyBroadcastReceiver();
        this.registerReceiver(this.mBroadcastReceiver, mIntentFilter);
    } 
}

6)สำหรับ Toasts โดยทั่วไปใช้getApplicationContext()แต่เป็นไปได้ใช้บริบทที่ส่งผ่านจากกิจกรรมบริการ ฯลฯ

ใช้บริบทของแอปพลิเคชัน:

Toast mToast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG);
mToast.show();

ใช้บริบทที่ส่งจากแหล่งข้อมูล:

public static void showLongToast(Context context, String message) {
    if (context != null && message != null) {
        Toast mToast = Toast.makeText(context, message, Toast.LENGTH_LONG);
        mToast.show();
    }
}

และสุดท้ายอย่าใช้ getBaseContext()ตามคำแนะนำของนักพัฒนาเฟรมเวิร์กของ Android

UPDATE:เพิ่มตัวอย่างContextการใช้งาน


1
แทนที่จะ mContext หนึ่งสามารถใช้OuterClass.this; เห็นความคิดเห็นในstackoverflow.com/questions/9605459/ …
พอล Verest

3
+1 สำหรับคำตอบที่เป็นประโยชน์! ฉันเห็นด้วยว่าคำตอบที่ได้รับการยอมรับนั้นเป็นคำตอบที่ได้รับการยอมรับ แต่ศักดิ์สิทธิ์คำตอบนี้เป็นข้อมูลที่ยอดเยี่ยมมาก! ขอบคุณสำหรับตัวอย่างเหล่านั้นพวกเขาช่วยให้ฉันเข้าใจการใช้บริบทโดยรวมดีขึ้น ฉันได้คัดลอกคำตอบของคุณลงในไฟล์ข้อความบนเครื่องของฉันเพื่อใช้อ้างอิง
Ryan

13

ฉันอ่านกระทู้นี้ไม่กี่วันที่ผ่านมาถามตัวเองคำถามเดียวกัน การตัดสินใจของฉันหลังจากอ่านข้อความนี้เป็นเรื่องง่าย: ใช้ applicationContext เสมอ

อย่างไรก็ตามฉันพบปัญหากับสิ่งนี้ฉันใช้เวลาสองสามชั่วโมงเพื่อค้นหามันและไม่กี่วินาทีในการแก้ปัญหา ... (เปลี่ยนหนึ่งคำ ... )

ฉันใช้ LayoutInflater เพื่อขยายมุมมองที่มีสปินเนอร์

ดังนั้นนี่คือความเป็นไปได้สองอย่าง:

1)

    LayoutInflater layoutInflater = LayoutInflater.from(this.getApplicationContext());

2)

    LayoutInflater layoutInflater = LayoutInflater.from(this.getBaseContext());

จากนั้นฉันกำลังทำสิ่งนี้:

    // managing views part
    View view = ContactViewer.mLayoutInflater.inflate(R.layout.aViewContainingASpinner, theParentView, false);
    Spinner spinner = (Spinner) view.findViewById(R.id.theSpinnerId);
    String[] myStringArray = new String[] {"sweet","love"};

    // managing adapter part
    // The context used here don't have any importance -- both work.
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this.getApplicationContext(), myStringArray, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);

    theParentView.addView(view);

สิ่งที่ฉันสังเกตเห็น: ถ้าคุณยกตัวอย่าง LinearLayout ของคุณด้วย applicationContext จากนั้นเมื่อคุณคลิกที่สปินเนอร์ในกิจกรรมของคุณคุณจะมีข้อยกเว้นที่ไม่สามารถตรวจจับได้มาจากเครื่องเสมือน dalvik (ไม่ใช่จากรหัสของคุณ ของเวลาในการค้นหาความผิดพลาดของฉันอยู่ที่ไหน ... )

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

ดังนั้นนี่คือข้อสรุปของฉัน: ฉันคิดว่า (ฉันยังไม่ได้ทดสอบมันเพิ่มเติม) กว่าที่จำเป็นต้องมี baseContext เมื่อจัดการกับ contextMenu ในกิจกรรมของคุณ ...

การทดสอบทำโค้ดด้วย API 8 และทดสอบกับ HTC Desire, android 2.3.3

ฉันหวังว่าความคิดเห็นของฉันไม่ได้เบื่อคุณและหวังว่าคุณจะดีที่สุด Happy coding ;-)


ฉันใช้ "สิ่งนี้" เสมอเมื่อสร้างมุมมองในกิจกรรม บนพื้นฐานที่ว่าหากกิจกรรมเริ่มต้นใหม่มุมมองจะถูกจัดใหม่และอาจมีบริบทใหม่ที่จะใช้ในการทำให้มุมมองจากอีกครั้ง ข้อเสียเปรียบที่โพสต์ในบล็อกของนักพัฒนาคือในขณะที่ ImageView ถูก destoryed drawable / bitmap ที่ใช้อาจแขวนเข้ากับบริบทนั้น อย่างไรก็ตามนั่นคือสิ่งที่ฉันทำในขณะนี้ เกี่ยวกับรหัสที่อื่นในแอพ (คลาสปกติ) ฉันแค่ใช้บริบทของแอปพลิเคชันซึ่งไม่เฉพาะเจาะจงกับกิจกรรมหรือองค์ประกอบ UI
JonWillis

6

ก่อนอื่นฉันยอมรับว่าเราควรใช้ appcontext ทุกครั้งที่ทำได้ จากนั้น "นี่" ในกิจกรรม ฉันไม่เคยต้องการ basecontext

ในการทดสอบของฉันในกรณีส่วนใหญ่พวกเขาสามารถแลกเปลี่ยน ในกรณีส่วนใหญ่เหตุผลที่คุณต้องการได้รับบริบทคือการเข้าถึงไฟล์การตั้งค่าฐานข้อมูล ฯลฯ ข้อมูลเหล่านี้ในที่สุดจะแสดงเป็นไฟล์ในโฟลเดอร์ข้อมูลส่วนตัวของแอป (/ data / data /) ไม่ว่าคุณจะใช้บริบทใดพวกเขาจะถูกแมปไปยังโฟลเดอร์ / ไฟล์เดียวกันดังนั้นคุณจึงตกลง

นั่นคือสิ่งที่ฉันสังเกต อาจมีหลายกรณีที่คุณควรแยกแยะความแตกต่าง


ฉันต้องการ basecontext เพื่อตั้งค่าภาษาแอปทั่วโลกเมื่อเริ่มต้น (เมื่อไม่ตรงกับภาษาเริ่มต้นของโทรศัพท์)
Tina

3

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

((YourActivity) context).yourCallbackMethod(yourResultFromThread, ...);

2

ในคำง่าย ๆ

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

getActivity()หรือจะทำให้แอปของคุณตระหนักถึงหน้าจอปัจจุบันซึ่งมองเห็นได้ยังระดับแอปรายละเอียดการให้บริการโดยthis application contextดังนั้นสิ่งที่คุณต้องการรู้เกี่ยวกับหน้าจอปัจจุบันเช่นนี้Window ActionBar Fragementmangerและสามารถใช้ได้กับบริบทนี้ โดยทั่วไปและการขยายActivity Contextบริบทนี้จะมีชีวิตอยู่จนกระทั่งองค์ประกอบปัจจุบัน (กิจกรรม) ยังมีชีวิตอยู่


1

ความสับสนเกิดจากความจริงที่ว่ามีหลายวิธีในการเข้าถึงบริบทด้วย (บนพื้นผิว) ไม่มีความแตกต่างที่มองเห็นได้ ด้านล่างเป็นวิธีทั่วไปสี่ประการที่คุณอาจเข้าถึงบริบทในกิจกรรม

getContext()
getBaseContext()
getApplicationContext()
getActionBar().getThemedContext() //new

บริบทคืออะไร ฉันชอบคิดว่าบริบทเป็นสถานะของใบสมัครของคุณในเวลาใดก็ตาม บริบทของแอปพลิเคชันแสดงถึงการกำหนดค่าส่วนกลางหรือฐานของแอปพลิเคชันของคุณและกิจกรรมหรือบริการสามารถสร้างขึ้นมาได้และแสดงถึงอินสแตนซ์การกำหนดค่าของแอปพลิเคชันของคุณหรือสถานะสกรรมกริยา

หากคุณดูที่แหล่งที่มาสำหรับ android.content.Context คุณจะเห็นว่าบริบทเป็นคลาสนามธรรมและความคิดเห็นในชั้นเรียนมีดังนี้

ส่วนต่อประสานกับข้อมูลทั่วโลกเกี่ยวกับสภาพแวดล้อมของแอปพลิเคชัน นี่เป็นคลาสนามธรรมที่มีการใช้งานโดยระบบ Android อนุญาตให้เข้าถึงapplication-specificทรัพยากรและคลาสรวมถึงการเรียกใช้สำหรับapplication-levelการดำเนินการเช่นการเปิดตัวกิจกรรมการแพร่ภาพและการรับเจตนา ฯลฯ สิ่งที่ฉันนำไปจากสิ่งนี้คือบริบทให้การใช้งานร่วมกันเพื่อเข้าถึงระดับแอปพลิเคชันเช่นเดียวกับระดับระบบ ทรัพยากร ทรัพยากรระดับแอปพลิเคชันอาจเข้าถึงสิ่งต่าง ๆ เช่นทรัพยากร String [getResources()]หรือสินทรัพย์[getAssets()]และทรัพยากรระดับระบบเป็นสิ่งที่คุณเข้าถึงด้วยContext.getSystemService().

ตามความเป็นจริงลองดูที่ความคิดเห็นเกี่ยวกับวิธีการและพวกเขาดูเหมือนจะเสริมความคิดนี้:

getSystemService(): คืนหมายเลขอ้างอิงไปยังsystem-levelบริการตามชื่อ คลาสของวัตถุที่ส่งคืนจะแตกต่างกันไปตามชื่อที่ร้องขอ getResources(): ส่งคืนอินสแตนซ์แหล่งข้อมูลสำหรับแพ็คเกจแอปพลิเคชันของคุณ getAssets(): ส่งคืนอินสแตนซ์แหล่งข้อมูลสำหรับแพ็คเกจแอปพลิเคชันของคุณ มันอาจจะคุ้มค่าที่ชี้ให้เห็นว่าในคลาสนามธรรมบริบททั้งหมดของวิธีการข้างต้นเป็นนามธรรม! getSystemService (Class) อินสแตนซ์เดียวเท่านั้นที่มีการนำไปใช้และเรียกใช้เมธอด abstract ซึ่งหมายความว่าการดำเนินการสำหรับสิ่งเหล่านี้ควรจัดให้เป็นส่วนใหญ่โดยการใช้งานชั้นเรียนซึ่งรวมถึง:

ContextWrapper
Application
Activity
Service
IntentService

ดูที่เอกสาร API, ลำดับชั้นของคลาสมีลักษณะดังนี้:

บริบท

| - ContextWrapper

| - - ใบสมัคร

| - - ContextThemeWrapper

| - - - - กิจกรรม

| - - บริการ

| - - - IntentService

เนื่องจากเรารู้ว่าContextตัวเองไม่ได้ให้ข้อมูลเชิงลึกใด ๆ เราจึงย้ายลงมาที่ต้นไม้และดูที่ContextWrapperและตระหนักว่ามีไม่มากเช่นกัน ตั้งแต่การประยุกต์ใช้ขยายที่มีอยู่ไม่มากที่จะมองไปที่นั่นอย่างใดอย่างหนึ่งเพราะมันไม่ได้แทนที่การใช้งานที่มีให้โดยContextWrapper ซึ่งหมายความว่าการดำเนินการสำหรับบริบทที่ให้บริการโดยระบบปฏิบัติการและถูกซ่อนจากContextWrapper APIคุณสามารถดูการใช้บริบทสำหรับบริบทได้อย่างเป็นรูปธรรมโดยดูที่แหล่งที่มาสำหรับคลาส ContextImpl


0

ฉันเพิ่งใช้มันและgetBaseContextเมื่อ toasting จากonClick(noob สีเขียวมากทั้ง Java และ Android) ฉันใช้สิ่งนี้เมื่อตัวคลิกของฉันอยู่ในกิจกรรมโดยตรงและต้องใช้getBaseContextในตัวคลิกภายในที่ไม่ระบุชื่อ ฉันเดาว่านี่เป็นกลอุบายที่ค่อนข้างมากgetBaseContextบางทีอาจกลับบริบทของกิจกรรมที่ชั้นในซ่อนตัวอยู่


1
นี่เป็นสิ่งที่ผิดมันกำลังส่งคืนบริบทฐานของกิจกรรมเอง ที่จะได้รับกิจกรรม (หนึ่งคุณต้องการที่จะใช้เป็นบริบท) MyActivity.thisจากที่ไม่ระบุชื่อบางสิ่งบางอย่างใช้ระดับชั้นเช่น การใช้บริบทฐานตามที่คุณอธิบายอาจไม่ทำให้เกิดปัญหา แต่มันผิด
nickmartens1980
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.