サービスから現在実行中のアクティビティへの参照を取得するためのネイティブのAndroidの方法はありますか?
バックグラウンドでサービスを実行しています。イベントが発生したときに(サービス内で)現在のアクティビティを更新します。それを行う簡単な方法はありますか(上で提案したもののように)?
サービスから現在実行中のアクティビティへの参照を取得するためのネイティブのAndroidの方法はありますか?
あなたは「現在実行中のアクティビティ」を所有することはできません。
バックグラウンドでサービスを実行しています。イベントが発生したときに(サービス内で)現在のアクティビティを更新します。それを行う簡単な方法はありますか(上で提案したもののように)?
Intent
をアクティビティに送信します - これはサンプルプロジェクトです このパターンを実証するPendingIntent
をアクティビティに提供させる(例:createPendingResult()
を介して)bindService()
経由でサービスに登録させ、サービスにそのコールバック/リスナオブジェクトのイベントメソッドを呼び出させる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ストアからアプリを削除することを推奨します。しかし、 これは再考されていると伝えられています 。
AccessibilityService
を使うAccessibilityService
を使って現在アクティブなウィンドウを検出できます。onAccessibilityEvent
コールバックで、 TYPE_WINDOW_STATE_CHANGED
イベントタイプ を確認して、現在のウィンドウがいつ変わるかを判断します。PackageManager.getActivityInfo()
を呼び出して、ウィンドウがアクティビティかどうかを確認します。GET_TASKS
権限を必要としません。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() {}
}
これをマニフェストにマージします。
<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]ボタンを押すことはできません。
それによってすることができます:
独自のアプリケーションクラスを実装し、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;
}
}
あなたのサービスでgetApplication()
を呼び出して、あなたのアプリクラス名(この場合はApp)にキャストしてください。あなたはapp.getActiveActivity()
を呼び出すことができます - それはあなたに現在の目に見えるActivityを与えます(または、activityが目に見えないときは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
のインスタンスを返すようにDIコンテナを設定します。
public class ApplicationModule extends AbstractModule {
@Provides
ContextProvider provideMainActivity() {
return MyApplication.getCurrent();
}
}
(getCurrent()
の実装は上記のコードから省略されています。これはアプリケーションコンストラクタから設定される単なる静的変数です)
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();
}
}
これは私の答えです。
このようにして現在のアクティビティを取得できるはずです...多数のフラグメントを含む少数のアクティビティでアプリを構成していて、現在のアクティビティが何であるかを追跡したい場合は、多くの作業が必要になります。私の先見性は、私は複数のフラグメントを持つ1つのアクティビティを持っているということでした。そのため、グローバル変数の現在の状態をすべて格納できるアプリケーションオブジェクトを通じて、現在のアクティビティを追跡できます。
これが方法です。アクティビティを開始すると、そのアクティビティはApplication.setCurrentActivity(getIntent())によって保存されます。このアプリケーションはそれを保存します。サービスクラスでは、単にIntent currentIntent = Application.getCurrentActivity();のようにすることができます。 getApplication()。startActivity(currentIntent);
愚かな答えかどうかはわかりませんが、アクティビティのonCreate()を入力するたびに共有設定にフラグを格納することでこの問題を解決し、次にshered設定の値を使用してフォアグラウンドのアクティビティを特定しました。