programing

Android:서비스에서 현재 전경 활동을 얻으려면 어떻게 해야 합니까?

codeshow 2023. 8. 5. 11:05
반응형

Android:서비스에서 현재 전경 활동을 얻으려면 어떻게 해야 합니까?

서비스에서 현재 실행 중인 활동에 대한 참조를 얻을 수 있는 네이티브 안드로이드 방법이 있습니까?

백그라운드에서 실행 중인 서비스가 있으며, (서비스에서) 이벤트가 발생할 때 현재 활동을 업데이트하려고 합니다.(위에서 제안한 것처럼) 그렇게 하는 쉬운 방법이 있습니까?

업데이트: Android 5.0 이후의 다른 앱 활동에서는 더 이상 작동하지 않습니다.


활동 관리자를 사용하여 이 작업을 수행하는 좋은 방법은 다음과 같습니다.기본적으로 작업 관리자로부터 실행 중인 작업을 받습니다.항상 현재 활성 작업을 먼저 반환합니다.여기에서 상위 활동을 얻을 수 있습니다.

여기 예

Activity Manager 서비스에서 실행 중인 작업 목록을 쉽게 가져올 수 있습니다.전화기에서 실행 중인 최대 작업 수를 요청할 수 있으며, 기본적으로 현재 활성 작업이 먼저 반환됩니다.

목록에서 상위 활동을 요청하여 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"/>

경고: Google Play 위반

구글은 앱이 접근성이 없는 목적으로 접근성 서비스를 사용할 경우 플레이스토어에서 앱을 제거하겠다고 위협했습니다.하지만 이는 재고 중인 것으로 알려졌습니다.


an을 사용

  • 를 사용하여 현재 활성 창을 탐지할 수 있습니다.
  • 콜백에서 이벤트 유형을 확인하여 현재 창이 변경되는 시기를 확인합니다.
  • 호출하여 창이 활동인지 확인합니다.

혜택들

  • Android 7.1(API 25)을 통해 Android 2.2(API 8)에서 테스트 및 작동합니다.
  • 폴링이 필요하지 않습니다.
  • 권한이 필요하지 않습니다.

단점들

  • 각 사용자는 Android의 내게 필요한 옵션 설정에서 서비스를 활성화해야 합니다.
  • 이것은 100% 신뢰할 수 없습니다.때때로 그 행사들은 순서가 뒤바뀌기도 합니다.
  • 서비스가 항상 실행 중입니다.
  • 프로그램이 화면에 오버레이를 배치한 경우 사용자가 를 활성화하려고 할 때 확인 버튼을 누를 수 없습니다.이를 수행하는 일부 앱에는 벨리스 자동 밝기 및 럭스가 있습니다.사용자가 버튼을 누를 수 없는 이유나 해결 방법을 모를 수 있기 때문에 혼란스러울 수 있습니다.
  • 번째 활동이 변경될 때까지 현재 활동을 알 수 없습니다.

서비스

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() {}
}

Android Manifest.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"/>

서비스 활성화

앱을 사용하려면 앱의 각 사용자가 를 명시적으로 사용하도록 설정해야 합니다.이 작업을 수행하는 방법은 스택 오버플로 답변을 참조하십시오.

Velis Auto Brightness(벨리스 자동 밝기) 또는 Lux(룩스)와 같이 앱이 화면에 오버레이를 배치한 경우 사용자가 내게 필요한 옵션 서비스를 활성화하려고 할 때 확인 버튼을 누를 수 없습니다.

서비스에서 현재 실행 중인 활동에 대한 참조를 얻을 수 있는 네이티브 안드로이드 방법이 있습니까?

현재 실행 중인 활동을 소유할 수 없습니다.

백그라운드에서 실행 중인 서비스가 있으며, (서비스에서) 이벤트가 발생할 때 현재 활동을 업데이트하려고 합니다.(위에서 제안한 것처럼) 그렇게 하는 쉬운 방법이 있습니까?

  1. Intent활동에 -- 여기 이 패턴을 보여주는 샘플 프로젝트가 있습니다.
  2. 에 활을동제합니다공다.PendingIntent 경유: 경유)createPendingResult())
  3. .bindService()에서 해당 콜백 객체에 대한 .
  4. 된 방송 문주로스트전송캐드브Intent 를 가진BroadcastReceiver (다우키▁a)▁())Notification활동이 화면에 표시되지 않는 경우) -- 여기 이 패턴에 대한 자세한 내용이 있는 블로그 게시물이 있습니다.

이 작업은 다음을 통해 수행할 수 있습니다.

  1. 자신만의 애플리케이션 클래스를 구현하고, 활동 라이프사이클 콜백에 등록합니다. 이렇게 하면 우리 앱에서 무슨 일이 일어나고 있는지 알 수 있습니다.재개할 때마다 콜백은 화면에 표시되는 현재 활동을 할당하고 일시 중지하면 할당을 제거합니다.은 방법을 사용합니다.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.getActiveActivity()현재 표시되는 활동(또는 활동이 표시되지 않는 경우 null)을 제공합니다.은 전를걸활이알수있다습니름을동으로 전화하면 알 수.activeActivity.getClass().getSimpleName()

