Cách phát hiện khi ứng dụng Android chạy nền và quay lại nền trước


382

Tôi đang cố gắng viết một ứng dụng làm một cái gì đó cụ thể khi nó được đưa trở lại nền trước sau một khoảng thời gian. Có cách nào để phát hiện khi một ứng dụng được gửi đến nền hoặc được đưa lên nền trước không?


2
Có thể thêm trường hợp sử dụng vào câu hỏi vì nó dường như không rõ ràng, vì vậy nó không được giải quyết trong các câu trả lời được đưa ra. Ứng dụng có thể khởi động một ứng dụng khác (ví dụ như Thư viện), ứng dụng này vẫn sẽ nằm trong cùng ngăn xếp và xuất hiện dưới dạng một trong các màn hình của ứng dụng, sau đó nhấn nút Home. Không có phương pháp nào dựa vào vòng đời ứng dụng (hoặc thậm chí quản lý bộ nhớ) có thể phát hiện ra điều này. Chúng sẽ kích hoạt trạng thái nền ngay khi Hoạt động bên ngoài xuất hiện, không phải khi bạn nhấn Home.
Dennis K

Đây là câu trả lời bạn đang tìm kiếm: stackoverflow.com/a/42679191/2352699
Fred Porciúncula

1
Xem Giải pháp của Google: stackoverflow.com/questions/3667022/
Kẻ

Câu trả lời:


98

Các phương thức onPause()onResume()được gọi khi ứng dụng được đưa vào nền và vào nền trước một lần nữa. Tuy nhiên, chúng cũng được gọi khi ứng dụng được khởi động lần đầu tiên và trước khi nó bị giết. Bạn có thể đọc thêm trong Hoạt động .

Không có bất kỳ cách tiếp cận trực tiếp nào để có được trạng thái ứng dụng trong khi ở chế độ nền hoặc tiền cảnh, nhưng ngay cả tôi cũng đã phải đối mặt với vấn đề này và tìm ra giải pháp với onWindowFocusChangedonStop.

Để biết thêm chi tiết, hãy kiểm tra tại đây Android: Giải pháp phát hiện khi ứng dụng Android chạy nền và quay lại nền trước mà không có getRastyT task hoặc getRastyAppProcesses .


173
Tuy nhiên, cách tiếp cận này gây ra dương tính giả như những cách khác đã chỉ ra, bởi vì các phương thức này cũng được gọi khi chuyển đổi giữa các hoạt động trong cùng một ứng dụng.
John Lehmann

9
Tệ hơn thế. Tôi đã thử nó và đôi khi onResume được gọi trong khi điện thoại bị khóa. Nếu bạn thấy định nghĩa của onResume trong tài liệu, bạn sẽ thấy: Hãy nhớ rằng onResume không phải là chỉ báo tốt nhất cho thấy hoạt động của bạn được hiển thị cho người dùng; một cửa sổ hệ thống như keyguard có thể ở phía trước. Sử dụng onWindowF FocusChanged (boolean) để biết chắc chắn rằng hoạt động của bạn được hiển thị cho người dùng (ví dụ: để tiếp tục trò chơi). developer.android.com/reference/android/app/NH
J-Rou

2
Giải pháp được đăng trong liên kết không sử dụng onResume / onPause, thay vào đó là sự kết hợp của onBackPression, onStop, onStart và onWindowsF FocusChanged. Nó đã làm việc cho tôi và tôi có một hệ thống phân cấp UI khá phức tạp (với các ngăn kéo, các khung nhìn động, v.v.)
Martin Marconcini 18/07/13

18
OnPause và onResume là Hoạt động cụ thể. Không áp dụng. Khi một Ứng dụng được đặt trên nền và sau đó được tiếp tục, nó sẽ tiếp tục Hoạt động cụ thể mà nó đã có trước khi đi vào nền. Điều này có nghĩa là bạn sẽ cần phải thực hiện bất cứ điều gì bạn muốn thực hiện khi tiếp tục từ nền trong tất cả Hoạt động của Ứng dụng. Tôi tin rằng câu hỏi ban đầu là tìm kiếm một cái gì đó giống như "onResume" cho Ứng dụng chứ không phải Hoạt động.
SysHex

4
Tôi không thể tin rằng một API thích hợp không được cung cấp cho nhu cầu chung như vậy. Ban đầu tôi nghĩ onUserLeaveHint () sẽ cắt nó, nhưng bạn không thể biết người dùng có rời khỏi ứng dụng hay không
atsakiridis 27/8/2015

197

2018: Android hỗ trợ điều này tự nhiên thông qua các thành phần vòng đời.

CẬP NHẬT tháng 3 năm 2018 : Hiện tại đã có một giải pháp tốt hơn. Xem ProcessLifecyclOwner . Bạn sẽ cần sử dụng các thành phần kiến ​​trúc mới 1.1.0 (mới nhất tại thời điểm này) nhưng nó được thiết kế đặc biệt để làm điều này.

Có một mẫu đơn giản được cung cấp trong câu trả lời này nhưng tôi đã viết một ứng dụng mẫu và một bài đăng trên blog về nó.

Kể từ khi tôi viết lại vào năm 2014, các giải pháp khác nhau đã nảy sinh. Một số đã làm việc, một số được cho là đang làm việc , nhưng có những sai sót (bao gồm cả của tôi!) Và chúng tôi, như một cộng đồng (Android) đã học cách sống với hậu quả và viết cách giải quyết cho các trường hợp đặc biệt.

Đừng bao giờ cho rằng một đoạn mã là giải pháp bạn đang tìm kiếm, đó là trường hợp không thể xảy ra; tốt hơn nữa, hãy cố gắng hiểu những gì nó làm và tại sao nó làm điều đó.

Các MemoryBosslớp đã bao giờ thực sự được sử dụng bởi tôi như viết ở đây, nó chỉ là một đoạn mã giả đã xảy ra để làm việc.

Trừ khi có lý do chính đáng để bạn không sử dụng các thành phần kiến ​​trúc mới (và có một số, đặc biệt là nếu bạn nhắm mục tiêu apis siêu cũ), thì hãy tiếp tục và sử dụng chúng. Họ là xa hoàn hảo, nhưng không phải là ComponentCallbacks2.

