Android:(サービスから)現在のフォアグラウンドアクティビティを取得するにはどうすればよいですか?


180

サービスから現在実行中のアクティビティへの参照を取得するネイティブのAndroid方法はありますか?

バックグラウンドでサービスを実行していますが、(サービスで)イベントが発生したときに現在のアクティビティを更新したいと思います。それを行う簡単な方法はありますか(私が上で提案したもののように)?


多分これはあなたにあなたにアイデアを与えることができますstackoverflow.com/a/28423385/185022
AZ_

回答:


87

サービスから現在実行中のアクティビティへの参照を取得するネイティブのAndroid方法はありますか?

「現在実行中のアクティビティ」を所有していない可能性があります。

バックグラウンドでサービスを実行していますが、(サービスで)イベントが発生したときに現在のアクティビティを更新したいと思います。それを行う簡単な方法はありますか(私が上で提案したもののように)?

  1. Intentアクティビティにブロードキャストを送信します - これは、このパターンを示すサンプルプロジェクトです
  2. サービスが呼び出すをアクティビティにPendingIntent(たとえばを介してcreatePendingResult())提供させる
  3. アクティビティにを介してコールバックまたはリスナーオブジェクトをサービスに登録させ、サービスbindService()にそのコールバック/リスナーオブジェクトのイベントメソッドを呼び出させる
  4. 順序付けさIntentれたブロードキャストを、優先度の低いBroadcastReceiverバックアップとして(Notificationアクティビティが画面上にない場合に発生させるために)アクティビティに送信します- このパターンの詳細が記載されたブログ投稿があります

3
おかげで、私は略奪活動があり、それらすべてを更新したくないので、foregroundActivityを取得するAndroidの方法を探していました。あなたが言うように、私はそれをすることができないはずです。これを回避する必要があることを意味します。
ジョージ

1
@George:とにかく、あなたが欲しいものはあなたを助けにはなりません。あなたが指摘するように、あなたは「活動の略奪」を持っています。したがって、これらはすべて別個のクラスです。したがって、instanceofチェックの大規模なifブロック、または共通のスーパークラスまたはインターフェースを共有するためのアクティビティのリファクタリングなしでは、サービスはそれらを使って何もできません。また、アクティビティをリファクタリングする場合は、フレームワークによりよく適合し、アクティブなアクティビティがないなど、より多くのシナリオをカバーする方法で行うこともできます。#4はおそらく最も作業が少なく、最も柔軟性があります。
CommonsWare 2010年

2
おかげで、私はより良い解決策を持っています。私のすべてのアクティビティは、カスタマイズされたBaseActivityクラスを拡張します。私は、BaseActivityクラスに文字通り3行で、フォアグラウンドにあるときは常にアクティビティを現​​在として登録するContextRegisterを設定しました。それでも、サポートに感謝します。
ジョージ

3
@George:メモリリークに注意してください。変更可能な静的データメンバーは、Javaでは可能な限り避けてください。
CommonsWare 2010年

オブジェクトをしっかりとカプセル化しましたが、実際にこれを覚えておきます。ありがとう。
ジョージ

139

更新:Android 5.0以降、他のアプリのアクティビティでは動作なくなりました


アクティビティマネージャーを使用してこれを行う良い方法を次に示します。基本的には、runningTasksをアクティビティマネージャーから取得します。常に最初に現在アクティブなタスクを常に返します。そこからtopActivityを取得できます。

ここの例

ActivityManagerサービスから実行中のタスクのリストを取得する簡単な方法があります。電話で実行するタスクの最大数をリクエストできます。デフォルトでは、現在アクティブなタスクが最初に返されます。

それができたら、リストからtopActivityを要求することにより、ComponentNameオブジェクトを取得できます。

ここに例があります。

    ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
    Log.d("topActivity", "CURRENT Activity ::" + taskInfo.get(0).topActivity.getClassName());
    ComponentName componentInfo = taskInfo.get(0).topActivity;
    componentInfo.getPackageName();

マニフェストには次の権限が必要です。

<uses-permission android:name="android.permission.GET_TASKS"/>

