Xác định ứng dụng nền hiện tại từ một nhiệm vụ hoặc dịch vụ nền


112

Tôi muốn có một ứng dụng chạy ở chế độ nền, ứng dụng này có thể biết khi nào bất kỳ ứng dụng tích hợp nào (nhắn tin, danh bạ, v.v.) đang chạy.

Vì vậy, câu hỏi của tôi là:

  1. Tôi nên chạy ứng dụng của mình như thế nào trong nền.

  2. Làm thế nào để ứng dụng nền của tôi có thể biết ứng dụng hiện đang chạy ở nền trước là gì.

Phản hồi từ những người có kinh nghiệm sẽ được đánh giá cao.


Tôi không nghĩ rằng bạn đã đưa ra lời giải thích đầy đủ về những gì bạn đang cố gắng làm. Có gì ứng dụng nền của bạn cố gắng làm là? Nó có thể tương tác với người dùng theo những cách nào? Tại sao bạn cần biết ứng dụng nền trước hiện tại là gì? Vv
Charles Duffy


1
để phát hiện ứng dụng nền trước, bạn có thể sử dụng github.com/ricvalerio/foregroundappchecker
rvalerio

Câu trả lời:


104

Liên quan đến "2. Làm thế nào ứng dụng nền của tôi có thể biết ứng dụng hiện đang chạy ở nền trước là gì."

KHÔNG sử dụng getRunningAppProcesses()phương pháp này vì điều này trả về tất cả các loại rác hệ thống từ kinh nghiệm của tôi và bạn sẽ nhận được nhiều kết quả có RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Sử dụng getRunningTasks()thay thế

Đây là mã tôi sử dụng trong dịch vụ của mình để xác định ứng dụng nền hiện tại, nó thực sự dễ dàng:

ActivityManager am = (ActivityManager) AppService.this.getSystemService(ACTIVITY_SERVICE);
// The first in the list of RunningTasks is always the foreground task.
RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);

Đó là nó, sau đó bạn có thể dễ dàng truy cập chi tiết của ứng dụng / hoạt động nền trước:

String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();
PackageManager pm = AppService.this.getPackageManager();
PackageInfo foregroundAppPackageInfo = pm.getPackageInfo(foregroundTaskPackageName, 0);
String foregroundTaskAppName = foregroundAppPackageInfo.applicationInfo.loadLabel(pm).toString();

Điều này yêu cầu một sự cho phép bổ sung trong menifest hoạt động và hoạt động hoàn hảo.

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

1
Đây phải được đánh dấu là câu trả lời đúng. Quyền bắt buộc: android.permission.GET_TASKS là nhược điểm rất nhỏ duy nhất mà các phương pháp khác có thể tránh được.
brandall

3
CHỈNH SỬA Ở TRÊN: Lưu ý: phương pháp này chỉ nhằm gỡ lỗi và trình bày giao diện người dùng quản lý tác vụ. <- Chỉ phát hiện thấy điều đó trong tài liệu Java. Vì vậy, phương pháp này có thể thay đổi mà không cần thông báo trong tương lai.
brandall

47
Lưu ý: getRunningTasks()không được dùng nữa trong API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler

@dtyler, được rồi. Có phương pháp nào khác thay thế không? Mặc dù Google không muốn các ứng dụng của bên thứ ba có được thông tin nhạy cảm nhưng tôi có chìa khóa nền tảng của thiết bị. Có phương pháp nào khác làm tương tự nếu tôi có đặc quyền hệ thống không?
Yeung

Có một cách sử dụng "api thống kê sử dụng" được giới thiệu trong 21
KunalK

38

tôi đã phải tìm ra giải pháp phù hợp một cách khó khăn. mã bên dưới là một phần của cyanogenmod7 (tinh chỉnh máy tính bảng) và được thử nghiệm trên android 2.3.3 / Gingerbread.

phương pháp:

  • getForegroundApp - trả về ứng dụng nền trước.
  • getActivityForApp - trả về hoạt động của ứng dụng được tìm thấy.
  • isStillActive - xác định xem ứng dụng được tìm thấy trước đó có còn là ứng dụng đang hoạt động hay không.
  • isRunningService - một chức năng trợ giúp cho getForegroundApp

