回答:
サービスから現在実行中のアクティビティへの参照を取得するネイティブのAndroid方法はありますか?
「現在実行中のアクティビティ」を所有していない可能性があります。
バックグラウンドでサービスを実行していますが、(サービスで)イベントが発生したときに現在のアクティビティを更新したいと思います。それを行う簡単な方法はありますか(私が上で提案したもののように)?
Intent
アクティビティにブロードキャストを送信します - これは、このパターンを示すサンプルプロジェクトですPendingIntent
(たとえばを介してcreatePendingResult()
)提供させるbindService()
にそのコールバック/リスナーオブジェクトのイベントメソッドを呼び出させるIntent
れたブロードキャストを、優先度の低いBroadcastReceiver
バックアップとして(Notification
アクティビティが画面上にない場合に発生させるために)アクティビティに送信します- このパターンの詳細が記載されたブログ投稿がありますinstanceof
チェックの大規模なifブロック、または共通のスーパークラスまたはインターフェースを共有するためのアクティビティのリファクタリングなしでは、サービスはそれらを使って何もできません。また、アクティビティをリファクタリングする場合は、フレームワークによりよく適合し、アクティブなアクティビティがないなど、より多くのシナリオをカバーする方法で行うこともできます。#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
アプリが画面にオーバーレイを配置した場合、[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]ボタンを押すことができないことに注意してください。
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)にキャストします。あなたが呼び出すことができるよりapp.getActiveActivity()
-それはあなたに現在表示されているアクティビティを与えます(またはアクティビティが表示されていない場合はnull)。を呼び出すことでアクティビティの名前を取得できますactiveActivity.getClass().getSimpleName()
onActivityResumed()
、onActivityPaused()
としgetActiveActivity()
、これまでそうならば、それはカレする方法を参照してください。
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();
}
}
これがうまくいく私の答えです...
この方法で現在のアクティビティを取得できるはずです...多くのフラグメントを持ついくつかのアクティビティでアプリを構築し、現在のアクティビティが何であるかを追跡したい場合は、多くの作業が必要になります。私のシナリオは、複数のフラグメントを持つ1つのアクティビティがあることでした。そのため、グローバル変数の現在の状態をすべて保存できるアプリケーションオブジェクトを通じて現在のアクティビティを追跡できます。
ここに方法があります。アクティビティを開始すると、Application.setCurrentActivity(getIntent());によってそのアクティビティを保存します。このアプリケーションはそれを保存します。サービスクラスでは、Intent currentIntent = Application.getCurrentActivity();のように簡単に実行できます。getApplication()。startActivity(currentIntent);
それが愚かな答えかどうかはわかりませんが、アクティビティのonCreate()を入力するたびに共有設定にフラグを保存することでこの問題を解決し、共有された設定の値を使用して、それがフォアグラウンドアクティビティであることを確認しました。
つい最近これについて知りました。次のようにAPIを使用:
targetSdkVersion 26
ActivityManager.getCurrentActivity(context)
これが役に立てば幸いです。
getCurrentActivity()
関数はありません。この関数が存在するのはおかしい: 。ActivityManager
ActivityManager.isUserAMonkey()