CẬP NHẬT / GHI CHÚ (Tháng 11 năm 2015) : Mọi người đã đưa ra hai nhận xét, đầu tiên là >=nên sử dụng thay ==vì vì tài liệu nói rằng bạn không nên kiểm tra các giá trị chính xác . Điều này tốt cho hầu hết các trường hợp, nhưng hãy nhớ rằng nếu bạn chỉ quan tâm đến việc làm gì đó khi ứng dụng chạy nền, bạn sẽ phải sử dụng == ứng dụng của bạn với màn hình mật khẩu khi vào nền (như 1Password nếu bạn đã quen với nó), bạn có thể vô tình khóa ứng dụng của mình nếu bạn thiếu bộ nhớ và đột nhiên kiểm tra cũng kết hợp nó với một giải pháp khác (như cuộc gọi lại Vòng đời hoạt động) hoặc bạn có thể không có được hiệu quả mong muốn của bạn. Ví dụ (và điều này đã xảy ra với tôi) là nếu bạn muốn khóa>= TRIM_MEMORY , vì Android sẽ kích hoạt LOW MEMORYcuộc gọi và đó là cao hơn của bạn Vì vậy, hãy cẩn thận làm thế nào / những gì bạn kiểm tra.

Ngoài ra, một số người đã hỏi về cách phát hiện khi bạn quay lại.

Cách đơn giản nhất tôi có thể nghĩ đến được giải thích bên dưới, nhưng vì một số người không quen với nó, tôi đang thêm một số mã giả ngay tại đây. Giả sử bạn có YourApplicationvà các MemoryBosslớp, trong bạn class BaseActivity extends Activity(bạn sẽ cần tạo một cái nếu bạn không có).

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

Tôi khuyên bạn nên khởi động vì Hộp thoại có thể tạm dừng một hoạt động vì vậy tôi cá rằng bạn không muốn ứng dụng của mình nghĩ rằng "nó đã chạy đến nền" nếu tất cả những gì bạn đã làm là hiển thị hộp thoại toàn màn hình, nhưng số dặm của bạn có thể thay đổi.

Và đó là tất cả. Mã kiểm tra vào nếu khối sẽ chỉ được thực hiện một lần , ngay cả khi bạn đi đến các hoạt động khác, cái mới (mà cũng extends BaseActivity) sẽ báo cáo wasInBackgroundfalsevì vậy nó sẽ không thực thi mã, cho đến khi onMemoryTrimmedđược gọi và lá cờ được thiết lập là true lại .

Mong rằng sẽ giúp.

CẬP NHẬT / LƯU Ý (Tháng 4 năm 2015) : Trước khi bạn đi tất cả Sao chép và Dán vào mã này, lưu ý rằng tôi đã tìm thấy một vài trường hợp có thể không đáng tin cậy 100% và phải được kết hợp với các phương pháp khác để đạt được kết quả tốt nhất. Đáng chú ý, có hai trường hợp đã biếtonTrimMemorycuộc gọi lại không được đảm bảo sẽ được thực hiện:

  1. Nếu điện thoại của bạn khóa màn hình trong khi ứng dụng của bạn hiển thị (giả sử thiết bị của bạn khóa sau nn phút), cuộc gọi lại này không được gọi (hoặc không phải luôn luôn) vì màn hình khóa chỉ ở trên cùng, nhưng ứng dụng của bạn vẫn "chạy" mặc dù được bảo hiểm.

  2. Nếu thiết bị của bạn tương đối thấp về bộ nhớ (và bị căng thẳng bộ nhớ), Hệ điều hành dường như bỏ qua cuộc gọi này và đi thẳng đến các mức quan trọng hơn.

Bây giờ, tùy thuộc vào mức độ quan trọng của bạn để biết khi nào ứng dụng của bạn chạy đến nền, bạn có thể hoặc không cần phải mở rộng giải pháp này cùng với việc theo dõi vòng đời hoạt động và không có gì.

Chỉ cần ghi nhớ những điều trên và có một nhóm QA tốt;)

KẾT THÚC CẬP NHẬT

Nó có thể bị trễ nhưng có một phương pháp đáng tin cậy trong Ice Cream Sandwich (API 14) trở lên .

Hóa ra khi ứng dụng của bạn không còn giao diện người dùng hiển thị nữa, một cuộc gọi lại được kích hoạt. Cuộc gọi lại, mà bạn có thể thực hiện trong một lớp tùy chỉnh, được gọi là ElementCallbacks2 (có, với hai). Cuộc gọi lại này chỉ khả dụng trong API Cấp 14 (Ice Cream Sandwich) trở lên.

Về cơ bản, bạn nhận được một cuộc gọi đến phương thức:

public abstract void onTrimMemory (int level)

Cấp độ là 20 hoặc cụ thể hơn

public static final int TRIM_MEMORY_UI_HIDDEN

Tôi đã thử nghiệm điều này và nó luôn hoạt động, bởi vì cấp 20 chỉ là một "gợi ý" mà bạn có thể muốn phát hành một số tài nguyên do ứng dụng của bạn không còn hiển thị nữa.

Để trích dẫn các tài liệu chính thức:

Cấp độ cho onTrimMemory (int): quá trình đã hiển thị giao diện người dùng và không còn làm như vậy nữa. Phân bổ lớn với UI nên được phát hành tại thời điểm này để cho phép bộ nhớ được quản lý tốt hơn.

Tất nhiên, bạn nên thực hiện điều này để thực sự làm theo những gì nó nói (thanh lọc bộ nhớ chưa được sử dụng trong một số thời điểm nhất định, xóa một số bộ sưu tập đã không được sử dụng, v.v. Khả năng là vô tận (xem các tài liệu chính thức để biết thêm mức độ quan trọng ).

Nhưng, điều thú vị là HĐH đang nói với bạn: HEY, ứng dụng của bạn đã chạy nền!

Đó là chính xác những gì bạn muốn biết ở nơi đầu tiên.

Làm thế nào để bạn xác định khi bạn quay trở lại?

Thật dễ dàng, tôi chắc chắn rằng bạn có "BaseActivity" để bạn có thể sử dụng onResume () của mình để đánh dấu sự thật rằng bạn đã trở lại. Bởi vì lần duy nhất bạn sẽ nói rằng bạn không quay lại là khi bạn thực sự nhận được một cuộc gọi đến onTrimMemoryphương thức trên .

Nó hoạt động. Bạn không nhận được dương tính giả. Nếu một hoạt động được nối lại, bạn sẽ quay lại, 100% số lần. Nếu người dùng quay lại, bạn sẽ nhận được một onTrimMemory()cuộc gọi khác .

Bạn cần mô tả các Hoạt động của mình (hoặc tốt hơn là một lớp tùy chỉnh).

Cách dễ nhất để đảm bảo rằng bạn luôn nhận được điều này là tạo một lớp đơn giản như thế này:

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

Để sử dụng điều này, trong triển khai Ứng dụng của bạn ( bạn có một, ĐÚNG? ), Hãy làm một cái gì đó như:

MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
   super.onCreate();
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      mMemoryBoss = new MemoryBoss();
      registerComponentCallbacks(mMemoryBoss);
   } 
}