điều này hy vọng sẽ giải đáp được vấn đề này trong toàn bộ vấn đề (:

private RunningAppProcessInfo getForegroundApp() {
    RunningAppProcessInfo result=null, info=null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningAppProcessInfo> l = mActivityManager.getRunningAppProcesses();
    Iterator <RunningAppProcessInfo> i = l.iterator();
    while(i.hasNext()){
        info = i.next();
        if(info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                && !isRunningService(info.processName)){
            result=info;
            break;
        }
    }
    return result;
}

private ComponentName getActivityForApp(RunningAppProcessInfo target){
    ComponentName result=null;
    ActivityManager.RunningTaskInfo info;

    if(target==null)
        return null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <ActivityManager.RunningTaskInfo> l = mActivityManager.getRunningTasks(9999);
    Iterator <ActivityManager.RunningTaskInfo> i = l.iterator();

    while(i.hasNext()){
        info=i.next();
        if(info.baseActivity.getPackageName().equals(target.processName)){
            result=info.topActivity;
            break;
        }
    }

    return result;
}

private boolean isStillActive(RunningAppProcessInfo process, ComponentName activity)
{
    // activity can be null in cases, where one app starts another. for example, astro
    // starting rock player when a move file was clicked. we dont have an activity then,
    // but the package exits as soon as back is hit. so we can ignore the activity
    // in this case
    if(process==null)
        return false;

    RunningAppProcessInfo currentFg=getForegroundApp();
    ComponentName currentActivity=getActivityForApp(currentFg);

    if(currentFg!=null && currentFg.processName.equals(process.processName) &&
            (activity==null || currentActivity.compareTo(activity)==0))
        return true;

    Slog.i(TAG, "isStillActive returns false - CallerProcess: " + process.processName + " CurrentProcess: "
            + (currentFg==null ? "null" : currentFg.processName) + " CallerActivity:" + (activity==null ? "null" : activity.toString())
            + " CurrentActivity: " + (currentActivity==null ? "null" : currentActivity.toString()));
    return false;
}

private boolean isRunningService(String processname){
    if(processname==null || processname.isEmpty())
        return false;

    RunningServiceInfo service;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningServiceInfo> l = mActivityManager.getRunningServices(9999);
    Iterator <RunningServiceInfo> i = l.iterator();
    while(i.hasNext()){
        service = i.next();
        if(service.process.equals(processname))
            return true;
    }

    return false;
}

1
nếu bạn không phiền tôi hỏi, tại sao bạn cần các phương pháp để xem liệu nó có đang chạy một dịch vụ hay không, có còn hoạt động hay không và để có được hoạt động chỉ để tải ứng dụng nền trước?

2
xin lỗi, nhưng nó không hoạt động. Một số ứng dụng bị lọc mà không có lý do, tôi nghĩ đó là khi chúng chạy một số dịch vụ. Vì vậy, isRunningService đang loại bỏ chúng.
seb

15
Rất tiếc, getRunningTasks () đã không được dùng nữa kể từ Android L (API 20). Kể từ L, phương pháp này không còn khả dụng đối với các ứng dụng của bên thứ ba: việc giới thiệu các nội dung gần đây tập trung vào tài liệu có nghĩa là nó có thể làm rò rỉ thông tin người cho người gọi. Đối với khả năng tương thích ngược, nó vẫn sẽ trả về một tập hợp con nhỏ dữ liệu của nó: ít nhất là tác vụ của riêng người gọi và có thể là một số tác vụ khác như home được biết là không nhạy cảm.
Sam Lu

Có ai biết cách tiếp cận này tốt hơn cách làm đơn giản mActivityManager.getRunningTasks(1).get(0).topActivitykhông?
Sam

35

Hãy thử mã sau:

ActivityManager activityManager = (ActivityManager) newContext.getSystemService( Context.ACTIVITY_SERVICE );
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
for(RunningAppProcessInfo appProcess : appProcesses){
    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
        Log.i("Foreground App", appProcess.processName);
    }
}

