c2dmによってトリガーされるAndroidアプリでステータスバー通知を行っています。アプリが実行されている場合に通知を表示したくありません。アプリが実行中であり、フォアグラウンドにあるかどうかをどのように判断しますか?
c2dmによってトリガーされるAndroidアプリでステータスバー通知を行っています。アプリが実行されている場合に通知を表示したくありません。アプリが実行中であり、フォアグラウンドにあるかどうかをどのように判断しますか?
回答:
のようなグローバル変数を作成private boolean mIsInForegroundMode;
し、のfalse
値onPause()
とのtrue
値を割り当てますonResume()
。
サンプルコード:
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;
}
または、メソッドActivityManager
ごとに実行されているタスクを確認することもできますgetRunningTasks
。次に、返されたタスクのリストの最初のタスク(フォアグラウンドのタスク)を確認します(それが自分のタスクである場合)。
コード例は次のとおりです。
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
}
}
また、上記のコードでメソッドを実行できるようにするにGET_TASKS
は、manifest.xmlファイルに権限を追加することを忘れないでくださいgetRunningTasks()
。
<uses-permission android:name="android.permission.GET_TASKS" />
p / s:この方法で同意する場合、この許可は現在非推奨であることに注意してください。
toString()
によって返される文字列の呼び出しgetPackageName()
は冗長です。また、によって返される最初のタスクのみに関心があるため、の代わりにgetRunningTasks()
渡すことができます。1
Integer.MAX_VALUE
これはかなり古い投稿ですが、それでもかなり関連性があります。上記の受け入れられた解決策は機能するかもしれませんが、間違っています。ダイアンハックボーンが書いたように:
これらのAPIは、アプリケーションがUIフローのベースにするためのものではなく、実行中のアプリやタスクマネージャーなどをユーザーに表示するためのものです。
はい、これらのもののためにメモリに保持されているリストがあります。ただし、別のプロセスではオフになっており、スレッドとは別に実行されているスレッドによって管理されており、(a)正しい決定を下すために時間内に確認したり、(b)戻ったときまでに一貫した状況を把握したりすることはできません。さらに、どの「次の」アクティビティに進むかについての決定は、切り替えが行われる時点で常に行われ、その正確な時点(切り替えを行うためにアクティビティ状態が一時的にロックダウンされる)までは行われません。そのようなことで次のことがどうなるかを実際に知っています。
また、ここでの実装とグローバルな動作は、将来同じであるとは限りません。
正しい解決策は、ActivityLifeCycleCallbacksを実装することです。
これには基本的にアプリケーションクラスが必要であり、そこにハンドラーを設定して、アプリでのアクティビティの状態を識別できます。
onPause
およびonResume
方法とどのように異なりますか?
Vinayが言うように、おそらく最良の解決策(新しいAndroidバージョン、14以降をサポートするため)はActivityLifecycleCallbacks
、Application
クラスの実装で使用することです。
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 --;
}
}
そして、アプリケーションのonCreate()
方法で:
registerActivityLifecycleCallbacks(new ApplicationLifecycleManager());
次に、ApplicationLifecycleManager.isAppVisible()
またはApplicationLifecycleManager.isAppInForeground()
を使用して、目的の状態を確認します。
API 16以降、次のように実行できます。
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();
}
Gadenkanのソリューションのわずかにクリーンアップされたバージョン。任意のアクティビティ、またはすべてのアクティビティの基本クラスを配置します。
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());
}
を呼び出すことができるようにするにはgetRunningTasks()
、これをあなたのAndroidManifest.xml
:に追加する必要があります
<uses-permission android:name="android.permission.GET_TASKS"/>
ただし、ActivityManager.getRunningTasks()
Javadocの内容に注意してください。
注:このメソッドは、タスク管理ユーザーインターフェイスのデバッグと表示のみを目的としています。ここにある情報に基づいて異なる動作を決定するなど、アプリケーションのコアロジックにこれを使用しないでください。このような使用はサポートされておらず、将来的に機能しなくなる可能性があります。
APIレベル21で非推奨になったことgetRunningTasks()
に注意してください!
現在
LOLLIPOP
、この方法はサードパーティのアプリケーションでは使用できません。ドキュメント中心の最近の導入により、発信者に個人情報が漏洩する可能性があります。下位互換性のために、データの小さなサブセットを返します。少なくとも呼び出し元自身のタスクと、機密性が低いことがわかっているホームなどの他のタスクです。
したがって、私が以前に書いたことはさらに関連性があります。
多くの場合、おそらくより良い解決策を思い付くことができます。たとえば、onPause()
およびonResume()
、おそらくすべてのアクティビティのBaseActivityで何かを実行します。
(私たちの場合、フォアグラウンドにいない場合にオフラインアラートアクティビティを起動したくないので、BaseActivityでは、「オフラインになりました」信号をリッスンしてonPause()
いるRxJavaから単にサブスクライブを解除しSubscription
ます。)
Gadenkanの返信をフォローアップするために、アプリがフォアグラウンドで実行されていないかどうかを確認できるように、このようなものが必要でしたが、アプリ全体でフラグを設定/設定解除する必要のないものが必要でした。
Gadenkanのコードはほとんど頭に釘付けになりましたが、それは私自身のスタイルではなく、よりきれいにできると感じたので、私のアプリではこれに凝縮されています。
if (!context.getPackageName().equalsIgnoreCase(((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0).topActivity.getPackageName()))
{
// App is not in the foreground
}
(補足:チェックを逆に機能させたい場合は、!を削除するだけです)
このアプローチではGET_TASKS
許可が必要ですが。
サポートライブラリのバージョン26の起動に使用できるProcessLifecycleOwnerを説明したように、ちょうどあなたの依存関係に追加し、アプリの現在の状態を判断するために、ここで例えば、:
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
}
、ProcessLifecycleOwner
アプリの状態を確認したいときはいつでもクエリを実行できるようになりました。たとえば、アプリがフォアグラウンドで実行されているかどうかを確認するには、次のようにする必要があります。
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'
、プロジェクトGradleで使用します。
さまざまな回答とコメントに基づいて、ヘルパークラスに追加できるインラインバージョンを次に示します。
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());
}
他の回答で述べたように、次の権限をに追加する必要がありますAndroidManifest.xml
。
<uses-permission android:name="android.permission.GET_TASKS"/>
これを行うためのより安全な方法は、通知を作成する前にアプリがバックグラウンドにあるかどうかを確認するよりも、ブロードキャストレシーバーのonPause()とonResume()をそれぞれ無効または有効にすることです。
この方法では、実際のアプリケーションロジックをより細かく制御でき、将来変更される可能性はありません。
@Override
protected void onPause() {
unregisterReceiver(mHandleMessageReceiver);
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION));
}
アクティビティをブール値にマッピングすることで、アプリケーションがフォアグラウンドかバックグラウンドかを確認するためのより簡単で正確な方法を見つけました。
ここで完全な要点を確認してください
これは、@ user2690455によって上記で説明された素晴らしい単純なソリューションのコードです。少し冗長に見えますが、全体的にはかなり軽量であることがわかります。
私の場合、AppCompatActivityも使用しているため、2つの基本クラスが必要でした。
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;
}
}
これは、アクティビティの開始時に何らかのアクションを実行し、アプリがフォアグラウンドかバックグラウンドかを確認する場合にのみ役立ちます。
アクティビティマネージャーを使用する代わりに、コードを介して実行できる簡単なトリックがあります。活動サイクルを注意深く観察すると、2つの活動と前景から背景への流れは次のようになります。AとBが2つのアクティビティであるとします。
AからBへの移行時:1。AのonPause()が呼び出されます2. BのonResume()が呼び出されます3. Bが完全に再開されたときにAのonStop()が呼び出されます
アプリがバックグラウンドに入ると:1。AのonPause()が呼び出されます2. AのonStop()が呼び出されます
アクティビティにフラグを設定するだけで、バックグラウンドイベントを検出できます。
抽象アクティビティを作成し、他のアクティビティから拡張して、バックグラウンドイベントが必要な場所で他のすべてのアクティビティのコードをコピーして貼り付ける必要がないようにします。
抽象アクティビティでは、フラグisAppInBackgroundを作成します。
onCreate()メソッドの場合:isAppInBackground = false;
onPause()メソッドの場合:isAppInBackground = false;
onStop()メソッドの場合:isAppInBackground = true;
isAppInBackgroundがtrueの場合は、onResume()をチェックインする必要があります。nフラグを確認した後、もう一度isAppInBackground = falseを設定します。
最初のonSTop()は常に2番目のアクティビティが再開した後に呼び出されるため、2つのアクティビティ間の移行では、フラグがtrueになることはなく、アプリがバックグラウンドの場合、アクティビティのonStop()はonPauseの直後に呼び出されるため、フラグは次の場合にtrueになります。後でアプリを開きます。
ただし、このアプローチにはもう1つのシナリオがあります。アプリの画面のいずれかがすでに開いていて、モバイルをアイドル状態にすると、しばらくするとモバイルはスリープモードになり、モバイルのロックを解除すると、バックグラウンドイベントで処理されます。
これが私が使用するメソッド(およびサポートメソッド)です:
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;
}
}
これに対するグローバルコールバックはありませんが、アクティビティごとにonStop()です。アトミックintをいじる必要はありません。開始されたアクティビティの数を含むグローバルintを作成し、すべてのアクティビティでonStart()でインクリメントし、onStop()でデクリメントします。
これに従ってください
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;
}
ここで説明した以前のアプローチは最適ではありません。タスクベースのアプローチには、望ましくない可能性のある権限が必要であり、「ブール」アプローチでは、同時変更が混乱する傾向があります。
私が使用しているアプローチと(私が信じている)ほとんどの場合、非常にうまく機能します。
で活動回数を追跡する「MainApplication」クラス持っているの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();
}
}
}
そして、他のアクティビティが拡張する基本アクティビティクラスを作成します。
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();
}
}