Nếu bạn tạo một Interfacebạn có thể thêm một elsecái đó ifvà thực hiện ComponentCallbacks(không có 2) được sử dụng trong bất cứ điều gì bên dưới API 14. Cuộc gọi lại đó chỉ có onLowMemory()phương thức và không được gọi khi bạn đi tới nền , nhưng bạn nên sử dụng nó để cắt bộ nhớ .

Bây giờ khởi chạy ứng dụng của bạn và nhấn home. onTrimMemory(final int level)Phương pháp của bạn nên được gọi (gợi ý: thêm ghi nhật ký).

Bước cuối cùng là hủy đăng ký khỏi cuộc gọi lại. Có lẽ vị trí tốt nhất là onTerminate()phương thức Ứng dụng của bạn, nhưng , phương thức đó không được gọi trên thiết bị thực:

/**
 * 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.
 */

Vì vậy, trừ khi bạn thực sự có một tình huống mà bạn không còn muốn được đăng ký, bạn có thể an toàn bỏ qua nó, vì dù sao quá trình của bạn đang chết ở cấp độ hệ điều hành.

Nếu bạn quyết định hủy đăng ký tại một số điểm (ví dụ: nếu bạn cung cấp cơ chế tắt máy cho ứng dụng của bạn để dọn dẹp và chết), bạn có thể làm:

unregisterComponentCallbacks(mMemoryBoss);

Và đó là nó.


Khi kiểm tra điều này từ một dịch vụ, dường như chỉ kích hoạt khi nhấn nút home. Nhấn nút quay lại không kích hoạt tính năng này trên KitKat.
Tìm hiểu OpenGL ES

Dịch vụ này không có UI nên có thể liên quan đến điều đó. Thực hiện kiểm tra trong hoạt động cơ sở của bạn, không phải trên một dịch vụ. Bạn muốn biết khi nào UI của bạn bị ẩn (và có thể nói với dịch vụ, vì vậy nó đi trước)
Martin Marconcini

1
Nó không hoạt động khi bạn tắt điện thoại. Nó không được kích hoạt.
Juangcg

2
Sử dụng ElementCallbacks2.onTrimMemory () (kết hợp với ActivityLifecyclCallbacks) là giải pháp đáng tin cậy duy nhất tôi tìm thấy cho đến nay, cảm ơn Martin! Đối với những người quan tâm, xem câu trả lời được cung cấp của tôi.
rickul

3
Tôi đã sử dụng phương pháp này từ một năm trước và nó luôn đáng tin cậy đối với tôi. Thật tốt khi biết người khác cũng sử dụng nó. Tôi chỉ sử dụng level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDENđể tránh sự cố trong bản cập nhật của bạn, điểm 2. Về điểm 1, điều đó không liên quan đến tôi, vì ứng dụng không thực sự đi vào nền, vì vậy đó là cách nó hoạt động.
sorianiv

175

Đây là cách tôi đã giải quyết vấn đề này. Nó hoạt động dựa trên tiền đề rằng việc sử dụng tham chiếu thời gian giữa các lần chuyển đổi hoạt động rất có thể sẽ cung cấp bằng chứng đầy đủ rằng ứng dụng đã được "làm nền" hay chưa.

Đầu tiên, tôi đã sử dụng một phiên bản android.app.Application (hãy gọi nó là MyApplication) có Timer, TimerTask, một hằng số để biểu thị số mili giây tối đa mà quá trình chuyển đổi từ hoạt động này sang hoạt động khác có thể diễn ra một cách hợp lý (tôi đã đi với giá trị là 2 giây) và boolean để cho biết ứng dụng có "ở chế độ nền" hay không:

public class MyApplication extends Application {

    private Timer mActivityTransitionTimer;
    private TimerTask mActivityTransitionTimerTask;
    public boolean wasInBackground;
    private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
    ...

Ứng dụng cũng cung cấp hai phương thức để bắt đầu và dừng bộ hẹn giờ / tác vụ:

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

Phần cuối cùng của giải pháp này là thêm một cuộc gọi đến từng phương thức này từ các sự kiện onResume () và onPause () của tất cả các hoạt động hoặc, tốt nhất là, trong một Hoạt động cơ bản mà tất cả các Hoạt động cụ thể của bạn kế thừa:

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

Vì vậy, trong trường hợp khi người dùng chỉ cần điều hướng giữa các hoạt động của ứng dụng của bạn, thì onPause () của hoạt động khởi hành sẽ khởi động bộ hẹn giờ, nhưng gần như ngay lập tức hoạt động mới được nhập sẽ hủy bộ hẹn giờ trước khi nó có thể đạt đến thời gian chuyển tiếp tối đa. Và như vậy wasInBackground sẽ là sai .

Mặt khác, khi một Hoạt động đến tiền cảnh từ Trình khởi chạy, thiết bị thức dậy, kết thúc cuộc gọi điện thoại, v.v., nhiều khả năng tác vụ hẹn giờ được thực hiện trước sự kiện này và do đó, InBackground được đặt thành đúng .


4
Xin chào d60402, câu trả lời của bạn thực sự hữu ích .. cảm ơn bạn rất nhiều vì câu trả lời này ... thông báo nhỏ .. MyApplication nên đề cập trong thẻ ứng dụng tệp Manifest như android: name = "MyApplication", nếu không ứng dụng gặp sự cố ... chỉ để giúp đỡ ai đó thích tôi
Praveenb

2
đánh dấu của các lập trình viên tuyệt vời, giải pháp đơn giản cho một trong những vấn đề phức tạp nhất mà tôi từng gặp phải.
Aashish Bhatnagar

2
Giải pháp tuyệt vời! Cảm ơn. Nếu bất cứ ai gặp lỗi "ClassCastException" thì bạn có thể đã bỏ lỡ việc thêm nó vào thẻ ứng dụng bên trong Manifest.xml <application android: name = "your.package.MyApplication"
Wahib Ul Haq

27
Đây là một thực hiện tốt đẹp và đơn giản. Tuy nhiên tôi tin rằng điều này nên được thực hiện trong onStart / onStop chứ không phải onPause / onResume. OnPause sẽ được gọi ngay cả khi tôi bắt đầu một hộp thoại bao gồm một phần hoạt động. Và việc đóng hộp thoại sẽ thực sự gọi onResume làm cho nó xuất hiện như thể ứng dụng vừa mới xuất hiện
Shubhayu

7
Tôi hy vọng sẽ sử dụng một biến thể của giải pháp này. Điểm về các cuộc đối thoại được xác định ở trên là một vấn đề đối với tôi, vì vậy tôi đã thử đề xuất của @ Shubhayu (onStart / onStop). Tuy nhiên, điều này không giúp ích gì vì khi đi A-> B, onStart () của Activity B được gọi trước onStop () của Activity A.
Trevor

150

Chỉnh sửa: các thành phần kiến ​​trúc mới mang lại một điều gì đó đầy hứa hẹn: ProcessLifecycleOwner , xem câu trả lời của @ vokilam


Giải pháp thực tế theo cuộc thảo luận Google I / O :

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

}