3
あなたの答えは、より良いuはここに同じ答えを投稿することが100%correct.Thanxx 4 it.Itsある
Shahzadイマーム

1
@ArtOfWarfareは検証されていませんが、はい。フラグメントは常に単一のアクティビティによってホストされているため、フラグメントの数は関係ありません。
ネルソンラミレス

2
現在のアクティビティをより頻繁に必要とする場合、非常に頻繁にポーリングする必要がありますか?フォアグラウンドアクティビティが変更されるたびにコールバックイベントを取得する方法はありますか?
nizam.sp 2013年

43
誰もが知っているように、ドキュメントはgetRunningTasks()メソッドについてこれを述べています。--------注:このメソッドは、タスク管理ユーザーインターフェイスのデバッグと表示のみを目的としています。ここで見つかった情報に基づいて異なる動作を決定するなど、アプリケーションのコアロジックにはこれを使用しないでください。このような使用法はサポートされておらず、将来は機能しなくなる可能性があります。たとえば、複数のアプリケーションが同時にアクティブに実行できる場合、制御フローの目的でここでのデータの意味について行われた仮定は正しくありません。------------
Ryan

30
API 21のAs of LOLLIPOP, this method is no longer available to third party applications: the introduction of document-centric recents means it can leak person information to the caller. For backwards compatibility, it will still return a small subset of its data: at least the caller's own tasks, and possibly some other tasks such as home that are known to not be sensitive.
セミフェイク

115

警告:Google Play違反

Googleは、アクセシビリティ以外の目的でアクセシビリティサービスを使用するアプリをPlayストアから削除すると脅迫しています。しかし、これは再考されていると伝えられています。


を使用 AccessibilityService

利点

  • テスト済みで、Android 2.2(API 8)からAndroid 7.1(API 25)で動作します。
  • ポーリングは必要ありません。
  • GET_TASKS許可は必要ありません。

短所

  • 各ユーザーは、Androidのアクセシビリティ設定でサービスを有効にする必要があります。
  • これは100%信頼できるものではありません。時々、イベントは順不同で行われます。
  • サービスは常に実行されています。
  • ユーザーが有効にしようとすると AccessibilityServiceアプリが画面にオーバーレイを配置した場合、[OK]ボタンを押すことができません。これを行うアプリには、Velis Auto BrightnessとLuxがあります。ユーザーがボタンを押すことができない理由やその回避方法をユーザーが知らない可能性があるため、これは混乱を招く可能性があります。
  • AccessibilityService最初まで現在の活動を知ることができません変更の活動の。

サービス

public class WindowChangeDetectingService extends AccessibilityService {

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();

        //Configure these here for compatibility with API 13 and below.
        AccessibilityServiceInfo config = new AccessibilityServiceInfo();
        config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;

        if (Build.VERSION.SDK_INT >= 16)
            //Just in case this helps
            config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;

