มีวิธีรับContext
อินสแตนซ์ปัจจุบันในวิธีการคงที่หรือไม่
ฉันกำลังมองหาวิธีนี้เพราะฉันเกลียดการบันทึกอินสแตนซ์ 'บริบท' ทุกครั้งที่มีการเปลี่ยนแปลง
Context
อาจมีวิธีที่ดีกว่าในการออกแบบรหัส
มีวิธีรับContext
อินสแตนซ์ปัจจุบันในวิธีการคงที่หรือไม่
ฉันกำลังมองหาวิธีนี้เพราะฉันเกลียดการบันทึกอินสแตนซ์ 'บริบท' ทุกครั้งที่มีการเปลี่ยนแปลง
Context
อาจมีวิธีที่ดีกว่าในการออกแบบรหัส
คำตอบ:
ทำเช่นนี้:
ในไฟล์ Android Manifest ให้ประกาศสิ่งต่อไปนี้
<application android:name="com.xyz.MyApplication">
</application>
จากนั้นเขียนชั้นเรียน
public class MyApplication extends Application {
private static Context context;
public void onCreate() {
super.onCreate();
MyApplication.context = getApplicationContext();
}
public static Context getAppContext() {
return MyApplication.context;
}
}
ตอนนี้ทุกที่โทรMyApplication.getAppContext()
เพื่อรับบริบทใบสมัครของคุณแบบคงที่
static context
ตัวแปรนี้ด้วยvolatile
เหรอ?
แอพพลิเคชั่นส่วนใหญ่ที่ต้องการวิธีการที่สะดวกในการทำให้บริบทของแอพพลิเคชั่นสร้างคลาสของตัวเองandroid.app.Application
ขึ้นมา
GUIDE
คุณสามารถทำได้โดยการสร้างชั้นเรียนในโครงการของคุณเช่น:
import android.app.Application;
import android.content.Context;
public class App extends Application {
private static Application sApplication;
public static Application getApplication() {
return sApplication;
}
public static Context getContext() {
return getApplication().getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
sApplication = this;
}
}
จากนั้นใน AndroidManifest ของคุณคุณควรระบุชื่อคลาสของคุณในแท็กของ AndroidManifest.xml:
<application
...
android:name="com.example.App" >
...
</application>
จากนั้นคุณสามารถดึงบริบทแอปพลิเคชันในวิธีการคงที่ใด ๆ โดยใช้ต่อไปนี้:
public static void someMethod() {
Context context = App.getContext();
}
คำเตือน
ก่อนที่จะเพิ่มบางอย่างเช่นข้างต้นให้กับโครงการของคุณคุณควรพิจารณาว่าเอกสารระบุว่า:
โดยปกติไม่จำเป็นต้องใช้คลาสย่อย ในสถานการณ์ส่วนใหญ่ Singletons แบบคงที่สามารถให้ฟังก์ชั่นเดียวกันในทางโมดูลาร์มากขึ้น หากซิงเกิลของคุณต้องการบริบทส่วนกลาง (ตัวอย่างเช่นการลงทะเบียนรีซีฟเวอร์รีซีฟเวอร์) ฟังก์ชั่นเพื่อดึงข้อมูลสามารถรับบริบทซึ่งใช้ Context.getApplicationContext () ภายในเมื่อสร้างซิงเกิลครั้งแรก
การสะท้อน
นอกจากนี้ยังมีวิธีอื่นในการรับบริบทของแอปพลิเคชันโดยใช้การสะท้อน การสะท้อนกลับมักถูกมองลงใน Android และโดยส่วนตัวฉันคิดว่าสิ่งนี้ไม่ควรใช้ในการผลิต
ในการเรียกคืนบริบทแอปพลิเคชันเราจะต้องเรียกใช้เมธอดในคลาสที่ซ่อนอยู่ ( ActivityThread ) ซึ่งใช้ได้ตั้งแต่ API 1:
public static Application getApplicationUsingReflection() throws Exception {
return (Application) Class.forName("android.app.ActivityThread")
.getMethod("currentApplication").invoke(null, (Object[]) null);
}
มีคลาสที่ซ่อนอยู่อีกหนึ่งรายการ ( AppGlobals ) ซึ่งให้วิธีรับบริบทแอปพลิเคชันในแบบคงที่ มันทำให้บริบทใช้ActivityThread
ดังนั้นจึงไม่มีความแตกต่างระหว่างวิธีต่อไปนี้กับวิธีที่โพสต์ด้านบน:
public static Application getApplicationUsingReflection() throws Exception {
return (Application) Class.forName("android.app.AppGlobals")
.getMethod("getInitialApplication").invoke(null, (Object[]) null);
}
การเข้ารหัสที่มีความสุข!
สมมติว่าเรากำลังพูดถึงการรับบริบทของแอปพลิเคชันฉันได้ดำเนินการตามที่แนะนำโดย @Rohit Ghatol การขยายแอปพลิเคชัน สิ่งที่เกิดขึ้นในตอนนั้นก็คือไม่มีการรับประกันว่าบริบทที่ดึงมาในลักษณะนี้จะไม่เป็นโมฆะ ในเวลาที่คุณต้องการมันมักจะเป็นเพราะคุณต้องการเริ่มต้นช่วยเหลือหรือได้รับทรัพยากรที่คุณไม่สามารถล่าช้าในเวลา; การจัดการเคสว่างจะไม่ช่วยคุณ ดังนั้นฉันเข้าใจว่าฉันกำลังต่อสู้กับสถาปัตยกรรม Android ตามที่ระบุในเอกสาร
หมายเหตุ: โดยปกติไม่จำเป็นต้องใช้คลาสย่อย ในสถานการณ์ส่วนใหญ่ Singletons แบบคงที่สามารถให้ฟังก์ชั่นเดียวกันในทางโมดูลาร์มากขึ้น หากซิงเกิลของคุณต้องการบริบทส่วนกลาง (ตัวอย่างเช่นในการลงทะเบียนเครื่องรับสัญญาณออกอากาศ) ให้รวม Context.getApplicationContext () เป็นอาร์กิวเมนต์บริบทเมื่อเรียกใช้เมธอด getInstance () ของซิงเกิล
และอธิบายโดยDianne Hackborn
เหตุผลเดียวที่แอปพลิเคชันมีอยู่เป็นสิ่งที่คุณสามารถหาได้เนื่องจากในระหว่างการพัฒนา pre-1.0 หนึ่งในนักพัฒนาแอปพลิเคชันของเราถูก bugging ฉันอย่างต่อเนื่องเกี่ยวกับการต้องการวัตถุแอปพลิเคชันระดับบนสุดที่พวกเขาสามารถสืบทอด "สำหรับพวกเขาในรูปแบบของแอปพลิเคชันและในที่สุดฉันก็ยอมแพ้ฉันจะเสียใจในสิ่งนั้นตลอดไป :)
เธอยังแนะนำวิธีแก้ปัญหานี้ด้วย:
หากสิ่งที่คุณต้องการคือสถานะทั่วโลกที่สามารถแชร์ข้ามส่วนต่าง ๆ ของแอพของคุณได้ [... ] และสิ่งนี้นำไปสู่การจัดการสิ่งเหล่านี้อย่างเป็นธรรมชาติมากขึ้นโดยเริ่มต้นได้ตามต้องการ
ดังนั้นสิ่งที่ฉันทำคือกำจัดแอปพลิเคชันที่ขยายออกและส่งบริบทโดยตรงไปยัง getInstance () ของผู้ช่วยซิงเกิลตันในขณะที่บันทึกการอ้างอิงไปยังบริบทของแอปพลิเคชันใน Constructor ส่วนตัว:
private static MyHelper instance;
private final Context mContext;
private MyHelper(@NonNull Context context) {
mContext = context.getApplicationContext();
}
public static MyHelper getInstance(@NonNull Context context) {
synchronized(MyHelper.class) {
if (instance == null) {
instance = new MyHelper(context);
}
return instance;
}
}
ผู้โทรจะส่งบริบทท้องถิ่นไปยังผู้ช่วย:
Helper.getInstance(myCtx).doSomething();
ดังนั้นเพื่อตอบคำถามนี้อย่างถูกต้อง: มีวิธีเข้าถึงบริบทของแอปพลิเคชันแบบคงที่ แต่พวกเขาควรหมดกำลังใจและคุณควรส่งบริบทท้องถิ่นไปที่ getInstance () ของซิงเกิล
สำหรับผู้ที่สนใจคุณสามารถอ่านรายละเอียดเพิ่มเติมได้ที่fwd blog
getInstance(ctx)
ภาพแผนภูมิการอ้างอิงหลังจากที่สายแรกของคุณไป คุณมีราก GC instance
ประเภทMyHelper
ซึ่งมีข้อมูลส่วนตัวmContext
ของประเภทซึ่งอ้างอิงบริบทแอพลิเคชันที่เก็บรวบรวมผ่านบริบทที่ผ่านมา Context
ไม่เคยตั้งเป็นครั้งที่สองหรือเคลียร์เพื่อให้ประชาคมโลกจะไม่จับ appcontext อ้างอิงโดย คุณไม่รั่วไหลกิจกรรมใด ๆ ดังนั้นมันจึงเป็น IMO ที่ราคาถูก getInstance()
instance
instance
this
ในApplication.onCreate()
ซึ่งจะทำให้คำตอบที่ได้รับการยอมรับที่ดีขึ้น
ไม่ฉันไม่คิดว่าจะมี แต่น่าเสียดายที่คุณโทรติดgetApplicationContext()
จากActivity
หรือหนึ่ง subclasses อื่น ๆ Context
ของ นอกจากนี้คำถามนี้ค่อนข้างเกี่ยวข้องกัน
นี่คือวิธีที่ไม่มีเอกสารในการรับแอปพลิเคชัน (ซึ่งเป็นบริบท) จากทุกที่ในเธรด UI ActivityThread.currentApplication()
มันอาศัยอยู่กับวิธีการแบบคงที่ซ่อนอยู่ มันควรจะทำงานอย่างน้อยใน Android 4.x
try {
final Class<?> activityThreadClass =
Class.forName("android.app.ActivityThread");
final Method method = activityThreadClass.getMethod("currentApplication");
return (Application) method.invoke(null, (Object[]) null);
} catch (final ClassNotFoundException e) {
// handle exception
} catch (final NoSuchMethodException e) {
// handle exception
} catch (final IllegalArgumentException e) {
// handle exception
} catch (final IllegalAccessException e) {
// handle exception
} catch (final InvocationTargetException e) {
// handle exception
}
โปรดทราบว่าเป็นไปได้ที่วิธีนี้จะคืนค่า null เช่นเมื่อคุณเรียกใช้เมธอดภายนอกเธรด UI หรือแอปพลิเคชันไม่ได้ผูกไว้กับเธรด
การใช้โซลูชันของ@RohitGhatolจะดีกว่าหากคุณสามารถเปลี่ยนรหัสแอปพลิเคชันได้
ขึ้นอยู่กับสิ่งที่คุณใช้บริบท ฉันสามารถคิดถึงข้อเสียอย่างน้อยหนึ่งวิธี:
ถ้าคุณกำลังพยายามที่จะสร้างAlertDialog
กับAlertDialog.Builder
ที่Application
บริบทจะไม่ทำงาน ฉันเชื่อว่าคุณต้องการบริบทสำหรับปัจจุบันActivity
...
วิธี Kotlin :
Manifest:
<application android:name="MyApplication">
</application>
MyApplication.kt
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
lateinit var instance: MyApplication
private set
}
}
จากนั้นคุณสามารถเข้าถึงคุณสมบัติผ่าน MyApplication.instance
หากคุณเปิดให้ใช้RoboGuiceคุณสามารถใส่บริบทเข้าไปในชั้นเรียนที่คุณต้องการ นี่คือตัวอย่างเล็ก ๆ ของวิธีการทำกับ RoboGuice 2.0 (เบต้า 4 ในขณะที่เขียนนี้)
import android.content.Context;
import android.os.Build;
import roboguice.inject.ContextSingleton;
import javax.inject.Inject;
@ContextSingleton
public class DataManager {
@Inject
public DataManager(Context context) {
Properties properties = new Properties();
properties.load(context.getResources().getAssets().open("data.properties"));
} catch (IOException e) {
}
}
}
ฉันเคยใช้สิ่งนี้ในบางจุด:
ActivityThread at = ActivityThread.systemMain();
Context context = at.getSystemContext();
นี่เป็นบริบทที่ถูกต้องที่ฉันใช้ในการรับบริการระบบและทำงานได้
แต่ฉันใช้มันเฉพาะในการแก้ไขเฟรมเวิร์ก / ฐานและไม่ได้ลองในแอปพลิเคชัน Android
เตือนว่าคุณต้องรู้: เมื่อลงทะเบียนรับการออกอากาศกับบริบทนี้มันจะไม่ทำงานและคุณจะได้รับ:
java.lang.SecurityException: ชุดผู้โทรที่ได้รับ android ไม่ได้ทำงานในกระบวนการ ProcessRecord
open class MyApp : Application() {
override fun onCreate() {
super.onCreate()
mInstance = this
}
companion object {
lateinit var mInstance: MyApp
fun getContext(): Context? {
return mInstance.applicationContext
}
}
}
และรับบริบทเช่น
MyApp.mInstance
หรือ
MyApp.getContext()
คุณสามารถใช้สิ่งต่อไปนี้:
MainActivity.this.getApplicationContext();
MainActivity.java:
...
public class MainActivity ... {
static MainActivity ma;
...
public void onCreate(Bundle b) {
super...
ma=this;
...
คลาสอื่น ๆ :
public ...
public ANY_METHOD... {
Context c = MainActivity.ma.getApplicationContext();
หากคุณไม่ต้องการแก้ไขไฟล์รายการคุณสามารถจัดเก็บบริบทด้วยตนเองในตัวแปรสแตติกในกิจกรรมเริ่มต้นของคุณ:
public class App {
private static Context context;
public static void setContext(Context cntxt) {
context = cntxt;
}
public static Context getContext() {
return context;
}
}
และเพียงกำหนดบริบทเมื่อกิจกรรม (หรือกิจกรรม) ของคุณเริ่มต้น:
// MainActivity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set Context
App.setContext(getApplicationContext());
// Other stuff
}
หมายเหตุ:เช่นเดียวกับคำตอบอื่น ๆ ทั้งหมดนี่เป็นความจำที่อาจรั่วไหล
ฉันคิดว่าคุณต้องการร่างกายสำหรับgetAppContext()
วิธีการ:
public static Context getAppContext()
return MyApplication.context;
ตามแหล่งที่มานี้คุณสามารถรับบริบทของคุณเองโดยขยาย ContextWrapper
public class SomeClass extends ContextWrapper {
public SomeClass(Context base) {
super(base);
}
public void someMethod() {
// notice how I can use "this" for Context
// this works because this class has it's own Context just like an Activity or Service
startActivity(this, SomeRealActivity.class);
//would require context too
File cacheDir = getCacheDir();
}
}
การนำพร็อกซีมาใช้ของบริบทที่เพียงมอบหมายการเรียกทั้งหมดไปยังบริบทอื่น สามารถ subclassed เพื่อปรับเปลี่ยนพฤติกรรมโดยไม่ต้องเปลี่ยนบริบทเดิม
หากคุณต้องการบริบทของแอปพลิเคชันในชั้นเรียนไม่ว่าจะเป็นการขยายแอพพลิเคชั่น / กิจกรรมบางทีสำหรับบางคลาสจากโรงงานหรือผู้ช่วย คุณสามารถเพิ่มซิงเกิลต่อไปนี้ในแอปของคุณ
public class GlobalAppContextSingleton {
private static GlobalAppContextSingleton mInstance;
private Context context;
public static GlobalAppContextSingleton getInstance() {
if (mInstance == null) mInstance = getSync();
return mInstance;
}
private static synchronized GlobalAppContextSingleton getSync() {
if (mInstance == null) mInstance =
new GlobalAppContextSingleton();
return mInstance;
}
public void initialize(Context context) {
this.context = context;
}
public Context getApplicationContext() {
return context;
}
}
จากนั้นเริ่มต้นได้ใน onCreate ด้วยคลาสแอปพลิเคชันของคุณ
GlobalAppContextSingleton.getInstance().initialize(this);
ใช้งานได้ทุกที่โดยการโทร
GlobalAppContextSingleton.getInstance().getApplicationContext()
ฉันไม่แนะนำวิธีการนี้ให้กับสิ่งใดนอกจากบริบทของแอปพลิเคชัน เนื่องจากอาจทำให้หน่วยความจำรั่ว
ฉันใช้รูปแบบการออกแบบซิงเกิลที่หลากหลายเพื่อช่วยฉันในเรื่องนี้
import android.app.Activity;
import android.content.Context;
public class ApplicationContextSingleton {
private static Activity gContext;
public static void setContext( Activity activity) {
gContext = activity;
}
public static Activity getActivity() {
return gContext;
}
public static Context getContext() {
return gContext;
}
}
จากนั้นฉันก็โทรหาApplicationContextSingleton.setContext( this );
ในกิจกรรมของฉันบนสร้าง()และApplicationContextSingleton.setContext( null );
ในonDestroy () ;
ฉันเพิ่งเปิดตัวเฟรมเวิร์กที่ได้รับแรงบันดาลใจจาก jQuery สำหรับ Android ที่เรียกว่าVapor APIที่มีวัตถุประสงค์เพื่อทำให้การพัฒนาแอปง่ายขึ้น
$
คลาสซุ้มกลางรักษาWeakReference
(ลิงก์ไปยังโพสต์บล็อก Java ที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้โดย Ethan Nicholas) ไปยังActivity
บริบทปัจจุบันซึ่งคุณสามารถดึงข้อมูลได้ด้วยการโทร:
$.act()
การWeakReference
เก็บรักษาข้อมูลอ้างอิงโดยไม่ป้องกันการรวบรวมขยะเรียกคืนวัตถุต้นฉบับดังนั้นคุณไม่ควรมีปัญหากับหน่วยความจำรั่ว
ข้อเสียของหลักสูตรคือคุณมีความเสี่ยงที่$.act()
อาจส่งคืนค่าว่าง ฉันยังไม่ได้เจอกับสถานการณ์นี้ดังนั้นอาจเป็นเพียงความเสี่ยงขั้นต่ำที่ควรค่าแก่การกล่าวถึง
คุณยังสามารถตั้งค่าบริบทด้วยตนเองหากคุณไม่ได้ใช้VaporActivity
เป็นActivity
คลาสของคุณ:
$.act(Activity);
นอกจากนี้เฟรมเวิร์กVapor API ส่วนใหญ่ใช้บริบทที่เก็บไว้โดยเนื้อแท้ซึ่งอาจหมายความว่าคุณไม่จำเป็นต้องจัดเก็บด้วยตัวคุณเองเลยถ้าคุณตัดสินใจใช้เฟรมเวิร์ก ตรวจสอบเว็บไซต์สำหรับข้อมูลเพิ่มเติมและตัวอย่าง
ฉันหวังว่าจะช่วย :)
คำตอบของ Rohit นั้นถูกต้อง อย่างไรก็ตามโปรดทราบว่า "การเรียกใช้ทันที" ของ AndroidStudio ขึ้นอยู่กับการไม่มีstatic Context
แอตทริบิวต์ในรหัสของคุณเท่าที่ฉันรู้
ใน Kotlin การวางบริบท / แอปบริบทในวัตถุร่วมยังคงสร้างคำเตือน Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)
หรือถ้าคุณใช้สิ่งนี้:
companion object {
lateinit var instance: MyApp
}
เป็นเพียงการหลอกผ้าสำลีที่จะไม่ค้นพบการรั่วไหลของหน่วยความจำอินสแตนซ์ของแอปยังคงสามารถสร้างการรั่วไหลของหน่วยความจำได้เนื่องจากคลาสแอปพลิเคชันและผู้สืบทอดเป็นบริบท
หรือคุณสามารถใช้ส่วนต่อประสานการใช้งานหรือคุณสมบัติการใช้งานเพื่อช่วยให้คุณรับบริบทแอพของคุณ
เพียงสร้างคลาสวัตถุ:
object CoreHelper {
lateinit var contextGetter: () -> Context
}
หรือคุณสามารถใช้อย่างปลอดภัยมากขึ้นโดยใช้ประเภท nullable:
object CoreHelper {
var contextGetter: (() -> Context)? = null
}
และในคลาสแอพของคุณให้เพิ่มบรรทัดนี้:
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
CoreHelper.contextGetter = {
this
}
}
}
และในรายการของคุณประกาศชื่อแอป . MyApp
<application
android:name=".MyApp"
เมื่อคุณต้องการรับบริบทเพียงโทร:
CoreHelper.contextGetter()
// or if you use the nullable version
CoreHelper.contextGetter?.invoke()
หวังว่ามันจะช่วย
ลองอะไรเช่นนี้
import androidx.appcompat.app.AppCompatActivity; import android.content.Context; import android.os.Bundle; public class MainActivity extends AppCompatActivity { private static Context context; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = getApplicationContext(); } public static void getContext(View view){ Toast.makeText(context, "Got my context!", Toast.LENGTH_LONG).show(); } }