Tên quy trình là tên gói của ứng dụng đang chạy ở nền trước. So sánh nó với tên gói ứng dụng của bạn. Nếu nó giống nhau thì ứng dụng của bạn đang chạy trên nền trước.

Tôi mong bạn trả lời câu hỏi này.


7
Từ phiên bản Android L trở đi, đây là giải pháp duy nhất hoạt động vì các phương pháp được sử dụng trong các giải pháp khác đã không còn được dùng nữa.
androidGuy

4
Tuy nhiên, điều này không làm việc trong trường hợp đó ứng dụng là trong foreground và màn hình khóa
Krit

Câu trả lời đúng sẽ được đánh dấu
A_rmas

1
Nó có cần sự cho phép bổ sung như câu trả lời được chấp nhận không?
wonsuc

1
Điều này không hoàn toàn chính xác. Tên của quy trình không phải lúc nào cũng giống với tên gói ứng dụng tương ứng.
Sam

25

Từ kẹo mút trở đi, điều này đã thay đổi. Vui lòng tìm mã bên dưới, trước khi người dùng đó phải đi tới Cài đặt -> Bảo mật -> (Cuộn xuống cuối cùng) Ứng dụng có quyền truy cập sử dụng -> Cấp quyền cho ứng dụng của chúng tôi

private void printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        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();
            }
        }
    } else {
        ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
        currentApp = tasks.get(0).processName;
    }

    Log.e(TAG, "Current App in foreground is: " + currentApp);
}

1
không thể đặt số liệu thống kê sử dụng này theo chương trình?
rubmz

@rubmz Bạn có giải pháp nào về vấn đề này không?
VVB

1
trong Doogee tôi với android 5.1 không có tùy chọn "Cho phép để ứng dụng của chúng tôi"
djdance

Điều này không sản xuất 100% kết quả chính xác ... nó có phù hợp hầu hết thời gian
CamHart

1
Thao tác này sẽ không hiển thị Ứng dụng cuối cùng ở phía trước, nhưng có thể là ứng dụng cuối cùng được "sử dụng" theo một cách nào đó - nếu bạn rời khỏi một hoạt động và chuyển sang một hoạt động khác, hãy kéo menu launcher từ phía trên và thả nó ra, bạn sẽ ở trong " hoạt động sai "cho đến khi bạn chuyển sang hoạt động khác. Không quan trọng nếu bạn cuộn màn hình hay không, nó báo cáo hoạt động sai cho đến khi bạn khởi chạy một cái khác.
Azurlake

9

Đối với các trường hợp khi chúng tôi cần kiểm tra từ dịch vụ / luồng nền của chính mình xem ứng dụng của chúng tôi có ở nền trước hay không. Đây là cách tôi triển khai nó và nó hoạt động tốt đối với tôi:

public class TestApplication extends Application implements Application.ActivityLifecycleCallbacks {

    public static WeakReference<Activity> foregroundActivityRef = null;

    @Override
    public void onActivityStarted(Activity activity) {
        foregroundActivityRef = new WeakReference<>(activity);
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (foregroundActivityRef != null && foregroundActivityRef.get() == activity) {
            foregroundActivityRef = null;
        }
    }

    // IMPLEMENT OTHER CALLBACK METHODS
}

Bây giờ để kiểm tra các lớp khác, liệu ứng dụng có ở nền trước hay không, chỉ cần gọi:

if(TestApplication.foregroundActivityRef!=null){
    // APP IS IN FOREGROUND!
    // We can also get the activity that is currently visible!
}

Cập nhật (như SHS đã chỉ ra ):

Đừng quên đăng ký các lệnh gọi lại trong onCreatephương thức của lớp Ứng dụng của bạn .

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}

2
Đó là những gì tôi đã tìm kiếm suốt hai giờ qua. Cảm ơn bạn! :)
waseefakhtar

1
Chúng ta cần gọi "registerActivityLifecycleCallbacks (this);" bên trong Ghi đè phương thức "onCreate ()" của lớp Ứng dụng (TestApplication). Thao tác này sẽ bắt đầu các lệnh gọi lại trong lớp ứng dụng của chúng tôi.
SHS