Đúng. Tôi biết thật khó để tin giải pháp đơn giản này hoạt động vì chúng ta có quá nhiều giải pháp kỳ lạ ở đây.

Nhưng có hy vọng.


3
Điều này hoạt động hoàn hảo! Tôi đã thử rất nhiều giải pháp kỳ lạ có quá nhiều sai sót ... rất cảm ơn! Tôi đã tìm kiếm điều này trong một thời gian.
Eggakin Baconwalker

7
Nó hoạt động cho nhiều hoạt động, nhưng đối với một - onrotate sẽ cho biết tất cả các hoạt động đã biến mất hoặc ở chế độ nền
cá chết

2
@Shyri bạn đúng, nhưng đó là một phần của giải pháp này nên cần phải lo lắng. Nếu firebase dựa vào điều này, tôi nghĩ ứng dụng tầm thường của tôi cũng có thể :) Câu trả lời tuyệt vời BTW.
ElliotM

3
@deadfish Kiểm tra liên kết đến I / O được cung cấp ở đầu câu trả lời. Bạn có thể kiểm tra khoảng cách thời gian giữa dừng hoạt động và bắt đầu xác định xem bạn có thực sự đi đến nền hay không. Đây là một giải pháp tuyệt vời, thực sự.
Alex Berdnikov

2
Có một giải pháp Java? Đây là kotlin.
Giacomo Bartoli

116

ProcessLifecycleOwner dường như cũng là một giải pháp đầy hứa hẹn

ProcessLifecyclOwner sẽ gửi ON_START, ON_RESUMEcác sự kiện, khi một hoạt động đầu tiên di chuyển qua các sự kiện này. ON_PAUSE,, ON_STOPcác sự kiện sẽ được gửi đi với độ trễ sau khi một hoạt động cuối cùng được chuyển qua chúng. Độ trễ này đủ dài để đảm bảo rằng ProcessLifecycleOwnersẽ không gửi bất kỳ sự kiện nào nếu các hoạt động bị hủy và được tạo lại do thay đổi cấu hình.

Việc thực hiện có thể đơn giản như

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())

Theo mã nguồn, giá trị độ trễ hiện tại là 700ms .

Cũng sử dụng tính năng này yêu cầu dependencies:

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"

10
Lưu ý rằng bạn cần thêm các phụ thuộc vòng đời implementation "android.arch.lifecycle:extensions:1.0.0"annotationProcessor "android.arch.lifecycle:compiler:1.0.0"từ kho lưu trữ của Google (tức là google())
Sir Codalot

1
Điều này làm việc rất tốt cho tôi, cảm ơn bạn. Tôi đã phải sử dụng api 'android.arch.lifecycle: extend: 1.1.0' thay vì triển khai do lỗi cho biết phụ thuộc Android có phiên bản khác cho đường dẫn biên dịch và thời gian chạy.
FSUWX2011

Đây là một giải pháp tuyệt vời vì nó hoạt động trong các mô-đun mà không cần tham chiếu Activity!
Tối đa

Điều này không hoạt động khi ứng dụng bị sập. Có giải pháp nào để nhận sự kiện ứng dụng bị sập cũng thông qua giải pháp này không
tejraj

Giải pháp tuyệt vời. Cứu ngày của tôi.
Nắng

69

Dựa trên câu trả lời của Martín Marconcinis (cảm ơn!) Cuối cùng tôi đã tìm thấy một giải pháp đáng tin cậy (và rất đơn giản).

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

Sau đó thêm phần này vào onCreate () của lớp Ứng dụng của bạn

public class MyApp extends android.app.Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
        registerActivityLifecycleCallbacks(handler);
        registerComponentCallbacks(handler);

    }

}

Bạn có thể chỉ ra cách bạn sử dụng cái này trong một ứng dụng không, tôi có gọi nó từ lớp App hay ở nơi nào khác không?
JPM

điều này là hoàn hảo cảm ơn bạn !! hoạt động tốt trong thử nghiệm cho đến nay
aherrick

Ví dụ này nếu không đầy đủ. RegisterActivityLifecyclCallbacks là gì?
Noman

đó là một phương thức trong lớp android.app.Ứng dụng
rickul

1
hoàn thành tốt +1 để đi đầu, vì nó hoàn hảo, đừng tìm những câu trả lời khác, điều này dựa trên câu trả lời @reno nhưng với ví dụ thực tế
Stoycho Andreev

63

Chúng tôi sử dụng phương pháp này. Nó trông có vẻ quá đơn giản để làm việc, nhưng nó đã được thử nghiệm tốt trong ứng dụng của chúng tôi và trên thực tế hoạt động rất tốt trong mọi trường hợp, bao gồm cả việc đi đến màn hình chính bằng nút "home", bằng nút "return" hoặc sau khi khóa màn hình. Hãy thử một lần.

Ý tưởng là, khi ở phía trước, Android luôn bắt đầu hoạt động mới ngay trước khi dừng hoạt động trước đó. Điều đó không được đảm bảo, nhưng đó là cách nó hoạt động. BTW, Flurry dường như sử dụng cùng một logic (chỉ là phỏng đoán, tôi đã không kiểm tra điều đó, nhưng nó móc vào cùng một sự kiện).

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

}

Chỉnh sửa: theo nhận xét, chúng tôi cũng đã chuyển sang onStart () trong các phiên bản sau của mã. Ngoài ra, tôi đang thêm các cuộc gọi siêu bị thiếu trong bài viết ban đầu của mình, vì đây là một khái niệm nhiều hơn là một mã làm việc.


2
Đây là câu trả lời đáng tin cậy nhất, mặc dù tôi sử dụng onStart thay vì onResume.
Greg Enni

Bạn nên thêm các cuộc gọi đến super.onResume () và super.onStop () trong các phương thức overriden. Nếu không, một android.app.SuperNotCalledException sẽ bị ném.
Jan Laussmann

