답변:
서비스에서 현재 실행중인 활동에 대한 참조를 얻는 기본 안드로이드 방법이 있습니까?
"현재 실행중인 활동"을 소유 할 수 없습니다.
백그라운드에서 서비스를 실행하고 있으며 서비스에서 이벤트가 발생할 때 현재 활동을 업데이트하고 싶습니다. (위에서 제안한 것과 같은) 쉬운 방법이 있습니까?
Intent
을 활동으로 보내기 - 이 패턴을 보여주는 샘플 프로젝트 는 다음과 같습니다.PendingIntent
(예 :을 통해 createPendingResult()
) 제공하도록하십시오bindService()
하고 서비스가 해당 콜백 / 리스너 객체에서 이벤트 메소드를 호출하도록합니다.Intent
으로 우선 순위가 낮은 주문형 브로드 캐스트 를 활동에 전송 하십시오 (활동이 화면에없는 경우 BroadcastReceiver
증가시키기 위해 Notification
) .이 패턴에 대해 더 많은 블로그 게시물 이 있습니다.instanceof
점검이나 공통 수퍼 클래스 또는 인터페이스를 공유하기 위해 활동을 리팩토링 하지 않고는 서비스로 수행 할 수있는 작업이 없습니다 . 또한 활동을 리팩토링하려면 프레임 워크에 더 적합하고 활동이없는 것과 같은 더 많은 시나리오를 다루는 방식으로 수행 할 수도 있습니다. # 4는 아마도 가장 작고 가장 유연 할 것입니다.
업데이트 :이 더 이상 작동하지 않습니다 와 함께 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"/>
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.
Google은 접근성이 아닌 목적으로 접근성 서비스를 사용하는 경우 Play 스토어에서 앱을 삭제 하겠다고 위협 했습니다. 그러나 이것은보고 된 바있다 .
AccessibilityService
AccessibilityService
.onAccessibilityEvent
콜백의 확인 이벤트 유형TYPE_WINDOW_STATE_CHANGED
현재 윈도우가 변경 될 때 결정합니다.PackageManager.getActivityInfo()
.GET_TASKS
권한이 필요하지 않습니다 .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() {}
}
이것을 매니페스트에 병합하십시오.
<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와 같은 화면에 오버레이를 배치 한 경우 사용자는 접근성 서비스를 활성화하려고 할 때 확인 버튼을 누를 수 없습니다.
settingsActivity
를 지정 your.app.ServiceSettingsActivity
했으므로 접근성 서비스에 대한 설정 활동으로 변경해야합니다. 어쨌든 설정 활동은 선택 사항이라고 생각하므로 더 간단하게하기 위해 답변에서 해당 부분을 제거했습니다.
onAccessibilityEvent
이벤트를받지 못하지만 접근성 서비스를 비활성화했다가 다시 활성화하면 서비스가 다시 활성화되고 onAccessibilityEvent
작동하기 시작합니다.
다음을 수행 할 수 있습니다.
자체 애플리케이션 클래스를 구현하고 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;
}
}
서비스 요청시 getApplication()
앱 클래스 이름 (이 경우 앱)으로 캐스트하십시오. 당신이 호출 할 수있는 것보다 app.getActiveActivity()
-그것은 당신에게 현재 보이는 활동을 줄 것입니다 (또는 활동이 보이지 않으면 null입니다). 당신은 전화하여 활동의 이름을 얻을 수 있습니다activeActivity.getClass().getSimpleName()
onActivityResumed()
, onActivityPaused()
하고 getActiveActivity()
지금까지 그렇다면, 그것은 callet하는 방법을 참조하십시오.
activity
하고 activeActivity
할당하기 전에 동일 activeActivity
로 null
인해 다양한 활동의 라이프 사이클 방식의 혼합 된 호출 순서를 피하기 오류를 위해.
우리 팀이 만족할만한 해결책을 찾을 수 없었으므로 우리는 우리 자신을 굴 렸습니다. 우리는 사용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()
위의 코드에서는 구현 이 생략되었습니다. 응용 프로그램 생성자에서 설정 한 정적 변수 일뿐입니다.)
activity
하고 currentActivity
할당하기 전에 동일 currentActivity
로 null
인해 다양한 활동의 라이프 사이클 방식의 혼합 된 호출 순서를 피하기 오류를 위해.
ActivityManager
현재 활동이 포함 된 응용 프로그램 만 알고 싶다면을 사용하면 ActivityManager
됩니다. 사용할 수있는 기술은 Android 버전에 따라 다릅니다.
ActivityManager.getRunningTasks
( 예 )ActivityManager.getRunningAppProcesses
( 예 )ActivityManager.RunningAppProcessInfo.processState
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();
}
}
잘 작동하는 내 대답은 다음과 같습니다.
이런 식으로 현재 활동을 얻을 수 있어야합니다 ... 조각이 많은 활동을 몇 가지로하여 앱을 구성하고 현재 활동을 추적하려는 경우 많은 작업이 필요합니다. 나의 상원은 여러 조각으로 하나의 활동을 가지고 있다는 것이었다. 따라서 전역 변수의 현재 상태를 모두 저장할 수있는 응용 프로그램 개체를 통해 현재 활동을 추적 할 수 있습니다.
방법이 있습니다. 활동을 시작하면 Application.setCurrentActivity (getIntent ()); 이 응용 프로그램은 그것을 저장합니다. 서비스 클래스에서 Intent currentIntent = Application.getCurrentActivity (); getApplication (). startActivity (currentIntent);
나는 그것이 멍청한 대답인지 모르겠지만, 활동의 onCreate ()를 입력 할 때마다 공유 환경 설정에 플래그를 저장 하여이 문제를 해결했습니다.
최근에 이것에 대해 알게되었습니다. API를 다음과 같이 사용하십시오.
targetSdk 버전 26
ActivityManager.getCurrentActivity (컨텍스트)
이것이 어떤 용도로 사용되기를 바랍니다.
getCurrentActivity()
기능 이 없습니다 ActivityManager
( developer.android.com/reference/android/app/ActivityManager ). 이 기능이 존재한다는 것은 재미있다 : ActivityManager.isUserAMonkey()
.