저는 우리 팀이 만족할 만한 해결책을 찾지 못해서 각자 해결책을 제시했습니다.우리는 사용합니다.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 예: 예: 예: 예를 들어.

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

하십시오.getCurrent()위의 코드에서 누락되었습니다.애플리케이션 생성자에서 설정한 정적 변수일 뿐입니다.)

사용

현재 활동을 포함하는 응용프로그램만 알고 싶은 경우 를 사용하여 확인할 수 있습니다.사용할 수 있는 기술은 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);
    }

}

매니페스트

사용 권한 추가AndroidManifest.xml:

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

저는 이것을 시험에 사용하고 있습니다.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");
}

여기 제가 제안한 것과 저에게 효과가 있었던 것이 있습니다. 프로그램 애케이션구현다니합다음을서를 합니다.Application.ActivityLifeCycleCallbacks응용 프로그램 클래스에서 수신기를 사용하고 변수를 설정합니다.그런 다음 필요에 따라 변수를 쿼리합니다.

class YourApplication: Application.ActivityLifeCycleCallbacks {
    
    var currentActivity: Activity? = null

    fun onCreate() {
        registerActivityLifecycleCallbacks(this)
    }

    ...
    
    override fun onActivityResumed(activity: Activity) {
        currentActivity = activity
    }
}

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

저는 그 아이디어가 마음에 듭니다.Application.ActivityLifecycleCallbacks하지만 것은 수 .

  • 앱에 여러 개의 활동이 있을 수 있습니다.
  • 일부 활동이 파괴될 수 있습니다.
  • 일부 활동이 백그라운드로 이동할 수 있습니다.

이러한 모든 경우를 처리하려면 각 활동 수명 주기를 추적해야 합니다.이것이 바로 제가 아래의 해결책으로 한 일입니다.

이 하나의 통화인 ▁of▁▁a▁single▁it▁intoates▁consolid다▁all니됩이통으로 통합됩니다.getTopForegroundActivity() 위 맨위전활반환니다합동을경▁the를 반환합니다.null스택에 활동이 없거나 활동이 포그라운드에 없는 경우.

사용.

public class MyApp extends Application {

    private ActivityTracker activityTracker;

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

        activityTracker = new ActivityTracker();
        registerActivityLifecycleCallbacks(activityTracker);
        
        ...

        Activity activity = activityTracker.getTopForegroundActivity();
        if(activity != null) {
            // Do something
        }
    }
}

원천

public class ActivityTracker implements Application.ActivityLifecycleCallbacks {
    private final Map<Activity, ActivityData> activities = new HashMap<>();


    public Activity getTopForegroundActivity() {
        if (activities.isEmpty()) {
            return null;
        }
        ArrayList<ActivityData> list = new ArrayList<>(activities.values());
        Collections.sort(list, (o1, o2) -> {
            int compare = Long.compare(o2.started, o1.started);
            return compare != 0 ? compare : Long.compare(o2.resumed, o1.resumed);
        });

        ActivityData topActivity = list.get(0);
        return topActivity.started != -1 ? topActivity.activity : null;
    }


    @Override
    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
        activities.put(activity, new ActivityData(activity));
    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.started = System.currentTimeMillis();
        }
    }

    @Override
    public void onActivityResumed(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.resumed = System.currentTimeMillis();
        }
    }

    @Override
    public void onActivityPaused(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.resumed = -1;
        }
    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {
        ActivityData activityData = activities.get(activity);
        if (activityData != null) {
            activityData.started = -1;
        }
    }

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

    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {
        activities.remove(activity);
    }

    private static class ActivityData {

        public final Activity activity;
        public long started;
        public long resumed;

        private ActivityData(Activity activity) {
            this.activity = activity;
        }
    }
}

바보 같은 대답인지는 모르겠지만, 어떤 활동의 Create()에 들어갈 때마다 공유 기본 설정에 플래그를 저장하여 이 문제를 해결한 다음, 공유 기본 설정의 값을 사용하여 전경 활동이 무엇인지 알아냈습니다.

여기 내 대답이 잘 작동합니다...

이 방법으로 현재 활동을 가져올 수 있어야 합니다.많은 조각이 있는 몇 가지 활동으로 앱을 구성하고 현재 활동을 추적하려면 많은 작업이 필요합니다.제 시나리오는 여러 조각으로 구성된 하나의 활동이 있다는 것이었습니다.따라서 전역 변수의 현재 상태를 모두 저장할 수 있는 응용 프로그램 개체를 통해 현재 활동을 추적할 수 있습니다.

여기에 방법이 있습니다.활동을 시작하면 해당 활동을 응용프로그램별로 저장합니다.현재 활동 설정(get)의도();이 응용프로그램이 저장합니다.서비스 클래스에서는 Intent current와 같은 작업을 수행할 수 있습니다.Intent = Application.getCurrentActivity(); getApplication().startActivity(현재)의도);

최근에 이것에 대해 알게 되었습니다.아피스 포함:

  • minSdk 버전 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity(컨텍스트)

이것이 쓸모가 있기를 바랍니다.

언급URL : https://stackoverflow.com/questions/3873659/android-how-can-i-get-the-current-foreground-activity-from-a-service

반응형