1
đối với tôi nó không hoạt động ... hoặc ít nhất là nó kích hoạt sự kiện khi bạn cũng xoay thiết bị (đó là một imho dương tính giả).
Noya

Giải pháp rất đơn giản và hiệu quả! Nhưng tôi không chắc nó hoạt động với các hoạt động minh bạch một phần cho phép một số phần của hoạt động trước đó hiển thị. Từ các tài liệu , onStop is called when the activity is no longer visible to the user.
Nicolas Buquet

3
Điều gì xảy ra nếu người dùng thay đổi định hướng cho hoạt động đầu tiên? Nó sẽ báo cáo rằng ứng dụng đã chạy đến nền không đúng. Làm thế nào để bạn xử lý tình huống này?
Nimrod Dayan

54

Nếu ứng dụng của bạn bao gồm nhiều hoạt động và / hoặc các hoạt động được xếp chồng lên nhau như một tiện ích thanh tab, thì ghi đè onPause () và onResume () sẽ không hoạt động. Tức là khi bắt đầu một hoạt động mới, các hoạt động hiện tại sẽ bị tạm dừng trước khi hoạt động mới được tạo. Điều tương tự cũng áp dụng khi hoàn thiện (sử dụng nút "quay lại") một hoạt động.

Tôi đã tìm thấy hai phương pháp có vẻ hoạt động như mong muốn.

Cách thứ nhất yêu cầu quyền GET_TASKS và bao gồm một phương thức đơn giản để kiểm tra xem hoạt động chạy hàng đầu trên thiết bị có thuộc về ứng dụng hay không, bằng cách so sánh tên gói:

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

Phương pháp này đã được tìm thấy trong khung Droid-Fu (bây giờ được gọi là Đánh lửa).

Phương pháp thứ hai mà tôi đã tự thực hiện không yêu cầu sự cho phép GET_TASKS, rất tốt. Thay vào đó nó phức tạp hơn một chút để thực hiện.

Trong lớp MainApplication, bạn có một biến theo dõi số lượng hoạt động đang chạy trong ứng dụng của bạn. Trong onResume () cho mỗi hoạt động bạn tăng biến và trong onPause () bạn giảm nó.

Khi số lượng hoạt động đang chạy về 0, ứng dụng sẽ được đặt vào nền NẾU các điều kiện sau là đúng:

  • Hoạt động đang bị tạm dừng chưa kết thúc (nút "quay lại" đã được sử dụng). Điều này có thể được thực hiện bằng cách sử dụng phương thức Activity.isFinishing ()
  • Một hoạt động mới (cùng tên gói) sẽ không được bắt đầu. Bạn có thể ghi đè phương thức startActivity () để đặt một biến chỉ ra điều này và sau đó đặt lại nó trong onPostResume (), đây là phương thức cuối cùng được chạy khi một hoạt động được tạo / tiếp tục.

Khi bạn có thể phát hiện ra rằng ứng dụng đã nằm dưới nền, nó cũng dễ dàng phát hiện khi nó được đưa trở lại nền trước.


18
Google có thể sẽ từ chối một ứng dụng sử dụng ActivityManager.getRastyT task (). Các tài liệu nói rằng đó chỉ là mục đích của kẻ thù. developer.android.com/reference/android/app/NH
Sky Kelsey


1
Tôi thấy tôi phải sử dụng kết hợp các phương pháp này. onUserLeaveHint () đã được gọi khi khởi chạy một hoạt động trong 14. `@Override void công khai onUserLeaveHint () {inBackground = isApplicationBvedToBackground (); } `
thuyền niêm yết

7
Người dùng sẽ không quá hài lòng về việc sử dụng một quyền hạn mạnh mẽ của ERIC.GET_TASKS.
MSapes

6
getRastyT Nhiệm vụ không được chấp nhận trong API cấp 21.
Noya

33

Tạo một lớp mở rộng Application. Sau đó, trong đó chúng ta có thể sử dụng phương thức ghi đè của nó , onTrimMemory().

Để phát hiện xem ứng dụng có chạy nền hay không, chúng tôi sẽ sử dụng:

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

1
Đối với FragmentActivitybạn cũng có thể muốn thêm level == ComponentCallbacks2.TRIM_MEMORY_COMPLETEquá.
Srujan Simha

2
Cảm ơn rất nhiều vì đã chỉ ra phương pháp này, tôi cần hiển thị Hộp thoại Ghim bất cứ khi nào người dùng tiếp tục hoạt động cho nền, sử dụng phương pháp này để viết giá trị trước và kiểm tra giá trị này trên baseActivity.
Sam

18

Cân nhắc sử dụng onUserLeaveHint. Điều này sẽ chỉ được gọi khi ứng dụng của bạn đi vào nền. onPause sẽ có các trường hợp góc để xử lý, vì nó có thể được gọi vì các lý do khác; ví dụ: nếu người dùng mở một hoạt động khác trong ứng dụng của bạn, chẳng hạn như trang cài đặt của bạn, phương thức onPause của hoạt động chính của bạn sẽ được gọi ngay cả khi họ vẫn ở trong ứng dụng của bạn; theo dõi những gì đang diễn ra sẽ dẫn đến lỗi khi bạn chỉ có thể sử dụng cuộc gọi lại onUserLeaveHint thực hiện những gì bạn đang yêu cầu.

Khi trên UserLeaveHint được gọi, bạn có thể đặt cờ inBackground boolean thành true. Khi onResume được gọi, chỉ giả sử bạn quay lại nền trước nếu cờ inBackground được đặt. Điều này là do onResume cũng sẽ được gọi trong hoạt động chính của bạn nếu người dùng chỉ ở trong menu cài đặt của bạn và không bao giờ rời khỏi ứng dụng.

Hãy nhớ rằng nếu người dùng nhấn nút home trong khi ở màn hình cài đặt của bạn, onUserLeaveHint sẽ được gọi trong hoạt động cài đặt của bạn và khi họ quay lại onResume sẽ được gọi trong hoạt động cài đặt của bạn. Nếu bạn chỉ có mã phát hiện này trong hoạt động chính của mình, bạn sẽ bỏ lỡ trường hợp sử dụng này. Để có mã này trong tất cả các hoạt động của bạn mà không cần sao chép mã, hãy có một lớp hoạt động trừu tượng mở rộng Hoạt động và đặt mã chung của bạn vào đó. Sau đó, mỗi hoạt động bạn có thể mở rộng hoạt động trừu tượng này.

Ví dụ:

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 {
    ...
}

19
onUserLeaveHint cũng được gọi khi điều hướng đến một hoạt động khác
Jonas Stawski

