Programing

Android : 서비스에서 현재 포 그라운드 활동을 얻으려면 어떻게해야합니까?

lottogame 2020. 5. 24. 10:46
반응형

Android : 서비스에서 현재 포 그라운드 활동을 얻으려면 어떻게해야합니까?


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

백그라운드에서 서비스를 실행하고 있으며 서비스에서 이벤트가 발생할 때 현재 활동을 업데이트하고 싶습니다. (위에서 제안한 것과 같은) 쉬운 방법이 있습니까?


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

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

백그라운드에서 서비스를 실행하고 있으며 서비스에서 이벤트가 발생할 때 현재 활동을 업데이트하고 싶습니다. (위에서 제안한 것과 같은) 쉬운 방법이 있습니까?

  1. 방송 Intent을 활동으로 보내기 - 이 패턴을 보여주는 샘플 프로젝트 는 다음과 같습니다.
  2. 활동 이 서비스가 호출 하는 PendingIntent(예 :을 통해 createPendingResult()) 제공하도록하십시오
  3. 활동이를 통해 서비스에 콜백 또는 리스너 객체를 등록하도록 bindService()하고 서비스가 해당 콜백 / 리스너 객체에서 이벤트 메소드를 호출하도록합니다.
  4. 백업 Intent으로 우선 순위가 낮은 주문형 브로드 캐스트 를 활동에 전송 하십시오 (활동이 화면에없는 경우 BroadcastReceiver증가시키기 위해 Notification) .이 패턴에 대해 더 많은 블로그 게시물 이 있습니다.

활동 관리자를 사용하여이를 수행하는 좋은 방법이 있습니다. 기본적으로 활동 관리자에서 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"/>

경고 : Google Play 위반

Google은 접근성 이외의 목적으로 접근성 서비스를 사용하는 경우 Play 스토어에서 앱을 삭제 하겠다고 위협 했습니다. 그러나 이것은보고 된 바있다 .


사용 AccessibilityService

혜택

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

단점

  • 각 사용자는 Android의 접근성 설정에서 서비스를 활성화해야합니다.
  • 이것은 100 % 신뢰할 수 없습니다. 때때로 이벤트는 순서가 맞지 않습니다.
  • 서비스는 항상 실행 중입니다.
  • 사용자가을 활성화하려고 AccessibilityService하면 앱이 화면에 오버레이를 배치 한 경우 확인 버튼을 누를 수 없습니다. 이를 수행하는 일부 앱은 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와 같은 화면에 오버레이를 배치 한 경우 사용자는 접근성 서비스를 활성화하려고 할 때 확인 버튼을 누를 수 없습니다.


다음을 수행 할 수 있습니다.

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

그런 다음 반환 인스턴스에 DI 컨테이너를 구성 MyApplication하기위한 ContextProvider, 예를 들어,

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

( getCurrent()위의 코드에서는 구현 이 생략되었습니다. 응용 프로그램 생성자에서 설정 한 정적 변수 일뿐입니다.)


사용하다 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" />

나는 이것을 테스트에 사용하고 있습니다. 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");
}

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

잘 작동하는 내 대답은 다음과 같습니다.

You should be able to get current Activity in this way... If you structure your app with a few Activities with many fragments and you want to keep track of what is your current Activity, it would take a lot of work though. My senario was I do have one Activity with multiple Fragments. So I can keep track of Current Activity through Application Object, which can store all of the current state of Global variables.

Here is a way. When you start your Activity, you store that Activity by Application.setCurrentActivity(getIntent()); This Application will store it. On your service class, you can simply do like Intent currentIntent = Application.getCurrentActivity(); getApplication().startActivity(currentIntent);


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.


Just recently found out about this. With apis as:

  • minSdkVersion 19
  • targetSdkVersion 26

    ActivityManager.getCurrentActivity(context)

Hope this is of any use.

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

반응형