ฉันกำลังพยายามเขียนแอพที่ทำสิ่งที่เฉพาะเจาะจงเมื่อมันถูกนำกลับไปที่ส่วนหน้าหลังจากเวลาผ่านไประยะหนึ่ง มีวิธีการตรวจสอบหรือไม่เมื่อแอพถูกส่งไปที่พื้นหลังหรือถูกนำไปที่ฉากหน้า?
ฉันกำลังพยายามเขียนแอพที่ทำสิ่งที่เฉพาะเจาะจงเมื่อมันถูกนำกลับไปที่ส่วนหน้าหลังจากเวลาผ่านไประยะหนึ่ง มีวิธีการตรวจสอบหรือไม่เมื่อแอพถูกส่งไปที่พื้นหลังหรือถูกนำไปที่ฉากหน้า?
คำตอบ:
onPause()
และonResume()
วิธีการที่เรียกว่าเมื่อโปรแกรมประยุกต์ที่จะมาถึงพื้นหลังและเข้าไปในเบื้องหน้าอีกครั้ง อย่างไรก็ตามจะมีการเรียกเมื่อแอปพลิเคชั่นเริ่มต้นเป็นครั้งแรกและก่อนที่จะถูกฆ่า คุณสามารถอ่านเพิ่มเติมในกิจกรรม
ไม่มีวิธีการใด ๆ โดยตรงจะได้รับสถานะใบสมัครในขณะที่ในพื้นหลังหรือเบื้องหน้า แต่แม้ฉันต้องเผชิญกับปัญหานี้และพบว่าวิธีการแก้ปัญหาด้วยและonWindowFocusChanged
onStop
สำหรับรายละเอียดเพิ่มเติมตรวจสอบที่นี่Android: โซลูชั่นการตรวจสอบเมื่อ app Android ที่ไปที่พื้นหลังและกลับมาเบื้องหน้าโดยไม่ต้อง getRunningTasks หรือ getRunningAppProcesses
มีนาคม 2561 อัพเดท : ขณะนี้มีทางออกที่ดีกว่า ดูProcessLifecycleOwner คุณจะต้องใช้ส่วนประกอบสถาปัตยกรรมใหม่ 1.1.0 (ล่าสุดในขณะนี้) แต่เป็นเฉพาะรับการออกแบบมาเพื่อทำสิ่งนี้
มีตัวอย่างง่ายๆที่ให้ไว้ในคำตอบนี้แต่ฉันเขียนตัวอย่างแอพและโพสต์บล็อกเกี่ยวกับมัน
นับตั้งแต่ฉันเขียนบทความนี้ย้อนกลับไปในปี 2014 การแก้ปัญหาที่แตกต่างกันก็เกิดขึ้น บางคนทำงานบางคนคิดว่าจะทำงานแต่มีข้อบกพร่อง (รวมถึงของฉัน!) และเราในฐานะชุมชน (Android) เรียนรู้ที่จะอยู่กับผลที่ตามมาและเขียนวิธีแก้ปัญหาสำหรับกรณีพิเศษ
อย่าสันนิษฐานว่ามีตัวอย่างข้อมูลเพียงรหัสเดียวคือโซลูชันที่คุณกำลังมองหา ดีกว่ายังพยายามที่จะเข้าใจว่ามันทำอะไรและทำไมมันทำ
MemoryBoss
ชั้นก็ไม่เคยนำมาใช้จริงโดยผมเขียนนี่มันเป็นเพียงชิ้นส่วนของรหัสเทียมที่เกิดขึ้นกับการทำงาน
เว้นแต่จะมีเหตุผลที่ถูกต้องสำหรับคุณที่จะไม่ใช้ส่วนประกอบสถาปัตยกรรมใหม่ (และมีบางอย่างโดยเฉพาะถ้าคุณกำหนดเป้าหมาย apis เก่าสุด) จากนั้นไปข้างหน้าและใช้พวกเขา พวกเขาห่างไกลจากความสมบูรณ์แบบ ComponentCallbacks2
แต่ก็ยังไม่ได้
อัพเดท / หมายเหตุ (พฤศจิกายน 2558) : ผู้คนแสดงความคิดเห็นสองข้อก่อนอื่นคือ>=
ควรใช้แทน==
เพราะรัฐเอกสารที่คุณไม่ควรตรวจสอบค่าที่แน่นอน นี้เป็นที่ดีสำหรับกรณีส่วนใหญ่ แต่จำไว้ว่าถ้าคุณเพียงดูแลเกี่ยวกับการทำบางสิ่งบางอย่างเมื่อ app ไปพื้นหลังคุณจะต้องใช้ == และยังรวมกับวิธีอื่น (เช่นการเรียกกลับกิจกรรม Lifecycle) หรือคุณอาจไม่ได้รับผลที่คุณต้องการ ตัวอย่าง (และสิ่งนี้เกิดขึ้นกับฉัน) คือถ้าคุณต้องการล็อคแอปของคุณด้วยหน้าจอรหัสผ่านเมื่อไปที่พื้นหลัง (เช่น 1Password หากคุณคุ้นเคย) คุณอาจล็อคแอปของคุณโดยไม่ตั้งใจถ้าคุณใช้หน่วยความจำเหลือน้อยและมีการทดสอบโดยฉับพลัน>= TRIM_MEMORY
เพราะ Android จะเปิดการLOW MEMORY
โทร สูงกว่าของคุณ ดังนั้นควรระวังวิธี / สิ่งที่คุณทดสอบ
นอกจากนี้บางคนถามถึงวิธีการตรวจจับเมื่อคุณกลับมา
วิธีที่ง่ายที่สุดที่ฉันคิดได้อธิบายไว้ด้านล่าง แต่เนื่องจากบางคนไม่คุ้นเคยกับมันฉันจึงเพิ่มโค้ดหลอกบางส่วนที่นี่ สมมติว่าคุณมีYourApplication
และMemoryBoss
คลาสในของคุณclass BaseActivity extends Activity
(คุณจะต้องสร้างถ้าคุณไม่มี)
@Override
protected void onStart() {
super.onStart();
if (mApplication.wasInBackground()) {
// HERE YOU CALL THE CODE YOU WANT TO HAPPEN ONLY ONCE WHEN YOUR APP WAS RESUMED FROM BACKGROUND
mApplication.setWasInBackground(false);
}
}
ฉันแนะนำ onStart เนื่องจากกล่องโต้ตอบสามารถหยุดกิจกรรมชั่วคราวดังนั้นฉันจึงคิดว่าคุณไม่ต้องการให้แอปของคุณคิดว่า "มันเป็นพื้นหลัง" หากสิ่งที่คุณทำคือการแสดงข้อความเต็มหน้าจอ แต่ระยะทางของคุณอาจแตกต่างกันไป
และนั่นคือทั้งหมดที่ รหัสในบล็อก if จะถูกเรียกใช้งานเพียงครั้งเดียวแม้ว่าคุณจะไปที่กิจกรรมอื่น ๆ รายงานใหม่ (ที่ยังextends BaseActivity
) จะรายงานwasInBackground
คือfalse
มันจะไม่รันรหัสจนกว่าonMemoryTrimmed
จะถูกเรียกและตั้งค่าสถานะเป็นจริงอีกครั้ง .
หวังว่าจะช่วย
อัปเดต / หมายเหตุ (เมษายน 2558) : ก่อนที่คุณจะไปคัดลอกและวางบนรหัสนี้โปรดทราบว่าฉันได้พบสองสามกรณีที่อาจไม่น่าเชื่อถือ 100% และต้องใช้ร่วมกับวิธีอื่นเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด มีสองกรณีที่เป็นที่รู้จักonTrimMemory
เรียกกลับไม่ได้รับการรับประกันว่าจะดำเนินการ:
หากโทรศัพท์ของคุณล็อคหน้าจอในขณะที่แอปของคุณมองเห็นได้ (พูดว่าอุปกรณ์ของคุณล็อคหลังจาก nn นาที) การโทรกลับนี้จะไม่ถูกเรียก (หรือไม่เสมอไป) เพราะ lockscreen อยู่ด้านบน แต่แอปของคุณยังคง
หากอุปกรณ์ของคุณมีหน่วยความจำค่อนข้างต่ำ (และอยู่ภายใต้ความกดดันหน่วยความจำ) ระบบปฏิบัติการดูเหมือนจะไม่สนใจการโทรนี้และตรงไปยังระดับที่สำคัญยิ่งกว่า
ตอนนี้ขึ้นอยู่กับความสำคัญสำหรับคุณที่จะรู้ว่าเมื่อแอปของคุณไปที่พื้นหลังคุณอาจหรืออาจไม่จำเป็นต้องขยายโซลูชันนี้พร้อมกับติดตามวงจรชีวิตของกิจกรรมและอะไรก็ตาม
เพียงจำไว้ข้างต้นและมีทีมงาน QA ที่ดี;)
สิ้นสุดการอัพเดท
มันอาจจะสาย แต่มีวิธีที่เชื่อถือได้ในIce Cream Sandwich (API 14) และด้านบน
ปรากฎว่าเมื่อแอปของคุณไม่มี UI ที่มองเห็นได้อีกการโทรกลับจะถูกเรียกใช้ การเรียกกลับซึ่งคุณสามารถนำไปใช้ในคลาสที่กำหนดเองเรียกว่าComponentCallbacks2 (ใช่มีสองอัน) การโทรกลับนี้มีให้บริการเท่านั้นใน API ระดับ 14 (Ice Cream Sandwich) และสูงกว่า
โดยทั่วไปคุณจะได้รับการเรียกวิธีการ:
public abstract void onTrimMemory (int level)
ระดับคือ 20 ขึ้นไปโดยเฉพาะ
public static final int TRIM_MEMORY_UI_HIDDEN
ฉันได้ทำการทดสอบและใช้งานได้ดีเสมอเพราะระดับ 20 เป็นเพียง "ข้อเสนอแนะ" ที่คุณอาจต้องการปล่อยทรัพยากรบางอย่างเนื่องจากแอปของคุณไม่ปรากฏให้เห็นอีกต่อไป
หากต้องการอ้างอิงเอกสารอย่างเป็นทางการ:
ระดับสำหรับ onTrimMemory (int): กระบวนการแสดงส่วนต่อประสานผู้ใช้และไม่ทำเช่นนั้นอีกต่อไป การจัดสรร UI ขนาดใหญ่ควรได้รับการปล่อยตัวในเวลานี้เพื่อให้หน่วยความจำมีการจัดการที่ดีขึ้น
แน่นอนคุณควรใช้สิ่งนี้เพื่อทำสิ่งที่พูด (ล้างหน่วยความจำที่ไม่ได้ใช้ในบางเวลาล้างคอลเลกชันบางอย่างที่ไม่ได้ใช้งาน ฯลฯ ) ความเป็นไปได้ไม่มีที่สิ้นสุด (ดูเอกสารอย่างเป็นทางการสำหรับคนอื่นๆระดับวิกฤติ )
แต่สิ่งที่น่าสนใจคือระบบปฏิบัติการกำลังบอกคุณ: เฮ้แอปของคุณไปที่พื้นหลัง!
ซึ่งเป็นสิ่งที่คุณต้องการรู้ตั้งแต่แรก
คุณจะตัดสินอย่างไรเมื่อคุณกลับมา?
ง่ายมากฉันแน่ใจว่าคุณมี "BaseActivity" เพื่อให้คุณสามารถใช้ onResume () ของคุณเพื่อตั้งค่าสถานะความเป็นจริงที่คุณกลับมา เพราะมีเพียงครั้งเดียวที่คุณจะบอกว่าคุณไม่ได้กลับมาเมื่อคุณได้รับสายจริงonTrimMemory
วิธีการดังกล่าว
มันได้ผล. คุณไม่ได้รับผลบวกผิด ๆ หากกิจกรรมกลับมาทำงานต่อคุณกลับมาแล้ว 100% หากผู้ใช้ไปที่ด้านหลังอีกครั้งคุณจะได้รับonTrimMemory()
สายอีกครั้ง
คุณต้องประกาศกิจกรรมของคุณ (หรือดีกว่านั้นเป็นคลาสที่กำหนดเอง)
วิธีที่ง่ายที่สุดในการรับประกันว่าคุณจะได้รับสิ่งนี้เสมอคือการสร้างคลาสแบบง่าย ๆ ดังนี้:
public class MemoryBoss implements ComponentCallbacks2 {
@Override
public void onConfigurationChanged(final Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// We're in the Background
}
// you might as well implement some memory cleanup here and be a nice Android dev.
}
}
ในการใช้สิ่งนี้ในการติดตั้งแอปพลิเคชันของคุณ ( คุณมีใช่มั้ย? ) ทำสิ่งต่อไปนี้:
MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMemoryBoss = new MemoryBoss();
registerComponentCallbacks(mMemoryBoss);
}
}
หากคุณสร้างInterface
คุณสามารถเพิ่มสิ่งelse
นั้นif
และนำไปใช้ComponentCallbacks
(โดยไม่ต้องใช้ 2) ในสิ่งใดก็ตามด้านล่าง API 14 การโทรกลับนั้นมีเพียงonLowMemory()
วิธีการและไม่ได้รับการเรียกเมื่อคุณไปที่พื้นหลังแต่คุณควรใช้มันเพื่อตัดหน่วยความจำ .
ตอนนี้เปิดแอพแล้วกด home onTrimMemory(final int level)
ควรเรียกวิธีการของคุณ(คำใบ้: เพิ่มการบันทึก)
ขั้นตอนสุดท้ายคือการยกเลิกการลงทะเบียนจากการโทรกลับ สถานที่ที่ดีที่สุดน่าจะเป็นonTerminate()
วิธีการใช้แอพของคุณแต่วิธีการนั้นไม่ได้รับการเรียกใช้บนอุปกรณ์จริง:
/** * This method is for use in emulated process environments. It will * never be called on a production Android device, where processes are * removed by simply killing them; no user code (including this callback) * is executed when doing so. */
ดังนั้นหากคุณไม่มีสถานการณ์ที่ไม่ต้องการลงทะเบียนอีกต่อไปคุณสามารถละเว้นได้อย่างปลอดภัยเนื่องจากกระบวนการของคุณกำลังจะตายที่ระดับ OS ต่อไป
หากคุณตัดสินใจที่จะยกเลิกการลงทะเบียนในบางจุด (ตัวอย่างเช่นหากคุณมีกลไกการปิดระบบเพื่อให้แอปของคุณล้างและตาย) คุณสามารถทำได้:
unregisterComponentCallbacks(mMemoryBoss);
และนั่นคือมัน
level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
สิ่งที่หลีกเลี่ยงปัญหาในการอัปเดตของคุณจุดที่ 2 เกี่ยวกับจุดที่ 1 มันไม่ใช่เรื่องที่น่ากังวลสำหรับฉันเพราะแอพไม่ได้ไปที่พื้นหลังจริงๆ
นี่คือวิธีที่ฉันจัดการเพื่อแก้ไขปัญหานี้ มันทำงานบนสมมติฐานที่ว่าการใช้การอ้างอิงเวลาระหว่างการเปลี่ยนกิจกรรมมักจะให้หลักฐานที่เพียงพอว่าแอปได้รับ "แบ็กกราวน์" หรือไม่
อันดับแรกฉันใช้อินสแตนซ์ android.app.Application (เรียกว่า MyApplication) ซึ่งมีตัวจับเวลา TimerTask ค่าคงที่เพื่อแสดงจำนวนมิลลิวินาทีสูงสุดที่การเปลี่ยนจากกิจกรรมหนึ่งไปอีกกิจกรรมหนึ่งอาจทำได้อย่างสมเหตุสมผล (ฉันไป ด้วยค่า 2s) และบูลีนเพื่อระบุว่าแอปนั้น "อยู่ในพื้นหลัง" หรือไม่:
public class MyApplication extends Application {
private Timer mActivityTransitionTimer;
private TimerTask mActivityTransitionTimerTask;
public boolean wasInBackground;
private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
...
แอปพลิเคชันยังมีวิธีการสองวิธีสำหรับการเริ่มและหยุดตัวจับเวลา / งาน:
public void startActivityTransitionTimer() {
this.mActivityTransitionTimer = new Timer();
this.mActivityTransitionTimerTask = new TimerTask() {
public void run() {
MyApplication.this.wasInBackground = true;
}
};
this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask,
MAX_ACTIVITY_TRANSITION_TIME_MS);
}
public void stopActivityTransitionTimer() {
if (this.mActivityTransitionTimerTask != null) {
this.mActivityTransitionTimerTask.cancel();
}
if (this.mActivityTransitionTimer != null) {
this.mActivityTransitionTimer.cancel();
}
this.wasInBackground = false;
}
ส่วนสุดท้ายของโซลูชันนี้คือการเพิ่มการเรียกไปยังแต่ละวิธีเหล่านี้จากเหตุการณ์ onResume () และ onPause () ของกิจกรรมทั้งหมดหรือโดยเฉพาะอย่างยิ่งในกิจกรรมพื้นฐานที่กิจกรรมคอนกรีตของคุณสืบทอด:
@Override
public void onResume()
{
super.onResume();
MyApplication myApp = (MyApplication)this.getApplication();
if (myApp.wasInBackground)
{
//Do specific came-here-from-background code
}
myApp.stopActivityTransitionTimer();
}
@Override
public void onPause()
{
super.onPause();
((MyApplication)this.getApplication()).startActivityTransitionTimer();
}
ดังนั้นในกรณีที่ผู้ใช้เพียงแค่นำทางระหว่างกิจกรรมของแอพของคุณ onPause () ของกิจกรรมการออกจะเริ่มตัวจับเวลา แต่เกือบจะทันทีที่กิจกรรมใหม่ที่เข้ามายกเลิกตัวจับเวลาก่อนที่จะถึงเวลาการเปลี่ยนสูงสุด และดังนั้นพื้นหลังจะเป็นเท็จเท็จ
ในทางกลับกันเมื่อมีกิจกรรมมาถึงเบื้องหน้าจากเปิดอุปกรณ์ตื่นขึ้นมาวางสายโทรศัพท์ ฯลฯ กว่าจะงานจับเวลาดำเนินการก่อนที่จะมีเหตุการณ์นี้และทำให้wasInBackgroundถูกกำหนดให้เป็นความจริง
แก้ไข:องค์ประกอบสถาปัตยกรรมใหม่นำเสนอสิ่งที่มีแนวโน้ม: ProcessLifecycleOwnerดูคำตอบของ @ vokilam
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(AppLifecycleTracker())
}
}
class AppLifecycleTracker : Application.ActivityLifecycleCallbacks {
private var numStarted = 0
override fun onActivityStarted(activity: Activity?) {
if (numStarted == 0) {
// app went to foreground
}
numStarted++
}
override fun onActivityStopped(activity: Activity?) {
numStarted--
if (numStarted == 0) {
// app went to background
}
}
}
ใช่. ฉันรู้ว่ามันยากที่จะเชื่อว่าโซลูชันง่าย ๆ นี้ทำงานได้เนื่องจากเรามีโซลูชันแปลก ๆ มากมายที่นี่
แต่มีความหวัง
ProcessLifecycleOwner
ดูเหมือนว่าจะเป็นทางออกที่มีแนวโน้มด้วย
ProcessLifecycleOwner จะส่ง
ON_START
,ON_RESUME
เหตุการณ์ที่เกิดขึ้นเป็นย้ายกิจกรรมแรกผ่านเหตุการณ์เหล่านี้ON_PAUSE
,ON_STOP
กิจกรรมจะถูกส่งไปพร้อมกับความล่าช้าหลังจากกิจกรรมครั้งสุดท้ายผ่านไปแล้ว ความล่าช้านี้นานพอที่จะรับประกันว่าProcessLifecycleOwner
จะไม่ส่งกิจกรรมใด ๆ หากกิจกรรมถูกทำลายและสร้างใหม่เนื่องจากการเปลี่ยนแปลงการกำหนดค่า
การใช้งานสามารถทำได้ง่ายเพียงแค่
class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() { // app moved to foreground
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() { // app moved to background
}
}
// register observer
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
700ms
ตามรหัสที่มาล่าช้ามูลค่าปัจจุบันคือ
การใช้คุณสมบัตินี้ต้องการdependencies
:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
implementation "android.arch.lifecycle:extensions:1.0.0"
และannotationProcessor "android.arch.lifecycle:compiler:1.0.0"
จากที่เก็บของ Google (เช่นgoogle()
)
จากคำตอบของMartín Marconcinis (ขอบคุณ!) ในที่สุดฉันก็พบวิธีแก้ปัญหาที่เชื่อถือได้
public class ApplicationLifecycleHandler implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
private static final String TAG = ApplicationLifecycleHandler.class.getSimpleName();
private static boolean isInBackground = false;
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
if(isInBackground){
Log.d(TAG, "app went to foreground");
isInBackground = false;
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int i) {
if(i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN){
Log.d(TAG, "app went to background");
isInBackground = true;
}
}
}
จากนั้นเพิ่มสิ่งนี้ลงใน onCreate () ของคลาสแอปพลิเคชันของคุณ
public class MyApp extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
registerActivityLifecycleCallbacks(handler);
registerComponentCallbacks(handler);
}
}
เราใช้วิธีนี้ มันดูง่ายเกินไปที่จะทำงาน แต่มันผ่านการทดสอบอย่างดีในแอพของเราและในความเป็นจริงแล้วมันทำงานได้ดีอย่างน่าประหลาดใจในทุกกรณีรวมถึงการไปที่หน้าจอหลักด้วยปุ่ม "home" โดยปุ่ม "return" หรือหลังจากล็อกหน้าจอ ให้มันลอง.
แนวคิดคือเมื่ออยู่เบื้องหน้า Android จะเริ่มกิจกรรมใหม่เสมอก่อนที่จะหยุดกิจกรรมก่อนหน้านี้ ไม่รับประกัน แต่นั่นเป็นวิธีการทำงาน BTW, วุ่นวายดูเหมือนจะใช้ตรรกะเดียวกัน (เพียงเดาฉันไม่ได้ตรวจสอบว่า แต่ตะขอในเหตุการณ์เดียวกัน)
public abstract class BaseActivity extends Activity {
private static int sessionDepth = 0;
@Override
protected void onStart() {
super.onStart();
sessionDepth++;
if(sessionDepth == 1){
//app came to foreground;
}
}
@Override
protected void onStop() {
super.onStop();
if (sessionDepth > 0)
sessionDepth--;
if (sessionDepth == 0) {
// app went to background
}
}
}
แก้ไข: ตามความคิดเห็นเรายังย้ายไปที่ onStart () ในรหัสรุ่นที่ใหม่กว่า นอกจากนี้ฉันกำลังเพิ่มการโทรสุด ๆ ซึ่งหายไปจากโพสต์เริ่มต้นของฉันเพราะนี่เป็นแนวคิดมากกว่าโค้ดที่ใช้งานได้
onStop is called when the activity is no longer visible to the user
.
หากแอปของคุณประกอบด้วยหลายกิจกรรมและ / หรือกิจกรรมที่ซ้อนกันเช่นวิดเจ็ตแถบแท็บการแทนที่ onPause () และ onResume () จะไม่ทำงาน เช่นเมื่อเริ่มกิจกรรมใหม่กิจกรรมปัจจุบันจะหยุดชั่วคราวก่อนที่กิจกรรมใหม่จะถูกสร้างขึ้น เช่นเดียวกับเมื่อเสร็จสิ้น (โดยใช้ปุ่ม "ย้อนกลับ") กิจกรรม
ฉันพบสองวิธีที่ดูเหมือนจะทำงานได้ตามที่ต้องการ
คนแรกต้องได้รับอนุญาต GET_TASKS และประกอบด้วยวิธีการง่าย ๆ ที่ตรวจสอบว่ากิจกรรมที่ทำงานอยู่บนอุปกรณ์เป็นของแอปพลิเคชันหรือไม่โดยการเปรียบเทียบชื่อแพคเกจ:
private boolean isApplicationBroughtToBackground() {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasks = am.getRunningTasks(1);
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
if (!topActivity.getPackageName().equals(context.getPackageName())) {
return true;
}
}
return false;
}
วิธีนี้พบในกรอบการทำงานของ Droid-Fu (ปัจจุบันเรียกว่าระบบจุดระเบิด)
วิธีที่สองที่ฉันใช้เองไม่ต้องได้รับอนุญาตจาก GET_TASKS ซึ่งดี แต่มันซับซ้อนกว่าเล็กน้อยที่จะใช้
ในคลาส MainApplication ของคุณคุณมีตัวแปรที่ติดตามจำนวนกิจกรรมการทำงานในแอปพลิเคชันของคุณ ใน onResume () สำหรับแต่ละกิจกรรมคุณเพิ่มตัวแปรและใน onPause () คุณลดมันลง
เมื่อจำนวนกิจกรรมที่รันถึง 0 แอปพลิเคชันจะถูกใส่ลงในพื้นหลังหากเงื่อนไขต่อไปนี้เป็นจริง:
เมื่อคุณสามารถตรวจพบว่าแอปพลิเคชันลาออกไปที่พื้นหลังมันจะตรวจจับได้ง่ายเมื่อนำกลับมาที่พื้นหน้าเช่นกัน
สร้างชั้นApplication
ที่ขยาย จากนั้นเราสามารถใช้วิธีการแทนที่, onTrimMemory()
.
ในการตรวจสอบว่าแอปพลิเคชันไปที่พื้นหลังหรือไม่เราจะใช้:
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Works for Activity
// Get called every-time when application went to background.
}
else if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { // Works for FragmentActivty
}
}
FragmentActivity
คุณอาจต้องการเพิ่มlevel == ComponentCallbacks2.TRIM_MEMORY_COMPLETE
ด้วย
พิจารณาใช้ onUserLeaveHint สิ่งนี้จะถูกเรียกเมื่อแอปของคุณเข้าสู่พื้นหลัง onPause จะมีมุมเคสที่ต้องจัดการเนื่องจากสามารถถูกเรียกด้วยเหตุผลอื่น ตัวอย่างเช่นหากผู้ใช้เปิดกิจกรรมอื่นในแอปของคุณเช่นหน้าการตั้งค่าวิธีการ onPause ของกิจกรรมหลักของคุณจะถูกเรียกแม้ว่าพวกเขาจะยังคงอยู่ในแอปของคุณ การติดตามสิ่งที่กำลังเกิดขึ้นจะนำไปสู่ข้อผิดพลาดเมื่อคุณสามารถใช้การติดต่อกลับ onUserLeaveHint แทนซึ่งทำสิ่งที่คุณขอ
เมื่อเรียกใช้ UserLeaveHint คุณสามารถตั้งค่าบูลีน inBackground ให้เป็นจริง เมื่อเรียกว่า onResume ให้ถือว่าคุณกลับมาที่หน้าก่อนหากตั้งค่าสถานะ inBackground นี่เป็นเพราะ onResume จะถูกเรียกใช้ในกิจกรรมหลักของคุณหากผู้ใช้อยู่ในเมนูการตั้งค่าของคุณและไม่เคยออกจากแอป
โปรดจำไว้ว่าหากผู้ใช้กดปุ่มหน้าหลักในขณะที่อยู่ในหน้าจอการตั้งค่าของคุณ onUserLeaveHint จะถูกเรียกในกิจกรรมการตั้งค่าของคุณและเมื่อพวกเขากลับมาที่รีบูตจะถูกเรียกในกิจกรรมการตั้งค่าของคุณ หากคุณมีรหัสตรวจจับนี้ในกิจกรรมหลักของคุณคุณจะพลาดกรณีการใช้งานนี้ หากต้องการให้รหัสนี้ในกิจกรรมทั้งหมดของคุณโดยไม่ต้องทำรหัสซ้ำให้มีคลาสกิจกรรมที่เป็นนามธรรมซึ่งขยายกิจกรรมและใส่รหัสทั่วไปลงไป จากนั้นแต่ละกิจกรรมที่คุณสามารถขยายกิจกรรมนามธรรมนี้ได้
ตัวอย่างเช่น:
public abstract AbstractActivity extends Activity {
private static boolean inBackground = false;
@Override
public void onResume() {
if (inBackground) {
// You just came from the background
inBackground = false;
}
else {
// You just returned from another activity within your own app
}
}
@Override
public void onUserLeaveHint() {
inBackground = true;
}
}
public abstract MainActivity extends AbstractActivity {
...
}
public abstract SettingsActivity extends AbstractActivity {
...
}
ActivityLifecycleCallbacksอาจเป็นที่สนใจ แต่ก็ยังไม่ได้บันทึกไว้อย่างดี
แม้ว่าถ้าคุณเรียกregisterActivityLifecycleCallbacks () คุณควรจะได้รับ callback เมื่อกิจกรรมถูกสร้างทำลาย ฯลฯ คุณสามารถเรียกgetComponentName () สำหรับกิจกรรมได้
android.arch.lifecycleแพคเกจให้เรียนและอินเตอร์เฟซที่ช่วยให้คุณสร้างส่วนประกอบวงจรทราบ
แอปพลิเคชันของคุณควรใช้อินเทอร์เฟซ LifecycleObserver:
public class MyApplication extends Application implements LifecycleObserver {
@Override
public void onCreate() {
super.onCreate();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private void onAppBackgrounded() {
Log.d("MyApp", "App in background");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
private void onAppForegrounded() {
Log.d("MyApp", "App in foreground");
}
}
ในการทำเช่นนั้นคุณต้องเพิ่มการขึ้นต่อกันนี้ในไฟล์ build.gradle ของคุณ:
dependencies {
implementation "android.arch.lifecycle:extensions:1.1.1"
}
ตามที่ Google แนะนำคุณควรย่อเล็กสุดรหัสที่ประมวลผลในวิธีการดำเนินชีวิต:
รูปแบบทั่วไปคือการใช้การกระทำของส่วนประกอบที่พึ่งพาในวิธีวงจรชีวิตของกิจกรรมและชิ้นส่วน อย่างไรก็ตามรูปแบบนี้นำไปสู่องค์กรที่ไม่ดีของรหัสและการแพร่กระจายของข้อผิดพลาด ด้วยการใช้ส่วนประกอบที่คำนึงถึงวงจรชีวิตคุณสามารถย้ายรหัสของส่วนประกอบที่พึ่งพาได้จากวิธีวงจรชีวิตและเข้าไปในส่วนประกอบด้วยตนเอง
คุณสามารถอ่านเพิ่มเติมได้ที่นี่: https://developer.android.com/topic/l ไลบรารี/architecture/lifecycle
ในแอปพลิเคชันของคุณเพิ่มการติดต่อกลับและตรวจสอบกิจกรรมรูทในลักษณะดังนี้:
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity.isTaskRoot() && !(activity instanceof YourSplashScreenActivity)) {
Log.e(YourApp.TAG, "Reload defaults on restoring from background.");
loadDefaults();
}
}
});
}
ฉันได้สร้างโครงการบน Github app-foreground-background-Listen
สร้าง BaseActivity สำหรับกิจกรรมทั้งหมดในแอปพลิเคชันของคุณ
public class BaseActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
public static boolean isAppInFg = false;
public static boolean isScrInFg = false;
public static boolean isChangeScrFg = false;
@Override
protected void onStart() {
if (!isAppInFg) {
isAppInFg = true;
isChangeScrFg = false;
onAppStart();
}
else {
isChangeScrFg = true;
}
isScrInFg = true;
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
if (!isScrInFg || !isChangeScrFg) {
isAppInFg = false;
onAppPause();
}
isScrInFg = false;
}
public void onAppStart() {
// Remove this toast
Toast.makeText(getApplicationContext(), "App in foreground", Toast.LENGTH_LONG).show();
// Your code
}
public void onAppPause() {
// Remove this toast
Toast.makeText(getApplicationContext(), "App in background", Toast.LENGTH_LONG).show();
// Your code
}
}
ตอนนี้ใช้ BaseActivity เป็นคลาสซุปเปอร์ของกิจกรรมทั้งหมดของคุณเช่น MainActivity ขยาย BaseActivity และ onAppStart จะถูกเรียกเมื่อคุณเริ่มโปรแกรมประยุกต์ของคุณและ onAppPause () จะถูกเรียกเมื่อแอปพลิเคชันไปที่พื้นหลังจากหน้าจอใด ๆ
กระบวนการนี้ค่อนข้างง่ายด้วยProcessLifecycleOwner
เพิ่มการพึ่งพาเหล่านี้
implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"
ในKotlin :
class ForegroundBackgroundListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startSomething() {
Log.v("ProcessLog", "APP IS ON FOREGROUND")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopSomething() {
Log.v("ProcessLog", "APP IS IN BACKGROUND")
}
}
จากนั้นในกิจกรรมพื้นฐานของคุณ:
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get()
.lifecycle
.addObserver(
ForegroundBackgroundListener()
.also { appObserver = it })
}
ดูบทความของฉันในหัวข้อนี้: https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48
คุณสามารถใช้ProcessLifecycleOwnerแนบผู้สังเกตการณ์วงจรชีวิต
public class ForegroundLifecycleObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onAppCreated() {
Timber.d("onAppCreated() called");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onAppStarted() {
Timber.d("onAppStarted() called");
}
@OnLifecycleEvent(Event.ON_RESUME)
public void onAppResumed() {
Timber.d("onAppResumed() called");
}
@OnLifecycleEvent(Event.ON_PAUSE)
public void onAppPaused() {
Timber.d("onAppPaused() called");
}
@OnLifecycleEvent(Event.ON_STOP)
public void onAppStopped() {
Timber.d("onAppStopped() called");
}
}
จากนั้นในonCreate()
คลาสแอปพลิเคชันของคุณคุณเรียกสิ่งนี้ว่า:
ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());
ด้วยสิ่งนี้คุณจะสามารถจับภาพกิจกรรมON_PAUSE
และON_STOP
แอปพลิเคชันของคุณที่เกิดขึ้นเมื่อมันอยู่ในพื้นหลัง
ไม่มีวิธีวงจรชีวิตที่ตรงไปตรงมาที่จะบอกคุณเมื่อแอปพลิเคชันทั้งหมดไปที่พื้นหลัง / เบื้องหน้า
ฉันทำสิ่งนี้ด้วยวิธีที่ง่าย ทำตามคำแนะนำด้านล่างเพื่อตรวจสอบพื้นหลังแอปพลิเคชัน / เบื้องหน้าเฟส
ด้วยวิธีแก้ปัญหาเพียงเล็กน้อยก็เป็นไปได้ ที่นี่ActivityLifecycleCallbacksมาช่วยเหลือ ให้ฉันเดินผ่านทีละขั้นตอน
ขั้นแรกให้สร้างคลาสที่ขยายandroid.app.Applicationและใช้ส่วนต่อประสานActivityLifecycleCallbacks ใน Application.onCreate () ให้ลงทะเบียนการติดต่อกลับ
public class App extends Application implements
Application.ActivityLifecycleCallbacks {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
}
การลงทะเบียน“App” <application android:name=".App"
ชั้นในเป็นที่ประจักษ์ด้านล่าง
จะมีกิจกรรมอย่างน้อยหนึ่งรายการในสถานะเริ่มต้นเมื่อแอปอยู่ในเบื้องหน้าและจะไม่มีกิจกรรมในสถานะเริ่มต้นเมื่อแอปอยู่ในพื้นหลัง
ประกาศตัวแปร 2 ตัวดังต่อไปนี้ในคลาส“ แอป”
private int activityReferences = 0;
private boolean isActivityChangingConfigurations = false;
activityReferences
จะเก็บจำนวนกิจกรรมในสถานะเริ่มต้น isActivityChangingConfigurations
เป็นแฟล็กเพื่อระบุว่ากิจกรรมปัจจุบันกำลังดำเนินการเปลี่ยนแปลงการกำหนดค่าเช่นสวิตช์การวางแนวหรือไม่
การใช้รหัสต่อไปนี้คุณสามารถตรวจสอบได้ว่าแอปมาก่อนหน้าหรือไม่
@Override
public void onActivityStarted(Activity activity) {
if (++activityReferences == 1 && !isActivityChangingConfigurations) {
// App enters foreground
}
}
นี่คือวิธีการตรวจสอบว่าแอปทำงานเป็นพื้นหลังหรือไม่
@Override
public void onActivityStopped(Activity activity) {
isActivityChangingConfigurations = activity.isChangingConfigurations();
if (--activityReferences == 0 && !isActivityChangingConfigurations) {
// App enters background
}
}
มันทำงานอย่างไร:
นี่เป็นเคล็ดลับเล็ก ๆ น้อย ๆ ที่ทำกับวิธีการที่เรียกว่า Lifecycle ตามลำดับ ให้ฉันแนะนำสถานการณ์
สมมติว่าผู้ใช้เปิดแอพและเรียกใช้งานกิจกรรมตัวเรียกใช้งาน A การโทรระยะเวลาจะเป็น
A.onCreate ()
A.onStart () (++ activityReferences == 1) (แอปเข้าสู่เบื้องหน้า)
A.onResume ()
กิจกรรม A เริ่มกิจกรรม B
A.onPause ()
B.onCreate ()
B.onStart () (++ activityReferences == 2)
B.onResume ()
A.onStop () (--activityReferences == 1)
จากนั้นผู้ใช้นำทางกลับจากกิจกรรม B
B.onPause ()
A.onStart () (++ activityReferences == 2)
A.onResume ()
B.onStop () (--activityReferences == 1)
B.onDestroy ()
จากนั้นผู้ใช้กดปุ่มโฮม
A.onPause ()
A.onStop () (--activityReferences == 0) (แอปเข้าสู่พื้นหลัง)
ในกรณีที่ผู้ใช้กดปุ่มโฮมจากกิจกรรม B แทนปุ่มย้อนกลับก็จะยังคงเหมือนเดิมและกิจกรรมการอ้างอิงจะเป็น 0
เช่นเดิม ดังนั้นเราสามารถตรวจจับได้ว่าเป็นแอปที่เข้าสู่พื้นหลัง
ดังนั้นบทบาทของisActivityChangingConfigurations
อะไร ในสถานการณ์ข้างต้นสมมติว่ากิจกรรม B เปลี่ยนการวางแนว ลำดับการติดต่อกลับจะเป็น
B.onPause ()
B.onStop () (--activityReferences == 0) (แอปเข้าสู่พื้นหลัง ??)
B.onDestroy ()
B.onCreate ()
B.onStart () (++ activityReferences == 1) (แอปเข้าสู่ Foreground ??)
B.onResume ()
นั่นเป็นเหตุผลที่เรามีการตรวจสอบเพิ่มเติมisActivityChangingConfigurations
เพื่อหลีกเลี่ยงสถานการณ์เมื่อกิจกรรมผ่านการเปลี่ยนแปลงการกำหนดค่า
ฉันพบวิธีที่ดีในการตรวจสอบแอปพลิเคชันไม่ว่าจะใส่เบื้องหน้าหรือพื้นหลัง นี่คือฉันรหัส หวังว่านี่จะช่วยคุณได้
/**
* Custom Application which can detect application state of whether it enter
* background or enter foreground.
*
* @reference http://www.vardhan-justlikethat.blogspot.sg/2014/02/android-solution-to-detect-when-android.html
*/
public abstract class StatusApplication extends Application implements ActivityLifecycleCallbacks {
public static final int STATE_UNKNOWN = 0x00;
public static final int STATE_CREATED = 0x01;
public static final int STATE_STARTED = 0x02;
public static final int STATE_RESUMED = 0x03;
public static final int STATE_PAUSED = 0x04;
public static final int STATE_STOPPED = 0x05;
public static final int STATE_DESTROYED = 0x06;
private static final int FLAG_STATE_FOREGROUND = -1;
private static final int FLAG_STATE_BACKGROUND = -2;
private int mCurrentState = STATE_UNKNOWN;
private int mStateFlag = FLAG_STATE_BACKGROUND;
@Override
public void onCreate() {
super.onCreate();
mCurrentState = STATE_UNKNOWN;
registerActivityLifecycleCallbacks(this);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
// mCurrentState = STATE_CREATED;
}
@Override
public void onActivityStarted(Activity activity) {
if (mCurrentState == STATE_UNKNOWN || mCurrentState == STATE_STOPPED) {
if (mStateFlag == FLAG_STATE_BACKGROUND) {
applicationWillEnterForeground();
mStateFlag = FLAG_STATE_FOREGROUND;
}
}
mCurrentState = STATE_STARTED;
}
@Override
public void onActivityResumed(Activity activity) {
mCurrentState = STATE_RESUMED;
}
@Override
public void onActivityPaused(Activity activity) {
mCurrentState = STATE_PAUSED;
}
@Override
public void onActivityStopped(Activity activity) {
mCurrentState = STATE_STOPPED;
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
mCurrentState = STATE_DESTROYED;
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (mCurrentState == STATE_STOPPED && level >= TRIM_MEMORY_UI_HIDDEN) {
if (mStateFlag == FLAG_STATE_FOREGROUND) {
applicationDidEnterBackground();
mStateFlag = FLAG_STATE_BACKGROUND;
}
}else if (mCurrentState == STATE_DESTROYED && level >= TRIM_MEMORY_UI_HIDDEN) {
if (mStateFlag == FLAG_STATE_FOREGROUND) {
applicationDidDestroyed();
mStateFlag = FLAG_STATE_BACKGROUND;
}
}
}
/**
* The method be called when the application been destroyed. But when the
* device screen off,this method will not invoked.
*/
protected abstract void applicationDidDestroyed();
/**
* The method be called when the application enter background. But when the
* device screen off,this method will not invoked.
*/
protected abstract void applicationDidEnterBackground();
/**
* The method be called when the application enter foreground.
*/
protected abstract void applicationWillEnterForeground();
}
แก้ไข 2: สิ่งที่ฉันเขียนด้านล่างจะไม่ทำงาน Google ปฏิเสธแอปที่รวมการโทรไปที่ ActivityManager.getRunningTasks () จากเอกสารประกอบจะเห็นได้ว่า API นี้มีวัตถุประสงค์เพื่อการดีบักและการพัฒนาเท่านั้น ฉันจะอัปเดตโพสต์นี้ทันทีที่ฉันมีเวลาอัปเดตโครงการ GitHub ด้านล่างด้วยรูปแบบใหม่ที่ใช้ตัวจับเวลาและเกือบดี
แก้ไข 1: ฉันได้เขียนบทความในบล็อกและสร้างที่เก็บ GitHub อย่างง่ายเพื่อให้ง่าย
คำตอบที่ได้รับการยอมรับและติดอันดับสูงสุดนั้นไม่ใช่วิธีที่ดีที่สุด การใช้งานคำตอบที่ได้รับคะแนนสูงสุดของ isApplicationBroughtToBackground () ไม่ได้จัดการกับสถานการณ์ที่กิจกรรมหลักของแอปพลิเคชันยอมให้กิจกรรมที่กำหนดไว้ในแอปพลิเคชันเดียวกัน แต่มีแพคเกจ Java ที่แตกต่างกัน ฉันมากับวิธีการทำเช่นนี้ที่จะทำงานในกรณีที่
เรียกสิ่งนี้ใน onPause () และมันจะบอกคุณว่าแอปพลิเคชันของคุณกำลังเข้าสู่พื้นหลังเพราะแอปพลิเคชันอื่นเริ่มต้นขึ้นหรือผู้ใช้กดปุ่มโฮม
public static boolean isApplicationBroughtToBackground(final Activity activity) {
ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1);
// Check the top Activity against the list of Activities contained in the Application's package.
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
try {
PackageInfo pi = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES);
for (ActivityInfo activityInfo : pi.activities) {
if(topActivity.getClassName().equals(activityInfo.name)) {
return false;
}
}
} catch( PackageManager.NameNotFoundException e) {
return false; // Never happens.
}
}
return true;
}
คำตอบที่ถูกต้องที่นี่
สร้างคลาสด้วยชื่อ MyApp เหมือนด้านล่าง:
public class MyApp implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
private Context context;
public void setContext(Context context)
{
this.context = context;
}
private boolean isInBackground = false;
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
isInBackground = true;
Log.d("status = ","we are out");
}
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
if(isInBackground){
isInBackground = false;
Log.d("status = ","we are in");
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void onLowMemory() {
}
}
จากนั้นทุกที่ที่คุณต้องการ (เปิดตัวกิจกรรมแรกที่ดีกว่าในแอป) เพิ่มรหัสด้านล่าง:
MyApp myApp = new MyApp();
registerComponentCallbacks(myApp);
getApplication().registerActivityLifecycleCallbacks(myApp);
ทำ! ตอนนี้เมื่อแอปอยู่ในพื้นหลังเราจะได้รับการบันทึก status : we are out
และเมื่อเราเข้าไปในแอพเราจะได้รับการบันทึกstatus : we are out
โซลูชันของฉันได้รับแรงบันดาลใจจากคำตอบของ @ d60402 และยังขึ้นอยู่กับหน้าต่างเวลา แต่ไม่ได้ใช้Timer
:
public abstract class BaseActivity extends ActionBarActivity {
protected boolean wasInBackground = false;
@Override
protected void onStart() {
super.onStart();
wasInBackground = getApp().isInBackground;
getApp().isInBackground = false;
getApp().lastForegroundTransition = System.currentTimeMillis();
}
@Override
protected void onStop() {
super.onStop();
if( 1500 < System.currentTimeMillis() - getApp().lastForegroundTransition )
getApp().isInBackground = true;
}
protected SingletonApplication getApp(){
return (SingletonApplication)getApplication();
}
}
โดยที่SingletonApplication
เป็นส่วนขยายของApplication
คลาส:
public class SingletonApplication extends Application {
public boolean isInBackground = false;
public long lastForegroundTransition = 0;
}
ฉันใช้สิ่งนี้กับ Google Analytics EasyTracker และใช้งานได้ อาจขยายออกไปเพื่อทำสิ่งที่คุณต้องการโดยใช้จำนวนเต็มอย่างง่าย
public class MainApplication extends Application {
int isAppBackgrounded = 0;
@Override
public void onCreate() {
super.onCreate();
appBackgroundedDetector();
}
private void appBackgroundedDetector() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStart(activity);
}
@Override
public void onActivityResumed(Activity activity) {
isAppBackgrounded++;
if (isAppBackgrounded > 0) {
// Do something here
}
}
@Override
public void onActivityPaused(Activity activity) {
isAppBackgrounded--;
}
@Override
public void onActivityStopped(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStop(activity);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
ฉันรู้ว่ามันสายไปนิดหน่อย แต่ฉันคิดว่าคำตอบทั้งหมดเหล่านี้มีปัญหาบางอย่างในขณะที่ฉันชอบที่ด้านล่างและมันใช้งานได้ดี
สร้างกิจกรรมวงจรชีวิตการเรียกกลับเช่นนี้:
class ActivityLifeCycle implements ActivityLifecycleCallbacks{
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
Activity lastActivity;
@Override
public void onActivityResumed(Activity activity) {
//if (null == lastActivity || (activity != null && activity == lastActivity)) //use this condition instead if you want to be informed also when app has been killed or started for the first time
if (activity != null && activity == lastActivity)
{
Toast.makeText(MyApp.this, "NOW!", Toast.LENGTH_LONG).show();
}
lastActivity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
และเพียงลงทะเบียนในชั้นแอปพลิเคชันของคุณดังนี้:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
สิ่งนี้ดูเหมือนจะเป็นหนึ่งในคำถามที่ซับซ้อนที่สุดใน Android ตั้งแต่ (ขณะที่เขียนนี้) Android ไม่มี iOS ที่เทียบเท่าapplicationDidEnterBackground()
หรือapplicationWillEnterForeground()
โทรกลับ ฉันใช้ห้องสมุด AppStateที่ถูกใส่กันโดย@jenzz
[AppState คือ] ห้องสมุด Android ที่ใช้งานง่ายและมีปฏิกิริยาตอบสนองตาม RxJava ที่ตรวจสอบการเปลี่ยนแปลงสถานะแอป มันแจ้งเตือนสมาชิกทุกครั้งที่แอพเข้าสู่พื้นหลังและกลับเข้าสู่เบื้องหน้า
ปรากฏว่านี่เป็นสิ่งที่ฉันต้องการโดยเฉพาะอย่างยิ่งเนื่องจากแอปของฉันมีกิจกรรมหลายอย่างดังนั้นเพียงแค่ตรวจสอบonStart()
หรือonStop()
กิจกรรมที่ไม่ได้ลดลง
ครั้งแรกที่ฉันเพิ่มการพึ่งพาเหล่านี้เพื่อ gradle:
dependencies {
compile 'com.jenzz.appstate:appstate:3.0.1'
compile 'com.jenzz.appstate:adapter-rxjava2:3.0.1'
}
จากนั้นเป็นเรื่องง่าย ๆ ในการเพิ่มบรรทัดเหล่านี้ไปยังตำแหน่งที่เหมาะสมในรหัสของคุณ:
//Note that this uses RxJava 2.x adapter. Check the referenced github site for other ways of using observable
Observable<AppState> appState = RxAppStateMonitor.monitor(myApplication);
//where myApplication is a subclass of android.app.Application
appState.subscribe(new Consumer<AppState>() {
@Override
public void accept(@io.reactivex.annotations.NonNull AppState appState) throws Exception {
switch (appState) {
case FOREGROUND:
Log.i("info","App entered foreground");
break;
case BACKGROUND:
Log.i("info","App entered background");
break;
}
}
});
คุณอาจต้องยกเลิกการเป็นสมาชิกเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ ข้อมูลเพิ่มเติมอีกครั้งใน หน้า GitHub
นี่เป็นเวอร์ชันที่แก้ไขแล้วของคำตอบของ @ d60402: https://stackoverflow.com/a/15573121/4747587
ทำทุกอย่างที่กล่าวถึงที่นั่น แต่แทนที่จะมีBase Activity
และทำสิ่งนั้นในฐานะผู้ปกครองสำหรับทุกกิจกรรมและการเอาชนะonResume()
และonPause
ทำสิ่งต่อไปนี้:
ในคลาสแอปพลิเคชันของคุณเพิ่มบรรทัด:
registerActivityLifecycleCallbacks (แอปพลิเคชัน ActivityLifecycleCallbacks โทรกลับ);
นี้callback
มีทั้งหมดวิธีวงจรกิจกรรมและตอนนี้คุณสามารถแทนที่และonActivityResumed()
onActivityPaused()
ดูที่ส่วนสำคัญนี้: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b
คุณสามารถทำสิ่งนี้ได้อย่างง่ายดายด้วยความช่วยเหลือActivityLifecycleCallbacks
และComponentCallbacks2
บางอย่างเช่นด้านล่าง
สร้างคลาสที่AppLifeCycleHandler
ใช้อินเตอร์เฟซดังกล่าวข้างต้น
package com.sample.app;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.os.Bundle;
/**
* Created by Naveen on 17/04/18
*/
public class AppLifeCycleHandler
implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
AppLifeCycleCallback appLifeCycleCallback;
boolean appInForeground;
public AppLifeCycleHandler(AppLifeCycleCallback appLifeCycleCallback) {
this.appLifeCycleCallback = appLifeCycleCallback;
}
@Override
public void onActivityResumed(Activity activity) {
if (!appInForeground) {
appInForeground = true;
appLifeCycleCallback.onAppForeground();
}
}
@Override
public void onTrimMemory(int i) {
if (i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
appInForeground = false;
appLifeCycleCallback.onAppBackground();
}
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void onLowMemory() {
}
interface AppLifeCycleCallback {
void onAppBackground();
void onAppForeground();
}
}
ในชั้นเรียนของคุณที่ขยายApplication
การใช้งานAppLifeCycleCallback
เพื่อรับการเรียกกลับเมื่อแอพสลับระหว่างพื้นหน้าและพื้นหลัง สิ่งที่ชอบด้านล่าง
public class BaseApplication extends Application implements AppLifeCycleHandler.AppLifeCycleCallback{
@Override
public void onCreate() {
super.onCreate();
AppLifeCycleHandler appLifeCycleHandler = new AppLifeCycleHandler(this);
registerActivityLifecycleCallbacks(appLifeCycleHandler);
registerComponentCallbacks(appLifeCycleHandler);
}
@Override
public void onAppBackground() {
Log.d("LifecycleEvent", "onAppBackground");
}
@Override
public void onAppForeground() {
Log.d("LifecycleEvent", "onAppForeground");
}
}
หวังว่านี่จะช่วยได้
แก้ไข เป็นอีกทางเลือกหนึ่งที่คุณสามารถใช้องค์ประกอบสถาปัตยกรรมวงจรชีวิตตระหนักถึง
เนื่องจากฉันไม่พบวิธีการใด ๆ ซึ่งจัดการการหมุนโดยไม่ตรวจสอบการประทับเวลาฉันจึงคิดว่าฉันจะแบ่งปันวิธีที่เราทำเช่นนี้ในแอปของเราด้วย นอกเหนือจากคำตอบนี้เท่านั้นhttps://stackoverflow.com/a/42679191/5119746ก็คือเราต้องคำนึงถึงเรื่องนี้ด้วย
class MyApplication : Application(), Application.ActivityLifecycleCallbacks {
// Members
private var mAppIsInBackground = false
private var mCurrentOrientation: Int? = null
private var mOrientationWasChanged = false
private var mResumed = 0
private var mPaused = 0
จากนั้นสำหรับการโทรกลับเรามีประวัติย่อก่อน:
// ActivityLifecycleCallbacks
override fun onActivityResumed(activity: Activity?) {
mResumed++
if (mAppIsInBackground) {
// !!! App came from background !!! Insert code
mAppIsInBackground = false
}
mOrientationWasChanged = false
}
และเมื่อกิจกรรมหยุด:
override fun onActivityStopped(activity: Activity?) {
if (mResumed == mPaused && !mOrientationWasChanged) {
// !!! App moved to background !!! Insert code
mAppIsInBackground = true
}
จากนั้นที่นี่มาเพิ่มเติม: การตรวจสอบการเปลี่ยนแปลงการวางแนว:
override fun onConfigurationChanged(newConfig: Configuration) {
if (newConfig.orientation != mCurrentOrientation) {
mCurrentOrientation = newConfig.orientation
mOrientationWasChanged = true
}
super.onConfigurationChanged(newConfig)
}
แค่นั้นแหละ. หวังว่าจะช่วยให้ใครบางคน :)
เราสามารถขยายโซลูชันนี้โดยใช้LiveData
:
class AppForegroundStateLiveData : LiveData<AppForegroundStateLiveData.State>() {
private var lifecycleListener: LifecycleObserver? = null
override fun onActive() {
super.onActive()
lifecycleListener = AppLifecycleListener().also {
ProcessLifecycleOwner.get().lifecycle.addObserver(it)
}
}
override fun onInactive() {
super.onInactive()
lifecycleListener?.let {
this.lifecycleListener = null
ProcessLifecycleOwner.get().lifecycle.removeObserver(it)
}
}
internal inner class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
value = State.FOREGROUND
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
value = State.BACKGROUND
}
}
enum class State {
FOREGROUND, BACKGROUND
}
}
ตอนนี้เราสามารถสมัครสมาชิก LiveData นี้และติดตามเหตุการณ์ที่จำเป็น ตัวอย่างเช่น:
appForegroundStateLiveData.observeForever { state ->
when(state) {
AppForegroundStateLiveData.State.FOREGROUND -> { /* app move to foreground */ }
AppForegroundStateLiveData.State.BACKGROUND -> { /* app move to background */ }
}
}
คำตอบเหล่านี้ดูเหมือนจะไม่ถูกต้อง วิธีการเหล่านี้จะถูกเรียกเมื่อกิจกรรมอื่นเริ่มต้นและสิ้นสุด สิ่งที่คุณสามารถทำได้คือรักษาธงทั่วโลก (ใช่, globals ไม่ดี :) และตั้งค่านี้เป็นจริงทุกครั้งที่คุณเริ่มกิจกรรมใหม่ ตั้งค่าเป็นเท็จใน onCreate ของแต่ละกิจกรรม จากนั้นใน onPause คุณตรวจสอบการตั้งค่าสถานะนี้ หากเป็นเท็จแอปของคุณกำลังเข้าสู่พื้นหลังหรือถูกฆ่า