        setServiceInfo(config);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (event.getPackageName() != null && event.getClassName() != null) {
                ComponentName componentName = new ComponentName(
                    event.getPackageName().toString(),
                    event.getClassName().toString()
                );

                ActivityInfo activityInfo = tryGetActivity(componentName);
                boolean isActivity = activityInfo != null;
                if (isActivity)
                    Log.i("CurrentActivity", componentName.flattenToShortString());
            }
        }
    }

    private ActivityInfo tryGetActivity(ComponentName componentName) {
        try {
            return getPackageManager().getActivityInfo(componentName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    @Override
    public void onInterrupt() {}
}

AndroidManifest.xml

これをマニフェストにマージします。

<application>
    <service
        android:label="@string/accessibility_service_name"
        android:name=".WindowChangeDetectingService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService"/>
        </intent-filter>
        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessibilityservice"/>
    </service>
</application>

サービス情報

これを入れてくださいres/xml/accessibilityservice.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- These options MUST be specified here in order for the events to be received on first
 start in Android 4.1.1 -->
<accessibility-service
    xmlns:tools="http://schemas.android.com/tools"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews"
    android:description="@string/accessibility_service_description"
    xmlns:android="http://schemas.android.com/apk/res/android"
    tools:ignore="UnusedAttribute"/>

サービスを有効にする

アプリの各ユーザーは、アプリを使用するために明示的に有効にする必要がありAccessibilityServiceます。このStackOverflowの回答を見るを行う方法を。

アプリがVelis Auto BrightnessやLuxなどのオーバーレイを画面に配置した場合、ユーザー補助機能サービスを有効にしようとしたときにユーザーが[OK]ボタンを押すことができないことに注意してください。


1
@lovemint、私が意味することは次のとおりです。私は例指定されたsettingsActivityのをyour.app.ServiceSettingsActivity、あなたのユーザー補助サービスのために独自の設定の活動にそれを変更する必要がありますので、。とにかく設定アクティビティはオプションだと思うので、簡単にするためにその部分を私の答えから削除しました。
サム

1
最後の質問だけに感謝します。API 8から最新のものに対処する必要がある場合、XMLを使用する代わりにコードで機能させる必要がありますか?
paolo2988

1
@lovemint、そうです。これを行うためにサンプルコードを更新しました。
サム・

1
@Samこのサービスが完全に機能することを確認できます。奇妙な動作の1つとして、ユーザー補助機能サービスを有効にするためにメインアクティビティでインテントを使用しました。この最初のアクティブ化の後、サービスonAccessibilityEventはアクティブ化されますが、イベントを受け取りませんが、ユーザー補助サービスを無効にして再度有効にすると、サービスが再度アクティブになり、機能し onAccessibilityEvent始めます。
paolo2988 2015年

2
コードをありがとうございます。私はあなたのためにこのアプリを作成しました:)
ボールト

20

これは次の方法で実行できます。

  1. 独自のアプリケーションクラスを実装し、ActivityLifecycleCallbacksに登録します。これにより、アプリで何が起こっているかを確認できます。コールバックは、再開するたびに画面に表示されている現在のアクティビティを割り当て、一時停止すると割り当てを削除します。registerActivityLifecycleCallbacks()API 14で追加されたメソッドを使用します。

    public class App extends Application {
    
    private Activity activeActivity;
    
    @Override
    public void onCreate() {
        super.onCreate();
        setupActivityListener();
    }
    
    private void setupActivityListener() {
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }
            @Override
            public void onActivityStarted(Activity activity) {
            }
            @Override
            public void onActivityResumed(Activity activity) {
                activeActivity = activity;
            }
            @Override
            public void onActivityPaused(Activity activity) {
                activeActivity = null;
            }
            @Override
            public void onActivityStopped(Activity activity) {
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {
            }
        });
    }
    
    public Activity getActiveActivity(){
        return activeActivity;
    }
    
    }
  2. サービス呼び出しでgetApplication()、それをアプリのクラス名(この場合はApp)にキャストします。あなたが呼び出すことができるよりapp.getActiveActivity()-それはあなたに現在表示されているアクティビティを与えます(またはアクティビティが表示されていない場合はnull)。を呼び出すことでアクティビティの名前を取得できますactiveActivity.getClass().getSimpleName()


1
activeActivity.getClass()。getSimpleName()。でNullpointerの例外が発生します。助けていただけますか
初心者の初心者の

ActivityLifecycleCallbacksからわかるように、アクティビティが表示されない場合、application.getActiveActivity()はnullを返します。つまり、アクティビティは表示されません。あなたはあなたのサービスまたはあなたがこれを使用する他の場所でこれをチェックする必要があります。
Vit Veres 2017年

ok..butアクティビティが再開した場合、これはnullを返しません。私の活動が開始され、その再開時に私はそれを得ました
初心者

ハード、推測でブレークポイントを置くしようとするonActivityResumed()onActivityPaused()としgetActiveActivity()、これまでそうならば、それはカレする方法を参照してください。
Vit Veres 2017年

@beginner 1は、かどうかを確認しなければならない activityactiveActivity割り当てる前に同じactiveActivitynull起因する様々な活動のライフサイクルメソッドの混在呼び出すために避けるエラーが発生するために。
Rahul Tiwari

16

私たちのチームが満足できる解決策を見つけることができなかったので、私たちは自分たちで開発しました。ActivityLifecycleCallbacks現在のアクティビティを追跡し、サービスを通じて公開するために使用します。