3
onUserLeaveHint không được gọi khi v.v. developer.android.com/reference/android/content/
Mạnh

1
Ngoài ra, onResume không hoạt động tốt. Tôi đã thử nó và đôi khi onResume được gọi trong khi điện thoại bị khóa. Nếu bạn thấy định nghĩa của onResume trong tài liệu, bạn sẽ thấy: Hãy nhớ rằng onResume không phải là chỉ báo tốt nhất cho thấy hoạt động của bạn được hiển thị cho người dùng; một cửa sổ hệ thống như keyguard có thể ở phía trước. Sử dụng onWindowF FocusChanged (boolean) để biết chắc chắn rằng hoạt động của bạn được hiển thị cho người dùng (ví dụ: để tiếp tục trò chơi). developer.android.com/reference/android/app/NH
J-Rou

giải pháp này không giúp quyết định tiền cảnh / hậu cảnh nếu có nhiều hoạt động. Hãy tham khảo stackoverflow.com/questions/3667022/iêu
Raj Trivingi

14

ActivityLifecyclCallbacks có thể được quan tâm, nhưng nó không được ghi chép tốt.

Mặc dù, nếu bạn gọi registerActivityLifecyclCallbacks (), bạn sẽ có thể nhận được các cuộc gọi lại khi các Hoạt động được tạo, hủy, v.v. Bạn có thể gọi getComponentName () cho Hoạt động.


11
Kể từ khi api cấp 14 = \
imort

Có vẻ như cái này là sạch sẽ và làm việc cho tôi. Cảm ơn
duanbo1983

Điều này khác với câu trả lời được chấp nhận như thế nào, cả hai đều dựa vào cùng một vòng đời hoạt động phải không?
Saitama

13

các android.arch.lifecycle gói cung cấp các lớp và các giao diện cho phép bạn xây dựng các thành phần vòng đời-aware

Ứng dụng của bạn sẽ triển khai giao diện LifecyclObserver:

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

Để làm điều đó, bạn cần thêm phụ thuộc này vào tệp build.gradle của mình:

dependencies {
    implementation "android.arch.lifecycle:extensions:1.1.1"
}

Theo khuyến nghị của Google, bạn nên giảm thiểu mã được thực thi trong các phương thức hoạt động của vòng đời:

Một mô hình phổ biến là thực hiện các hành động của các thành phần phụ thuộc trong các phương thức vòng đời của các hoạt động và các đoạn. Tuy nhiên, mô hình này dẫn đến một tổ chức kém về mã và phổ biến các lỗi. Bằng cách sử dụng các thành phần nhận biết vòng đời, bạn có thể di chuyển mã của các thành phần phụ thuộc ra khỏi các phương thức vòng đời và vào chính các thành phần đó.

Bạn có thể đọc thêm tại đây: https://developer.android.com/topic/lologists/arch architecture / lifecycle


và thêm phần này vào bảng kê khai như: <application android: name = ". AnotherApp">
Dan Alboteanu

9

Trong Ứng dụng của bạn, hãy thêm cuộc gọi lại và kiểm tra hoạt động gốc theo cách như sau:

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

Tôi sẽ xem xét sử dụng cách thức thực hiện này. Quá trình chuyển đổi từ hoạt động này sang hoạt động khác chỉ mất vài mili giây. Dựa vào thời gian khi hoạt động cuối cùng biến mất có thể được xem xét để đăng nhập lại người dùng theo một chiến lược cụ thể.
Drindt

6

Tôi đã tạo một dự án trên ứng dụng Github -foreground-background-lắng nghe

Tạo BaseActivity cho tất cả Hoạt động trong ứng dụng của bạn.

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

Bây giờ, hãy sử dụng BaseActivity này như một siêu lớp của tất cả Hoạt động của bạn như MainActivity mở rộng BaseActivity và onAppStart sẽ được gọi khi bạn khởi động ứng dụng của mình và onAppPause () sẽ được gọi khi ứng dụng chạy nền từ bất kỳ màn hình nào.


@kiran boghra: Có bất kỳ dương tính giả trong giải pháp của bạn?
Harish Vishwakarma

Câu trả lời hoàn hảo hàm onStart () và onStop () có thể được sử dụng trong trường hợp này. trong đó cho bạn biết về ứng dụng của bạn
Pir Fahim Shah

6

Điều này khá dễ dàng với ProcessLifecycleOwner

Thêm các phụ thuộc này

implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"

Trong 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")
    }
}

Sau đó, trong hoạt động cơ sở của bạn:

override fun onCreate() {
        super.onCreate()

        ProcessLifecycleOwner.get()
                .lifecycle
                .addObserver(
                        ForegroundBackgroundListener()
                                .also { appObserver = it })
    }

Xem bài viết của tôi về chủ đề này: https://medium.com/@egek92/how-to-actual-detect-forground-background-changes-in-your-android-application-without-wocate-9719cc822c48


5

Bạn có thể sử dụng ProcessLifecyclOwner đính kèm một trình quan sát vòng đời với nó.

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

sau đó trên onCreate()lớp Ứng dụng của bạn, bạn gọi đây là:

ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());

với điều này, bạn sẽ có thể nắm bắt các sự kiện ON_PAUSEON_STOPứng dụng của bạn xảy ra khi nó chạy trong nền.


4

Không có phương pháp vòng đời đơn giản nào để cho bạn biết khi toàn bộ Ứng dụng chạy nền / nền trước.

Tôi đã làm điều này với cách đơn giản. Thực hiện theo các hướng dẫn bên dưới để phát hiện pha nền / nền trước của ứng dụng.

Với một chút cách giải quyết, nó là có thể. Ở đây, ActivityLifecyclCallbacks đến giải cứu. Hãy để tôi đi qua từng bước.

  1. Đầu tiên, tạo một lớp mở rộng android.app.Ứng dụng và thực hiện giao diện ActivityLifecycleCallbacks . Trong Application.onCreate (), hãy đăng ký gọi lại.

    public class App extends Application implements 
        Application.ActivityLifecycleCallbacks {
    
        @Override
        public void onCreate() {
            super.onCreate();
            registerActivityLifecycleCallbacks(this);
        }
    }
  2. Đăng ký lớp ứng dụng của ứng dụng trực tuyến trong Bản kê khai như dưới đây , <application android:name=".App".

  3. Sẽ có ít nhất một Hoạt động ở trạng thái bắt đầu khi ứng dụng ở nền trước và sẽ không có Hoạt động nào ở trạng thái bắt đầu khi ứng dụng ở chế độ nền.

    Khai báo 2 biến như dưới đây trong lớp App App.

    private int activityReferences = 0;
    private boolean isActivityChangingConfigurations = false;

    activityReferencessẽ giữ số lượng hoạt động ở trạng thái bắt đầu . isActivityChangingConfigurationslà một cờ để cho biết nếu Hoạt động hiện tại đang trải qua thay đổi cấu hình như một công tắc định hướng.

  4. Sử dụng đoạn mã sau bạn có thể phát hiện nếu Ứng dụng xuất hiện trước.

    @Override
    public void onActivityStarted(Activity activity) {
        if (++activityReferences == 1 && !isActivityChangingConfigurations) {
            // App enters foreground
        }
    }
  5. Đây là cách phát hiện nếu Ứng dụng chạy nền.

    @Override
    public void onActivityStopped(Activity activity) {
        isActivityChangingConfigurations = activity.isChangingConfigurations();
        if (--activityReferences == 0 && !isActivityChangingConfigurations) {
            // App enters background
        }
    }