@SarthakMittal, Nếu một thiết bị chuyển sang chế độ chờ hoặc khi chúng tôi khóa nó, thì onActivityStopped () sẽ được gọi. Dựa trên mã của bạn, điều này sẽ cho biết rằng ứng dụng đang ở chế độ nền. Nhưng nó đang ở phía trước thực sự.
Ayaz Alifov

@AyazAlifov Nếu điện thoại ở chế độ chờ hoặc điện thoại bị khóa, tôi sẽ không coi nó là ở nền trước, nhưng một lần nữa, nó phụ thuộc vào sở thích.
Sarthak Mittal

8

Để xác định ứng dụng nền trước, bạn có thể sử dụng để phát hiện ứng dụng nền trước, bạn có thể sử dụng https://github.com/ricvalerio/foregroundappchecker . Nó sử dụng các phương pháp khác nhau tùy thuộc vào phiên bản Android của thiết bị.

Đối với dịch vụ, repo cũng cung cấp mã bạn cần cho nó. Về cơ bản, hãy để android studio tạo dịch vụ cho bạn, sau đó onCreate thêm đoạn mã sử dụng appChecker. Tuy nhiên, bạn sẽ cần phải yêu cầu sự cho phép.


hoàn hảo!! thử nghiệm trên android 7
Pratik Bể

Cám ơn rất nhiều. Đây là câu trả lời duy nhất giải quyết được vấn đề mà chúng tôi đang gặp phải với các thông báo của ứng dụng. Khi một thông báo bật lên, tất cả các câu trả lời khác sẽ trả lại tên gói của ứng dụng đã gửi thông báo. LollipopDetector của bạn đã giải quyết được vấn đề này. Một gợi ý: từ API 29, MOVE_TO_FOREGROUND không được dùng nữa trong UsageEvents.Event, vì vậy từ Android Q trở đi, người dùng nên sử dụng ACTIVITY_RESUMED. Sau đó, nó sẽ hoạt động như một lá bùa!
Jorn Rigter

8

Do getRunningTasks()không dùng nữa và getRunningAppProcesses()không đáng tin cậy, tôi đã đi đến quyết định kết hợp 2 phương pháp được đề cập trong StackOverflow:

   private boolean isAppInForeground(Context context)
    {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
        {
            ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
            ActivityManager.RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
            String foregroundTaskPackageName = foregroundTaskInfo.topActivity.getPackageName();

            return foregroundTaskPackageName.toLowerCase().equals(context.getPackageName().toLowerCase());
        }
        else
        {
            ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
            ActivityManager.getMyMemoryState(appProcessInfo);
            if (appProcessInfo.importance == IMPORTANCE_FOREGROUND || appProcessInfo.importance == IMPORTANCE_VISIBLE)
            {
                return true;
            }

            KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
            // App is foreground, but screen is locked, so show notification
            return km.inKeyguardRestrictedInputMode();
        }
    }

4
bạn cần đề cập để thêm vào tệp kê khai quyền không được dùng nữa <
use

1
android.permission.GET_TASKS - hiện bị cấm trong Cửa hàng Play
Duna


1

Điều này đã làm việc cho tôi. Nhưng nó chỉ cung cấp tên menu chính. Đó là nếu người dùng đã mở màn hình Cài đặt -> Bluetooth -> Tên thiết bị, RunningAppProcessInfo sẽ gọi nó là Cài đặt. Không thể đào sâu

ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE );
                PackageManager pm = context.getPackageManager();
                List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
                for(RunningAppProcessInfo appProcess : appProcesses) {              
                    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                        CharSequence c = pm.getApplicationLabel(pm.getApplicationInfo(appProcess.processName, PackageManager.GET_META_DATA));
                        Log.i("Foreground App", "package: " + appProcess.processName + " App: " + c.toString());
                    }               
                }

4
Tôi nghĩ rằng điều này chỉ hoạt động nếu ứng dụng của riêng bạn đang chạy ở nền trước. Nó không báo cáo bất cứ điều gì khi một số ứng dụng khác ở trên nền.
Alice Van Der Land

0

Làm điều gì đó như sau:

int showLimit = 20;

