Tôi đang thực hiện một thông báo trên thanh trạng thái trong ứng dụng Android được kích hoạt bởi c2dm. Tôi không muốn hiển thị thông báo nếu ứng dụng đang chạy. Làm cách nào để xác định xem ứng dụng có đang chạy hay không và đang ở nền trước?
Tôi đang thực hiện một thông báo trên thanh trạng thái trong ứng dụng Android được kích hoạt bởi c2dm. Tôi không muốn hiển thị thông báo nếu ứng dụng đang chạy. Làm cách nào để xác định xem ứng dụng có đang chạy hay không và đang ở nền trước?
Câu trả lời:
Tạo một biến toàn cục như private boolean mIsInForegroundMode;
và gán một false
giá trị trong onPause()
và một true
giá trị trong onResume()
.
Mã mẫu:
private boolean mIsInForegroundMode;
@Override
protected void onPause() {
super.onPause();
mIsInForegroundMode = false;
}
@Override
protected void onResume() {
super.onResume();
mIsInForegroundMode = true;
}
// Some function.
public boolean isInForeground() {
return mIsInForegroundMode;
}
Ngoài ra, bạn có thể kiểm tra các ActivityManager
tác vụ đang chạy bằng getRunningTasks
phương pháp nào. Sau đó, kiểm tra nhiệm vụ đầu tiên (nhiệm vụ ở phía trước) trong Danh sách nhiệm vụ được trả về, nếu đó là nhiệm vụ của bạn.
Đây là ví dụ mã:
public Notification buildNotification(String arg0, Map<String, String> arg1) {
ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> services = activityManager
.getRunningTasks(Integer.MAX_VALUE);
boolean isActivityFound = false;
if (services.get(0).topActivity.getPackageName().toString()
.equalsIgnoreCase(appContext.getPackageName().toString())) {
isActivityFound = true;
}
if (isActivityFound) {
return null;
} else {
// write your code to build a notification.
// return the notification you built here
}
}
Và đừng quên thêm GET_TASKS
quyền vào tệp manifest.xml để có thể chạy getRunningTasks()
phương thức trong đoạn mã trên:
<uses-permission android:name="android.permission.GET_TASKS" />
p / s: Nếu đồng ý theo cách này, xin lưu ý rằng quyền này hiện không được chấp nhận.
toString()
trên một Chuỗi được trả về getPackageName()
là dư thừa. Ngoài ra, vì chúng tôi chỉ quan tâm đến nhiệm vụ đầu tiên được trả lại getRunningTasks()
, chúng tôi có thể vượt qua 1
thay vì Integer.MAX_VALUE
.
Đây là một bài khá cũ nhưng vẫn còn khá phù hợp. Giải pháp được chấp nhận ở trên có thể hoạt động nhưng sai.Như Dianne Hackborn đã viết:
Các API này không ở đó để các ứng dụng dựa trên luồng giao diện người dùng của chúng, mà để thực hiện những việc như hiển thị cho người dùng các ứng dụng đang chạy hoặc trình quản lý tác vụ, v.v.
Vâng, có một danh sách được lưu trong bộ nhớ cho những điều này. Tuy nhiên, nó bị tắt trong một quy trình khác, được quản lý bởi các chuỗi chạy riêng biệt với của bạn và không phải là điều bạn có thể tin tưởng (a) nhìn thấy kịp thời để đưa ra quyết định chính xác hoặc (b) có một bức tranh nhất quán vào thời điểm bạn quay lại. Thêm vào đó, quyết định về hoạt động "tiếp theo" sẽ luôn được thực hiện tại thời điểm chuyển đổi sẽ xảy ra và phải đến thời điểm chính xác đó (nơi trạng thái hoạt động được khóa lại trong thời gian ngắn để thực hiện chuyển đổi), chúng tôi thực sự biết điều tiếp theo sẽ là gì.
Và việc thực hiện và hành vi toàn cầu ở đây không được đảm bảo sẽ giữ nguyên trong tương lai.
Giải pháp chính xác là thực hiện: ActivityLifeCycleCallbacks .
Về cơ bản, điều này cần một Lớp ứng dụng và trình xử lý có thể được đặt trong đó để xác định trạng thái hoạt động của bạn trong ứng dụng.
onPause
và onResume
phương pháp được sử dụng trong câu trả lời được chấp nhận như thế nào?
Như Vinay nói, có lẽ giải pháp tốt nhất (để hỗ trợ các phiên bản android mới hơn, 14+) là sử dụng ActivityLifecycleCallbacks
trong việc Application
triển khai lớp.
package com.telcel.contenedor.appdelegate;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;
/** Determines global app lifecycle states.
*
* The following is the reference of activities states:
*
* The <b>visible</b> lifetime of an activity happens between a call to onStart()
* until a corresponding call to onStop(). During this time the user can see the
* activity on-screen, though it may not be in the foreground and interacting with
* the user. The onStart() and onStop() methods can be called multiple times, as
* the activity becomes visible and hidden to the user.
*
* The <b>foreground</b> lifetime of an activity happens between a call to onResume()
* until a corresponding call to onPause(). During this time the activity is in front
* of all other activities and interacting with the user. An activity can frequently
* go between the resumed and paused states -- for example when the device goes to
* sleep, when an activity result is delivered, when a new intent is delivered --
* so the code in these methods should be fairly lightweight.
*
* */
public class ApplicationLifecycleManager implements ActivityLifecycleCallbacks {
/** Manages the state of opened vs closed activities, should be 0 or 1.
* It will be 2 if this value is checked between activity B onStart() and
* activity A onStop().
* It could be greater if the top activities are not fullscreen or have
* transparent backgrounds.
*/
private static int visibleActivityCount = 0;
/** Manages the state of opened vs closed activities, should be 0 or 1
* because only one can be in foreground at a time. It will be 2 if this
* value is checked between activity B onResume() and activity A onPause().
*/
private static int foregroundActivityCount = 0;
/** Returns true if app has foreground */
public static boolean isAppInForeground(){
return foregroundActivityCount > 0;
}
/** Returns true if any activity of app is visible (or device is sleep when
* an activity was visible) */
public static boolean isAppVisible(){
return visibleActivityCount > 0;
}
public void onActivityCreated(Activity activity, Bundle bundle) {
}
public void onActivityDestroyed(Activity activity) {
}
public void onActivityResumed(Activity activity) {
foregroundActivityCount ++;
}
public void onActivityPaused(Activity activity) {
foregroundActivityCount --;
}
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
public void onActivityStarted(Activity activity) {
visibleActivityCount ++;
}
public void onActivityStopped(Activity activity) {
visibleActivityCount --;
}
}
Và trong onCreate()
phương pháp Ứng dụng :
registerActivityLifecycleCallbacks(new ApplicationLifecycleManager());
Sau đó ApplicationLifecycleManager.isAppVisible()
hoặc ApplicationLifecycleManager.isAppInForeground()
sẽ được sử dụng để biết trạng thái mong muốn.
Kể từ API 16, bạn có thể làm như thế này:
static boolean shouldShowNotification(Context context) {
RunningAppProcessInfo myProcess = new RunningAppProcessInfo();
ActivityManager.getMyMemoryState(myProcess);
if (myProcess.importance != RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
return true;
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
// app is in foreground, but if screen is locked show notification anyway
return km.inKeyguardRestrictedInputMode();
}
Một chút phiên bản làm sạch của giải pháp Gadenkan . Đặt nó bất kỳ Hoạt động nào, hoặc có thể là một lớp cơ sở cho tất cả các Hoạt động của bạn.
protected boolean isRunningInForeground() {
ActivityManager manager =
(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = manager.getRunningTasks(1);
if (tasks.isEmpty()) {
return false;
}
String topActivityName = tasks.get(0).topActivity.getPackageName();
return topActivityName.equalsIgnoreCase(getPackageName());
}
Để có thể gọi getRunningTasks()
, bạn cần thêm điều này vào AndroidManifest.xml
:
<uses-permission android:name="android.permission.GET_TASKS"/>
Hãy lưu ý những gì ActivityManager.getRunningTasks()
Javadoc nói:
Lưu ý: phương pháp này chỉ nhằm gỡ lỗi và trình bày giao diện người dùng quản lý tác vụ. Điều này không bao giờ được sử dụng cho logic cốt lõi trong một ứng dụng, chẳng hạn như quyết định giữa các hành vi khác nhau dựa trên thông tin tìm thấy ở đây. Việc sử dụng như vậy không được hỗ trợ và có thể sẽ bị hỏng trong tương lai.
Lưu ý rằng tính năng getRunningTasks()
này không được dùng nữa trong API cấp 21 !
Kể từ khi
LOLLIPOP
, phương pháp này không còn khả dụng cho các ứng dụng của bên thứ ba: việc giới thiệu các nội dung gần đây tập trung vào tài liệu có nghĩa là nó có thể làm rò rỉ thông tin người cho người gọi. Đối với khả năng tương thích ngược, nó vẫn sẽ trả về một tập hợp con nhỏ dữ liệu của nó: ít nhất là các tác vụ của riêng người gọi và có thể là một số tác vụ khác như home được biết là không nhạy cảm.
Vì vậy, những gì tôi đã viết trước đó thậm chí còn phù hợp hơn:
Trong nhiều trường hợp, bạn có thể đưa ra một giải pháp tốt hơn. Ví dụ: làm điều gì đó trong onPause()
và onResume()
có thể là trong BaseActivity cho tất cả các Hoạt động của bạn.
(Trong trường hợp của chúng tôi, chúng tôi không muốn một hoạt động cảnh báo ngoại tuyến được khởi chạy nếu chúng tôi không ở trong nền trước, vì vậy trong BaseActivity, onPause()
chúng tôi chỉ cần hủy đăng ký nhận RxJava Subscription
nghe tín hiệu "đã ngoại tuyến".)
Theo dõi câu trả lời của Gadenkan, tôi cần một cái gì đó như thế này để tôi có thể biết liệu ứng dụng của mình không chạy ở nền trước hay không, nhưng tôi cần một cái gì đó rộng rãi ứng dụng và không yêu cầu tôi phải cài đặt / gỡ bỏ cờ trong ứng dụng của mình.
Mã của Gadenkan khá ấn tượng nhưng nó không theo phong cách riêng của tôi và tôi cảm thấy nó có thể gọn gàng hơn, vì vậy trong ứng dụng của tôi, nó được cô đọng lại thành phần này.
if (!context.getPackageName().equalsIgnoreCase(((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0).topActivity.getPackageName()))
{
// App is not in the foreground
}
(Lưu ý bên: Bạn chỉ có thể bỏ dấu! Nếu bạn muốn séc hoạt động theo cách khác)
Mặc dù với cách tiếp cận này bạn cần sự GET_TASKS
cho phép.
Bắt đầu thư viện hỗ trợ phiên bản 26, bạn có thể sử dụng ProcessLifecycleOwner để xác định trạng thái hiện tại của ứng dụng, chỉ cần thêm nó vào các phần phụ thuộc của bạn như được mô tả ở đây , ví dụ:
dependencies {
def lifecycle_version = "1.1.1"
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
// alternatively - Lifecycles only (no ViewModel or LiveData).
// Support library depends on this lightweight import
implementation "android.arch.lifecycle:runtime:$lifecycle_version"
annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
}
, Bây giờ bạn có thể truy vấn ProcessLifecycleOwner
bất cứ khi nào bạn muốn kiểm tra trạng thái ứng dụng, chẳng hạn như để kiểm tra xem ứng dụng có đang chạy ở nền trước hay không, bạn chỉ cần thực hiện điều này:
boolean isAppInForeground = ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
if(!isAppInForeground)
//Show Notification in status bar
implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
trong gradle dự án của bạn.
Dựa trên các câu trả lời và nhận xét khác nhau, đây là một phiên bản nội tuyến hơn mà bạn có thể thêm vào lớp trợ giúp:
public static boolean isAppInForeground(Context context) {
List<RunningTaskInfo> task =
((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
.getRunningTasks(1);
if (task.isEmpty()) {
return false;
}
return task
.get(0)
.topActivity
.getPackageName()
.equalsIgnoreCase(context.getPackageName());
}
Như đã đề cập trong các câu trả lời khác, bạn cần thêm quyền sau vào của bạn AndroidManifest.xml
.
<uses-permission android:name="android.permission.GET_TASKS"/>
Tôi muốn nói thêm rằng một cách an toàn hơn để làm điều này - ngoài việc kiểm tra xem ứng dụng của bạn có ở chế độ nền hay không trước khi tạo thông báo - là chỉ cần tắt và bật Bộ thu phát sóng lần lượt onPause () và onResume ().
Phương pháp này cho phép bạn kiểm soát nhiều hơn trong logic ứng dụng thực tế và không có khả năng thay đổi trong tương lai.
@Override
protected void onPause() {
unregisterReceiver(mHandleMessageReceiver);
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION));
}
Tôi đã tìm thấy một cách đơn giản và chính xác hơn để kiểm tra xem ứng dụng đang ở chế độ nền hay nền bằng cách ánh xạ các hoạt động tới boolean.
Kiểm tra ý chính đầy đủ tại đây
Đây là mã cho giải pháp đơn giản tuyệt vời được mô tả ở trên bởi @ user2690455. Mặc dù có vẻ hơi dài dòng nhưng bạn sẽ thấy nó thực sự khá nhẹ
Trong trường hợp của tôi, chúng tôi cũng sử dụng AppCompatActivity, vì vậy tôi phải có 2 lớp cơ sở.
public class BaseActivity extends Activity {
/**
* Let field be set only in base class
* All callers must use accessors,
* and then it's not up to them to manage state.
*
* Making it static since ..
* 1. It needs to be used across two base classes
* 2. It's a singleton state in the app
*/
private static boolean IS_APP_IN_BACKGROUND = false;
@Override
protected void onResume() {
super.onResume();
BaseActivity.onResumeAppTracking(this);
BaseActivity.setAppInBackgroundFalse();
}
@Override
protected void onStop() {
super.onStop();
BaseActivity.setAppInBackgroundTrue();
}
@Override
protected void onPause() {
super.onPause();
BaseActivity.setAppInBackgroundFalse();
}
protected static void onResumeAppTracking(Activity activity) {
if (BaseActivity.isAppInBackground()) {
// do requirements for returning app to foreground
}
}
protected static void setAppInBackgroundFalse() {
IS_APP_IN_BACKGROUND = false;
}
protected static void setAppInBackgroundTrue() {
IS_APP_IN_BACKGROUND = true;
}
protected static boolean isAppInBackground() {
return IS_APP_IN_BACKGROUND;
}
}
Điều này chỉ hữu ích khi bạn muốn thực hiện một số hành động ngay khi hoạt động của bạn bắt đầu và nơi bạn muốn kiểm tra xem ứng dụng đang ở nền trước hay nền.
Thay vì sử dụng Trình quản lý hoạt động, có một thủ thuật đơn giản mà bạn có thể thực hiện thông qua mã. Nếu bạn quan sát kỹ chu trình hoạt động, dòng chảy giữa hai hoạt động và tiền cảnh đến hậu cảnh như sau. Giả sử A và B là hai hoạt động.
Khi chuyển từ A sang B: 1. onPause () của A được gọi là 2. onResume () của B được gọi là 3. onStop () của A được gọi khi B được tiếp tục hoàn toàn
Khi ứng dụng chuyển sang chế độ nền: 1. onPause () của A được gọi là 2. onStop () của A được gọi
Bạn có thể phát hiện sự kiện nền của mình bằng cách chỉ cần đặt một lá cờ trong hoạt động.
Thực hiện một hoạt động trừu tượng và mở rộng nó khỏi các hoạt động khác của bạn, để bạn không phải sao chép, dán mã cho tất cả các hoạt động khác ở bất cứ đâu bạn cần sự kiện nền.
Trong hoạt động trừu tượng, tạo cờ isAppInBackground.
Trong phương thức onCreate (): isAppInBackground = false;
Trong phương thức onPause (): isAppInBackground = false;
Trong phương thức onStop (): isAppInBackground = true;
Bạn chỉ cần kiểm tra onResume () của mình nếu isAppInBackground là true. n sau khi bạn kiểm tra cờ của mình rồi đặt lại isAppInBackground = false
Đối với quá trình chuyển đổi giữa hai hoạt động vì onSTop () của hoạt động đầu tiên sẽ luôn được gọi sau khi hoạt động thứ hai tiếp tục, cờ sẽ không bao giờ đúng và khi ứng dụng ở chế độ nền, hoạt động onStop () sẽ được gọi ngay sau onPause và do đó cờ sẽ đúng khi bạn mở ứng dụng sau này.
Có một kịch bản nữa mặc dù trong cách tiếp cận này. Nếu bất kỳ màn hình ứng dụng nào của bạn đã mở và bạn để thiết bị di động không hoạt động thì sau một thời gian, thiết bị di động sẽ chuyển sang chế độ ngủ và khi bạn mở khóa thiết bị di động, nó sẽ được xử lý ở sự kiện nền.
Đây là một phương pháp mà tôi sử dụng (và phương pháp hỗ trợ):
private boolean checkIfAppIsRunningInForeground() {
ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
for(ActivityManager.RunningAppProcessInfo appProcessInfo : activityManager.getRunningAppProcesses()) {
if(appProcessInfo.processName.contains(this.getPackageName())) {
return checkIfAppIsRunningInForegroundByAppImportance(appProcessInfo.importance);
}
}
return false;
}
private boolean checkIfAppIsRunningInForegroundByAppImportance(int appImportance) {
switch (appImportance) {
//user is aware of app
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE:
return true;
//user is not aware of app
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE:
case ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE:
default:
return false;
}
}
Không có lệnh gọi lại toàn cầu cho điều này, nhưng đối với mỗi hoạt động, nó là onStop (). Bạn không cần phải rắc rối với một int nguyên tử. Chỉ cần có một int toàn cục với số lượng các hoạt động đã bắt đầu, trong mỗi hoạt động, hãy tăng nó trong onStart () và giảm nó trong onStop ().
Theo dõi cái này
public static boolean isAppRunning(Context context) {
// check with the first task(task in the foreground)
// in the returned list of tasks
ActivityManager activityManager = (ActivityManager)
context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> services =
activityManager.getRunningTasks(Integer.MAX_VALUE);
if
(services.get(0).topActivity.getPackageName().toString().equalsIgnoreCase(context.getPackageName().toString()))
{
return true;
}
return false;
}
Các cách tiếp cận trước đây được đề cập ở đây không phải là tối ưu. Cách tiếp cận dựa trên nhiệm vụ yêu cầu một sự cho phép có thể không được mong muốn và cách tiếp cận "Boolean" dễ gây ra sự cố sửa đổi đồng thời.
Cách tiếp cận mà tôi sử dụng và (tôi tin rằng) hoạt động khá tốt trong hầu hết các trường hợp:
Có một lớp "MainApplication" để theo dõi số lượng hoạt động trong AtomicInteger :
import android.app.Application;
import java.util.concurrent.atomic.AtomicInteger;
public class MainApplication extends Application {
static class ActivityCounter {
private static AtomicInteger ACTIVITY_COUNT = new AtomicInteger(0);
public static boolean isAppActive() {
return ACTIVITY_COUNT.get() > 0;
}
public static void activityStarted() {
ACTIVITY_COUNT.incrementAndGet();
}
public static void activityStopped() {
ACTIVITY_COUNT.decrementAndGet();
}
}
}
Và tạo một lớp Hoạt động cơ sở mà các hoạt động khác sẽ mở rộng:
import android.app.Activity;
import android.support.annotation.CallSuper;
public class TestActivity extends Activity {
@Override
@CallSuper
protected void onStart() {
MainApplication.ActivityCounter.activityStarted();
super.onStart();
}
@Override
@CallSuper
protected void onStop() {
MainApplication.ActivityCounter.activityStopped();
super.onStop();
}
}