Làm thế nào nó hoạt động:

Đây là một mẹo nhỏ được thực hiện theo cách các phương thức Vòng đời được gọi theo trình tự. Hãy để tôi hướng dẫn một kịch bản.

Giả sử rằng người dùng khởi chạy Ứng dụng và Trình khởi chạy Hoạt động A được khởi chạy. Các cuộc gọi Vòng đời sẽ là,

A.onCreate ()

A.onStart () (++ ActivityReferences == 1) (Ứng dụng vào Tiền cảnh)

A.onResume ()

Bây giờ Hoạt động A bắt đầu Hoạt động B.

A.onPause ()

B.onCreate ()

B.onStart () (++ ActivityReferences == 2)

B.onResume ()

A.onStop () (--activityReferences == 1)

Sau đó, người dùng điều hướng trở lại từ Hoạt động B,

B.onPause ()

A.onStart () (++ ActivityReferences == 2)

A.onResume ()

B.onStop () (--activityReferences == 1)

B.onDestroy ()

Sau đó, người dùng nhấn nút Home,

A.onPause ()

A.onStop () (--activityReferences == 0) (Ứng dụng vào nền)

Trong trường hợp, nếu người dùng nhấn nút Home từ Hoạt động B thay vì nút Quay lại, thì nó vẫn giống nhau và ActivityReferences sẽ là 0 . Do đó, chúng ta có thể phát hiện khi Ứng dụng vào Nền.

Vậy, vai trò của nó là isActivityChangingConfigurationsgì? Trong kịch bản trên, giả sử Hoạt động B thay đổi hướng. Trình tự gọi lại sẽ là,

B.onPause ()

B.onStop () (--activityReferences == 0) (Ứng dụng vào Nền ??)

B.onDestroy ()

B.onCreate ()

B.onStart () (++ ActivityReferences == 1) (Ứng dụng vào Tiền cảnh ??)

B.onResume ()

Đó là lý do tại sao chúng tôi có một kiểm tra bổ sung isActivityChangingConfigurationsđể tránh kịch bản khi Hoạt động đang trải qua Thay đổi cấu hình.


3

Tôi tìm thấy một phương pháp tốt để phát hiện ứng dụng cho dù nhập nền trước hay nền. Đây là của tôi . Hy vọng điều này sẽ giúp bạn.