/* Get all Tasks available (with limit set). */
ActivityManager mgr = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> allTasks = mgr.getRunningTasks(showLimit);
/* Loop through all tasks returned. */
for (ActivityManager.RunningTaskInfo aTask : allTasks) 
{                  
    Log.i("MyApp", "Task: " + aTask.baseActivity.getClassName()); 
    if (aTask.baseActivity.getClassName().equals("com.android.email.activity.MessageList")) 
        running=true;
}

Google có thể sẽ từ chối một ứng dụng sử dụng ActivityManager.getRunningTasks (). Các tiểu bang tài liệu rằng nó là mục đích thù dev chỉ: developer.android.com/reference/android/app/... Xem nhận xét ban đầu: stackoverflow.com/questions/4414171/...
ForceMagic

3
@ForceMagic - Google không từ chối các ứng dụng chỉ vì sử dụng không đáng tin cậy các API ; xa hơn, Google không phải là kênh phân phối duy nhất cho các ứng dụng Android. Điều đó nói rằng, cần lưu ý rằng thông tin từ API này không đáng tin cậy.
Chris Stratton

@ChrisStratton Thật thú vị, nhưng bạn có thể đúng, mặc dù không khuyến khích sử dụng nó cho logic cốt lõi, họ sẽ không từ chối ứng dụng. Bạn có thể muốn cảnh báo Sky Kelsey từ liên kết bình luận.
ForceMagic

3
"getRunningTasks" làm việc cho api 21and ngoài nên nó có thể là ý tưởng tốt để tìm một cách khác để làm cho nó làm việc cho Lollipop (i bản thân mình havent figured này ra bản thân mình mặc dù :()
Amit

0

Trong kẹo mút trở lên:

Thêm vào tệp chính:

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

Và làm điều gì đó như sau:

if( mTaskId < 0 )
{
    List<AppTask> tasks = mActivityManager.getAppTasks(); 
    if( tasks.size() > 0 )
        mTaskId = tasks.get( 0 ).getTaskInfo().id;
}

4
Nó bị phản đối vào marshmallow
jinkal

0

Đây là cách tôi kiểm tra xem ứng dụng của mình có ở nền trước hay không. Lưu ý rằng tôi đang sử dụng AsyncTask theo đề xuất của tài liệu Android chính thức. '

`

    private class CheckIfForeground extends AsyncTask<Void, Void, Void> {
    @Override
    protected Void doInBackground(Void... voids) {

        ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                Log.i("Foreground App", appProcess.processName);

                if (mContext.getPackageName().equalsIgnoreCase(appProcess.processName)) {
                    Log.i(Constants.TAG, "foreground true:" + appProcess.processName);
                    foreground = true;
                    // close_app();
                }
            }
        }
        Log.d(Constants.TAG, "foreground value:" + foreground);
        if (foreground) {
            foreground = false;
            close_app();
            Log.i(Constants.TAG, "Close App and start Activity:");

        } else {
            //if not foreground
            close_app();
            foreground = false;
            Log.i(Constants.TAG, "Close App");

        }

        return null;
    }
}

và thực thi AsyncTask như thế này. new CheckIfForeground().execute();


Bạn đã có một liên kết cho điều này?
user1743524

0

Tôi đã kết hợp hai giải pháp trong một phương pháp và nó phù hợp với tôi cho API 24 và cho API 21. Những giải pháp khác tôi đã không thử nghiệm.

Mã trong Kotlin:

private fun isAppInForeground(context: Context): Boolean {
    val appProcessInfo = ActivityManager.RunningAppProcessInfo()
    ActivityManager.getMyMemoryState(appProcessInfo)
    if (appProcessInfo.importance == IMPORTANCE_FOREGROUND ||
            appProcessInfo.importance == IMPORTANCE_VISIBLE) {
        return true
    } else if (appProcessInfo.importance == IMPORTANCE_TOP_SLEEPING ||
            appProcessInfo.importance == IMPORTANCE_BACKGROUND) {
        return false
    }

    val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val foregroundTaskInfo = am.getRunningTasks(1)[0]
    val foregroundTaskPackageName = foregroundTaskInfo.topActivity.packageName
    return foregroundTaskPackageName.toLowerCase() == context.packageName.toLowerCase()
}

và trong Tệp kê khai

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.