public interface ContextProvider {
    Context getActivityContext();
}

public class MyApplication extends Application implements ContextProvider {
    private Activity currentActivity;

    @Override
    public Context getActivityContext() {
         return currentActivity;
    }

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

        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityStarted(Activity activity) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityResumed(Activity activity) {
                MyApplication.this.currentActivity = activity;
            }

            @Override
            public void onActivityPaused(Activity activity) {
                MyApplication.this.currentActivity = null;
            }

            @Override
            public void onActivityStopped(Activity activity) {
                // don't clear current activity because activity may get stopped after
                // the new activity is resumed
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                // don't clear current activity because activity may get destroyed after
                // the new activity is resumed
            }
        });
    }
}

その後の戻りインスタンスにあなたのDIコンテナを設定するMyApplicationためにContextProvider、例えば

public class ApplicationModule extends AbstractModule {    
    @Provides
    ContextProvider provideMainActivity() {
        return MyApplication.getCurrent();
    }
}

getCurrent()上記のコードではの実装は省略されていることに注意してください。これは、アプリケーションコンストラクターから設定される単なる静的変数です)


4
1は、かどうかを確認しなければならない activitycurrentActivity割り当てる前に同じcurrentActivitynull起因する様々な活動のライフサイクルメソッドの混ざり呼び出し順序に誤りを避けるために。
Rahul Tiwari

14

使用する ActivityManager

現在のアクティビティを含むアプリケーションのみを知りたい場合は、を使用して確認できますActivityManager。使用できる手法は、Androidのバージョンによって異なります。

利点

  • これまでのすべてのAndroidバージョンで動作するはずです。

短所

  • Android 5.1以降では機能しません(自分のアプリのみが返されます)
  • これらのAPIのドキュメントはデバッグと管理のユーザーインターフェイスのみを目的としているとています。
  • リアルタイムの更新が必要な場合は、ポーリングを使用する必要があります。
  • 非表示のAPIに依存: ActivityManager.RunningAppProcessInfo.processState
  • この実装は、アプリスイッチャーのアクティビティを取得しません。

例(KNaitoのコードに基づく)

public class CurrentApplicationPackageRetriever {

    private final Context context;

    public CurrentApplicationPackageRetriever(Context context) {
        this.context = context;
    }

    public String get() {
        if (Build.VERSION.SDK_INT < 21)
            return getPreLollipop();
        else
            return getLollipop();
    }

    private String getPreLollipop() {
        @SuppressWarnings("deprecation")
        List<ActivityManager.RunningTaskInfo> tasks =
            activityManager().getRunningTasks(1);
        ActivityManager.RunningTaskInfo currentTask = tasks.get(0);
        ComponentName currentActivity = currentTask.topActivity;
        return currentActivity.getPackageName();
    }