/**
 * 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();

}


3

Bạn có thể dùng:

khoảng trống được bảo vệ trênRestart ()

Để khác nhau giữa khởi động mới và khởi động lại.

nhập mô tả hình ảnh ở đây


3

Chỉnh sửa 2: Những gì tôi đã viết dưới đây sẽ không thực sự hoạt động. Google đã từ chối một ứng dụng bao gồm lệnh gọi ActivityManager.getRastyT task (). Từ tài liệu , rõ ràng API này chỉ dành cho mục đích gỡ lỗi và phát triển. Tôi sẽ cập nhật bài viết này ngay khi tôi có thời gian để cập nhật dự án GitHub bên dưới với sơ đồ mới sử dụng bộ tính giờ và gần như là tốt.

Chỉnh sửa 1: Tôi đã viết một bài đăng trên blog và tạo một kho lưu trữ GitHub đơn giản để thực hiện việc này thực sự dễ dàng.

Câu trả lời được chấp nhận và đánh giá hàng đầu là cả hai không thực sự là cách tiếp cận tốt nhất. Việc triển khai câu trả lời được xếp hạng hàng đầu của isApplicationBvedToBackground () không xử lý tình huống trong đó Hoạt động chính của Ứng dụng mang lại một Hoạt động được xác định trong cùng một Ứng dụng, nhưng nó có gói Java khác. Tôi đã nghĩ ra một cách để làm điều này sẽ hoạt động trong trường hợp đó.

Gọi cái này trong onPause () và nó sẽ cho bạn biết nếu ứng dụng của bạn đi vào nền vì ứng dụng khác đã bắt đầu hoặc người dùng đã nhấn nút home.

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

FYI, gọi nó trong onStart () thay vào đó sẽ tránh nó được gọi khi một hộp thoại đơn giản được đưa lên, ví dụ, một báo thức sẽ tắt.
Sky Kelsey

2

Câu trả lời đúng ở đây

Tạo lớp với tên MyApp như dưới đây:

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() {

    }
}

Sau đó, ở mọi nơi bạn muốn (hoạt động đầu tiên tốt hơn được khởi chạy trong ứng dụng), hãy thêm mã bên dưới:

MyApp myApp = new MyApp();
registerComponentCallbacks(myApp);
getApplication().registerActivityLifecycleCallbacks(myApp);

Làm xong! Bây giờ khi ứng dụng ở chế độ nền, chúng tôi nhận được nhật ký status : we are out và khi chúng tôi vào ứng dụng, chúng tôi nhận được nhật kýstatus : we are out


1

Giải pháp của tôi được lấy cảm hứng từ câu trả lời của @ d60402 và cũng dựa vào cửa sổ thời gian, nhưng không sử dụng 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();
  }
}

trong đó SingletonApplicationphần mở rộng của Applicationlớp:

public class SingletonApplication extends Application {
  public boolean isInBackground = false;
  public long lastForegroundTransition = 0;
}

1

Tôi đã sử dụng điều này với Google Analytics EasyTracker và nó đã hoạt động. Nó có thể được mở rộng để làm những gì bạn tìm kiếm bằng cách sử dụng một số nguyên đơn giản.

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) {

            }
        });
    }
}

1

Tôi biết nó hơi muộn nhưng tôi nghĩ tất cả những câu trả lời này có một số vấn đề trong khi tôi đã làm nó như dưới đây và nó hoạt động hoàn hảo.

tạo một cuộc gọi lại vòng đời hoạt động như thế này:

 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) {

    }
}

và chỉ cần đăng ký nó trên lớp ứng dụng của bạn như dưới đây:

public class MyApp extends Application {

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}

Điều này được gọi là tất cả thời gian trên mỗi hoạt động. Làm cách nào tôi có thể sử dụng điều này nếu tôi muốn phát hiện trạng thái trực tuyến của người dùng
Maksim Kniazev

đó là những gì câu hỏi muốn. nó chỉ được gọi khi bạn vào màn hình chính và quay lại bất kỳ hoạt động nào.
Amir Ziarati

Nếu bạn có nghĩa là kết nối internet tôi nghĩ rằng tốt hơn để kiểm tra khi bạn cần nó. nếu bạn cần gọi api, hãy kiểm tra kết nối internet ngay trước khi gọi.
Amir Ziarati

1

Đây dường như là một trong những câu hỏi phức tạp nhất trong Android vì (kể từ khi viết bài này) Android không có tương đương applicationDidEnterBackground()hoặc applicationWillEnterForeground()gọi lại iOS . Tôi đã sử dụng Thư viện AppState được kết hợp bởi @jenzz .

[AppState là] một thư viện Android đơn giản, dễ phản ứng dựa trên RxJava theo dõi các thay đổi trạng thái của ứng dụng. Nó thông báo cho người đăng ký mỗi khi ứng dụng chạy vào nền và trở lại nền trước.

Hóa ra đây chính xác là những gì tôi cần, đặc biệt là vì ứng dụng của tôi có nhiều hoạt động nên chỉ cần kiểm tra onStart()hoặconStop() trên một hoạt động sẽ không cắt giảm.

Đầu tiên tôi thêm các phụ thuộc này vào lớp:

dependencies {
    compile 'com.jenzz.appstate:appstate:3.0.1'
    compile 'com.jenzz.appstate:adapter-rxjava2:3.0.1'
}

Sau đó, việc thêm các dòng này vào một vị trí thích hợp trong mã của bạn là một vấn đề đơn giản:

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

Tùy thuộc vào cách bạn đăng ký để có thể quan sát, bạn có thể phải hủy đăng ký để tránh rò rỉ bộ nhớ. Một lần nữa thông tin thêm trên trang github .


1

Đây là phiên bản sửa đổi của câu trả lời của @ d60402: https://stackoverflow.com/a/15573121/4747587

Làm mọi thứ được đề cập ở đó. Nhưng thay vì có Base Activityvà làm điều đó với tư cách là cha mẹ cho mọi hoạt động và ghi đè lên onResume()onPause, hãy làm như sau:

Trong lớp ứng dụng của bạn, thêm dòng:

registerActivityLifecyclCallbacks (Application.ActivityLifecyclCallbacks gọi lại);

Điều này callbackcó tất cả các phương thức vòng đời hoạt động và bây giờ bạn có thể ghi đè onActivityResumed()onActivityPaused().

Hãy xem Gist này: https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b


1

Bạn có thể đạt được điều này một cách dễ dàng với sự giúp đỡ của ActivityLifecycleCallbacksComponentCallbacks2 một cái gì đó như dưới đây.

Tạo một lớp AppLifeCycleHandlerthực hiện các giao diện nói trên.

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

Trong lớp của bạn mở rộng Applicationtriển khai AppLifeCycleCallbackđể nhận các cuộc gọi lại khi ứng dụng chuyển giữa nền trước và nền. Một cái gì đó như dưới đây.

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

Hi vọng điêu nay co ich.

EDIT Là một giải pháp thay thế, giờ đây bạn có thể sử dụng thành phần kiến ​​trúc nhận biết Vòng đời.


1

Vì tôi không tìm thấy bất kỳ phương pháp nào, cũng xử lý xoay vòng mà không kiểm tra tem thời gian, tôi nghĩ tôi cũng chia sẻ cách chúng tôi thực hiện trong ứng dụng của mình. Bổ sung duy nhất cho câu trả lời này https://stackoverflow.com/a/42679191/5119746 là, chúng tôi cũng xem xét định hướng.

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

Sau đó, đối với các cuộc gọi lại, chúng tôi có sơ yếu lý lịch đầu tiên:

   // ActivityLifecycleCallbacks

   override fun onActivityResumed(activity: Activity?) {

      mResumed++

      if (mAppIsInBackground) {

         // !!! App came from background !!! Insert code

         mAppIsInBackground = false
      }
      mOrientationWasChanged = false
    }

Và onActivityStopped:

   override fun onActivityStopped(activity: Activity?) {

       if (mResumed == mPaused && !mOrientationWasChanged) {

       // !!! App moved to background !!! Insert code

        mAppIsInBackground = true
    }

Và sau đó, đến phần bổ sung: Kiểm tra thay đổi định hướng:

   override fun onConfigurationChanged(newConfig: Configuration) {

       if (newConfig.orientation != mCurrentOrientation) {
           mCurrentOrientation = newConfig.orientation
           mOrientationWasChanged = true
       }
       super.onConfigurationChanged(newConfig)
   }

Đó là nó. Hy vọng điều này sẽ giúp được ai đó :)


1

Chúng tôi có thể mở rộng giải pháp này bằng cách sử dụng 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
    }
}

Bây giờ chúng tôi có thể đăng ký LiveData này và nắm bắt các sự kiện cần thiết. Ví dụ:

appForegroundStateLiveData.observeForever { state ->
    when(state) {
        AppForegroundStateLiveData.State.FOREGROUND -> { /* app move to foreground */ }
        AppForegroundStateLiveData.State.BACKGROUND -> { /* app move to background */ }
    }
}

0

Những câu trả lời này dường như không đúng. Các phương thức này cũng được gọi khi một hoạt động khác bắt đầu và kết thúc. Những gì bạn có thể làm là giữ một lá cờ toàn cầu (vâng, toàn cầu là xấu :) và đặt điều này thành đúng mỗi khi bạn bắt đầu một hoạt động mới. Đặt nó thành false trong onCreate của từng hoạt động. Sau đó, trong phần OnPause bạn kiểm tra cờ này. Nếu nó sai, ứng dụng của bạn sẽ chạy vào nền hoặc nó sẽ bị giết.


Tôi đã không nói về cơ sở dữ liệu ... ý bạn là gì?
Joris Weimar

Tôi đang ủng hộ câu trả lời của bạn. mặc dù chúng ta có thể lưu giá trị cờ đó trong cơ sở dữ liệu trong khi tạm dừng cuộc gọi, đó không phải là giải pháp tốt ..
Sandeep P
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.