Android: How can I get the current foreground activity (from a service)?

前端 未结 12 1781
北恋
北恋 2020-11-22 07:53

Is there a native android way to get a reference to the currently running Activity from a service?

I have a service running on the background, and I would like to up

相关标签:
12条回答
  • 2020-11-22 08:16

    Update: this no longer works with other apps' activities as of Android 5.0


    Here's a good way to do it using the activity manager. You basically get the runningTasks from the activity manager. It will always return the currently active task first. From there you can get the topActivity.

    Example here

    There's an easy way of getting a list of running tasks from the ActivityManager service. You can request a maximum number of tasks running on the phone, and by default, the currently active task is returned first.

    Once you have that you can get a ComponentName object by requesting the topActivity from your list.

    Here's an example.

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

    You will need the following permission on your manifest:

    <uses-permission android:name="android.permission.GET_TASKS"/>
    
    0 讨论(0)
  • 2020-11-22 08:16

    Warning: Google Play violation

    Google has threatened to remove apps from the Play Store if they use accessibility services for non-accessibility purposes. However, this is reportedly being reconsidered.


    Use an AccessibilityService

    • You can detect the currently active window by using an AccessibilityService.
    • In the onAccessibilityEvent callback, check for the TYPE_WINDOW_STATE_CHANGED event type to determine when the current window changes.
    • Check if the window is an activity by calling PackageManager.getActivityInfo().

    Benefits

    • Tested and working in Android 2.2 (API 8) through Android 7.1 (API 25).
    • Doesn't require polling.
    • Doesn't require the GET_TASKS permission.

    Disadvantages

    • Each user must enable the service in Android's accessibility settings.
    • This isn't 100% reliable. Occasionally the events come in out-of-order.
    • The service is always running.
    • When a user tries to enable the AccessibilityService, they can't press the OK button if an app has placed an overlay on the screen. Some apps that do this are Velis Auto Brightness and Lux. This can be confusing because the user might not know why they can't press the button or how to work around it.
    • The AccessibilityService won't know the current activity until the first change of activity.

    Example

    Service

    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

    Merge this into your manifest:

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

    Service Info

    Put this in 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"/>
    

    Enabling the Service

    Each user of the app will need to explicitly enable the AccessibilityService in order for it to be used. See this StackOverflow answer for how to do this.

    Note that the user won't be able to press the OK button when trying to enable the accessibility service if an app has placed an overlay on the screen, such as Velis Auto Brightness or Lux.

    0 讨论(0)
  • 2020-11-22 08:17

    I don't know if it's a stupid answer, but resolved this problem by storing a flag in shared preferences every time I entered onCreate() of any activity, then I used the value from shered preferences to find out what it's the foreground activity.

    0 讨论(0)
  • 2020-11-22 08:19

    I'm using this for my tests. It's API > 19, and only for activities of your app, though.

    @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");
    }
    
    0 讨论(0)
  • 2020-11-22 08:21

    Here's what I suggest and what has worked for me. In your application class, implement an Application.ActivityLifeCycleCallbacks listener and set a variable in your application class. Then query the variable as needed.

    class YourApplication: Application.ActivityLifeCycleCallbacks {
        
        var currentActivity: Activity? = null
    
        fun onCreate() {
            registerActivityLifecycleCallbacks(this)
        }
    
        ...
        
        override fun onActivityResumed(activity: Activity) {
            currentActivity = activity
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:22

    It can be done by:

    1. Implement your own application class, register for ActivityLifecycleCallbacks - this way you can see what is going on with our app. On every on resume the callback assigns the current visible activity on the screen and on pause it removes the assignment. It uses method registerActivityLifecycleCallbacks() which was added in 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. In your service call getApplication() and cast it to your app class name (App in this case). Than you can call app.getActiveActivity() - that will give you a current visible Activity (or null when no activity is visible). You can get the name of the Activity by calling activeActivity.getClass().getSimpleName()

    0 讨论(0)
提交回复
热议问题