    private String getLollipop() {
        final int PROCESS_STATE_TOP = 2;

        try {
            Field processStateField = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");

            List<ActivityManager.RunningAppProcessInfo> processes =
                activityManager().getRunningAppProcesses();
            for (ActivityManager.RunningAppProcessInfo process : processes) {
                if (
                    // Filters out most non-activity processes
                    process.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                    &&
                    // Filters out processes that are just being
                    // _used_ by the process with the activity
                    process.importanceReasonCode == 0
                ) {
                    int state = processStateField.getInt(process);

                    if (state == PROCESS_STATE_TOP) {
                        String[] processNameParts = process.processName.split(":");
                        String packageName = processNameParts[0];

                        /*
                         If multiple candidate processes can get here,
                         it's most likely that apps are being switched.
                         The first one provided by the OS seems to be
                         the one being switched to, so we stop here.
                         */
                        return packageName;
                    }
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        return null;
    }

    private ActivityManager activityManager() {
        return (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    }

}

マニフェスト

GET_TASKS権限を追加しAndroidManifest.xmlます。

<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.GET_TASKS" />

10
これはAndroid Mではもう機能しません。getRunningAppProcesses()がアプリケーションのパッケージのみを返すようになりました
Lior Iluz

1
APIレベル22(Android 5.1)でも機能しません。ビルドLPB23でテスト
sumitb.mdi

9

これをテストに使用しています。これはAPI> 19 ですが、アプリのアクティビティのみを対象としています。

@TargetApi(Build.VERSION_CODES.KITKAT)
public static Activity getRunningActivity() {
    try {
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Object activityThread = activityThreadClass.getMethod("currentActivityThread")
                .invoke(null);
        Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
        activitiesField.setAccessible(true);
        ArrayMap activities = (ArrayMap) activitiesField.get(activityThread);
        for (Object activityRecord : activities.values()) {
            Class activityRecordClass = activityRecord.getClass();
            Field pausedField = activityRecordClass.getDeclaredField("paused");
            pausedField.setAccessible(true);
            if (!pausedField.getBoolean(activityRecord)) {
                Field activityField = activityRecordClass.getDeclaredField("activity");
                activityField.setAccessible(true);
                return (Activity) activityField.get(activityRecord);
            }
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    throw new RuntimeException("Didn't find the running activity");
}

ArrayMapをMapに置き換えると、4.3でも機能します。以前のバージョンのAndroidではテストされていません。
Oliver Jonas

2

このコードは、API 21以降で使用します。これは機能し、他の回答と比較してより良い結果をもたらし、フォアグラウンドプロセスを完全に検出します。

if (Build.VERSION.SDK_INT >= 21) {
    String currentApp = null;
    UsageStatsManager usm = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);
    long time = System.currentTimeMillis();
    List<UsageStats> applist = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time);
    if (applist != null && applist.size() > 0) {
        SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
        for (UsageStats usageStats : applist) {
            mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);

        }
        if (mySortedMap != null && !mySortedMap.isEmpty()) {
            currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
        }
    }

この方法の結果が他の回答よりも優れていることについて詳しく説明していただけますか?
Sam

これは通知メニューでも機能しますか?電話やメッセージを受け取った場合の問題に直面しています。UsageStatsManagerから、アプリではなくsystemUiのパッケージ名が表示されます。
kukroid 2019年

@kukroid、別の質問を投稿し、ソースコード、デバイス情報、使用しているメッセージングと電話アプリを含めます。
Sam

0

これがうまくいく私の答えです...

この方法で現在のアクティビティを取得できるはずです...多くのフラグメントを持ついくつかのアクティビティでアプリを構築し、現在のアクティビティが何であるかを追跡したい場合は、多くの作業が必要になります。私のシナリオは、複数のフラグメントを持つ1つのアクティビティがあることでした。そのため、グローバル変数の現在の状態をすべて保存できるアプリケーションオブジェクトを通じて現在のアクティビティを追跡できます。

ここに方法があります。アクティビティを開始すると、Application.setCurrentActivity(getIntent());によってそのアクティビティを保存します。このアプリケーションはそれを保存します。サービスクラスでは、Intent currentIntent = Application.getCurrentActivity();のように簡単に実行できます。getApplication()。startActivity(currentIntent);


2
これは、サービスがアクティビティと同じプロセスで実行されることを前提としていますよね?
helleye 2015年

0

それが愚かな答えかどうかはわかりませんが、アクティビティのonCreate()を入力するたびに共有設定にフラグを保存することでこの問題を解決し、共有された設定の値を使用して、それがフォアグラウンドアクティビティであることを確認しました。


1
それを行うネイティブな方法を探しています
IgniteCoders 2017年

-3

つい最近これについて知りました。次のようにAPIを使用:

  • minSdkVersion 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity(context)

これが役に立てば幸いです。


1
私の希望は上がっていた。おそらくこれが答えられたときに存在したかもしれませんが、現時点ではクラス(developer.android.com/reference/android/app/ActivityManager)にgetCurrentActivity()関数はありません。この関数が存在するのはおかしい: 。ActivityManagerActivityManager.isUserAMonkey()
ByteSlinger 2018年

1
これは、サルのテストエージェントを使用してアクティビティをテストしているかどうかを検出します。変な名前が、それはテストのために非常に便利かもしれません(と、他の人が笑うobvsを作る。) developer.android.com/studio/test/monkeyrunner
ケン・コーリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.