web-dev-qa-db-ja.com

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

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

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

166
George

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

あなたは「現在実行中のアクティビティ」を所有することはできません。

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

  1. ブロードキャストIntentをアクティビティに送信します - これはサンプルプロジェクトです このパターンを実証する
  2. サービスが呼び出すPendingIntentをアクティビティに提供させる(例:createPendingResult()を介して)
  3. アクティビティにコールバックまたはリスナオブジェクトをbindService()経由でサービスに登録させ、サービスにそのコールバック/リスナオブジェクトのイベントメソッドを呼び出させる
  4. バックアップとして優先順位の低いIntentを使用して、順序付きブロードキャストのBroadcastReceiverをアクティビティに送信します(アクティビティが画面に表示されていない場合はNotificationを上げる) - ブログの投稿です このパターンの詳細
81
CommonsWare

これはアクティビティマネージャを使用してそれを行うための良い方法です。基本的にはアクティビティマネージャから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"/>
137
Nelson Ramirez

警告: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]ボタンを押すことはできません。

101
Sam

それによってすることができます:

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

    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()を呼び出すことができます - それはあなたに現在の目に見えるActivityを与えます(または、activityが目に見えないときはnull)。 activeActivity.getClass().getSimpleName()を呼び出すことでアクティビティの名前を取得できます。

18
Vit Veres

私たちのチームが満足するような解決策を見つけることができなかったので、私たちは自分のチームを立ち上げました。 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
            }
        });
    }
}

次に、MyApplicationに対してContextProviderのインスタンスを返すようにDIコンテナを設定します。

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

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

13
Muxa

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_TASKSAndroidManifest.xml 権限を追加します。

<!--suppress DeprecatedClassUsageInspection -->
<uses-permission Android:name="Android.permission.GET_TASKS" />
12
Sam

私は自分のテストにこれを使っています。ただし、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");
}
9
espinchi

このコードは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();
        }
    }
1
Manish Godhani

これは私の答えです。

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

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

0
John3328

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

0
Vlad Vladescu