最近有一个这样的小功能,动态监听App在前台运行还是后台运行,然后做对应业务逻辑操作,故此全文主讲Android app 前后台监听 ~


up ~

  • 提要
  • 了解
  • 入门
  • 项目进阶 - A
  • 项目进阶 - B
  • 小课堂


提要

在Android中从你打开App的那一霎那就已经调用了Application的基本配置,随后又调用了对应四大组件,当然在调用对应类时肯定是先走的 onCreate()生命周期了 ~

故此在一切正式开始之前,首先我们要创建一个属于我们的Application继承于系统Application,这里我创建的是MyApplication,创建好之后将其注入到清单文件中 ~

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="nk.com.activitylifecycle">
    
    <!--将我们创建好的Application在application标签内进行声明 - android:name=".MyApplication"-->
    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

关于前后台监听,主要涉及三种场景

  • 用户将app放于多任务界面
  • 用户将app返回手机主页界面
  • 用户将app返回app应用内部界面

关于前后台监听的功能,主要涉及的API:registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) ,从字面意思不难看出就是注册Activity生命周期的监听回调 ~

通过Application的源码可以发现内部有注册的时候,自然少不了注销,一般这样的开闭方法都减少了内存方面可能隐藏的Bug,注销方法可以看场景使用,可以用在MainActivity销毁的时候,也可以不用 ~

android 应用进入后台监听 android监听app数据_android 应用进入后台监听

嘘,还有一点要注意
因为我们监听前后台的目的主要是要做一些业务逻辑,但是有的业务逻辑针对的是整个项目,有的业务逻辑可能只是针对某一个或者某几个类,所以当业务逻辑涉及到仅仅某些类的话,可以采用接口回调或者EventBus进行事件处理 ~ 个人建议是采用EventBus,因为接口回调有些朋友容易绕晕,同时代码会冗余较多,而EventBus的话发展至今已经比较完善了,所以为自己少点烦恼 ~

了解

以下代码是一个最简单的实现,你通过切换App场景可以发现已经实现了监听前后台的功能,但是!它是存在很严重的缺陷!当我们存在多个Activity且相互进行跳转的时候,通过Log可以发现频繁的调用了生命周期的监听方法 ~ 故此方法只用于基础理解,可以在看下方入门 ~

Look here:频繁调用方法,也就意味着内部的逻辑也会重复调用,从而大大增加了bug率 ~

package nk.com.activitylifecycle;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;

/**
 * @author MrLiu
 * @date 2020/10/12
 * desc
 */
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        
        //监听Activity生命周期
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {

            //当前Activity个数
            private int activityNumber = 0;

            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {

            }

            @Override
            public void onActivityStarted(Activity activity) {
               Log.e("tag", "ActivityLifecycleCallbacksAdapter:后台 --> 前台");
            }

            @Override
            public void onActivityResumed(Activity activity) {

            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {
               Log.e("tag", "ActivityLifecycleCallbacksAdapter:前台 --> 后台");
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }
}

入门

与上方的不同点在于监听前后台切换时,如果用户多Activity切换的话就不会被重复调用切后台监听内的逻辑方法,从而减少了部分bug,但是此入门方式仅仅解决了最基础的需求,如果在项目中使用的话还可以在优化一下,不过还是先看入门,然后在继续往进阶看吧~

package nk.com.activitylifecycle;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;

/**
 * @author MrLiu
 * @date 2020/10/12
 * desc
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {

            private int activityNumber = 0;

            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {

            }

            @Override
            public void onActivityStarted(Activity activity) {
                if (activityNumber++ == 0) {
                    Log.e("tag", "app回到前台");
                    Log.e("tag", "activityNumber = " + activityNumber);
                }
            }

            @Override
            public void onActivityResumed(Activity activity) {

            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {
                if (--activityNumber == 0) {
                    // app回到后台
                    Log.e("tag", "app回到后台");
                    Log.e("tag", "activityNumber = " + activityNumber);
                }
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }
}

项目进阶 - A

与上方的不同在于对Application内方法进行了抽离,内部使用了单例模式,减少了内存开销,同时将该业务进行了剥离,算是一个代码优化把 - -,这个阶段的代码基本可以投入项目使用了

LifecycleMonitor

package nk.com.activitylifecycle;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;

/**
 * @author MrLiu
 * @date 2020/10/12
 * desc
 */
public class LifecycleMonitor {

    private int resumeActivityCount = 0;

    private static LifecycleMonitor lifecycleHelper;

    public static LifecycleMonitor getInstance() {
        if (lifecycleHelper == null) {
            lifecycleHelper = new LifecycleMonitor();
        }
        return lifecycleHelper;
    }

    public void register(Application application) {
        if (application != null)
        	//这里对ActivityLifecycleCallbacksAdapter进行了二次包装,不必实现原有的抽象方法,仅需实现我们需要的方法即可!
            application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {
                @Override
                public void onActivityStarted(Activity activity) {
                    if (resumeActivityCount++ == 0) {
                        // 执行切换到前台的逻辑
                        Log.e("tag", "后台 --> 前台" + resumeActivityCount);
                        // ... 使用EventBus传递数据
                    }
                }

                @Override
                public void onActivityStopped(Activity activity) {
                    if (--resumeActivityCount == 0) {
                        // 执行切换到后台的逻辑
                        Log.e("tag", "前台 --> 后台" + resumeActivityCount);
                        // ... 使用EventBus传递数据
                    }
                }
            });
    }

    /**
     * Activity 生命周期监听,用于监控app前后台状态切换(对应Activity生命周期)
     */
    public static class ActivityLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {

        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {
        }

        @Override
        public void onActivityStarted(Activity activity) {
        }

        @Override
        public void onActivityResumed(Activity activity) {
        }

        @Override
        public void onActivityPaused(Activity activity) {
        }

        @Override
        public void onActivityStopped(Activity activity) {
        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
        }

        @Override
        public void onActivityDestroyed(Activity activity) {
        }
    }
}

MyApplication

package nk.com.activitylifecycle;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;

/**
 * @author MrLiu
 * @date 2020/10/12
 * desc
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LifecycleMonitor.getInstance().register(this);
    }
}

项目进阶 - B

与上方相比,此处调用主要采取了static的方式进行调用,同时加入了内部的俩个监听回调(个人感觉有点画蛇添足,但是从一定层面其实有达到了解耦的效果,所以仁者见仁智者见智咯),可能这个方法不是最好的,但是对于我这种懒人可能是最合适的 ~ 所以当前代码也可投入项目使用 ~

AppStateTracker

package nk.com.activitylifecycle;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;

/**
 * @author MrLiu
 * @date 2020/10/10
 * desc
 */
public class AppStateMonitor {
    public static final int STATE_FOREGROUND = 0;

    public static final int STATE_BACKGROUND = 1;

    private static int currentState;

    public static int getCurrentState() {
        return currentState;
    }

    public interface AppStateChangeListener {
        void appTurnIntoForeground();

        void appTurnIntoBackGround();
    }

    public static void track(Application application, final AppStateChangeListener appStateChangeListener) {

        application.registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() {

            private int resumeActivityCount = 0;

            @Override
            public void onActivityStarted(Activity activity) {
//                if (resumeActivityCount == 0) {
//                    currentState = STATE_FOREGROUND;
//                    appStateChangeListener.appTurnIntoForeground();
//                }
//                resumeActivityCount++;

                if (resumeActivityCount++ == 0) {
                    currentState = STATE_FOREGROUND;
                    appStateChangeListener.appTurnIntoForeground();
                }
            }


            @Override
            public void onActivityStopped(Activity activity) {
//                resumeActivityCount--;
//                if (resumeActivityCount == 0) {
//                    currentState = STATE_BACKGROUND;
//                    appStateChangeListener.appTurnIntoBackGround();
//                }
                
                if (--resumeActivityCount == 0) {
                    currentState = STATE_BACKGROUND;
                    appStateChangeListener.appTurnIntoBackGround();
                }
            }
        });
    }

    private static class SimpleActivityLifecycleCallbacks implements Application
            .ActivityLifecycleCallbacks {

        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {

        }

        @Override
        public void onActivityStarted(Activity activity) {

        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {

        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {

        }
    }
}

MyApplication

package nk.com.activitylifecycle;

import android.app.Application;
import android.util.Log;

/**
 * @author MrLiu
 * @date 2020/10/12
 * desc
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        AppStateMonitor.track(this, new AppStateMonitor.AppStateChangeListener() {
            @Override
            public void appTurnIntoForeground() {
                // 处理app到前台的逻辑
                Log.e("tag", "App - 处于前台");
            }

            @Override
            public void appTurnIntoBackGround() {
                // app处理到到后台的逻辑
                Log.e("tag", "App - 处于后台");
            }
        });
    }
}

小课堂

因为监听的去重方式使用到了++、–故此科普一下自增++、自减-- 前后位置作用

自加(++)自减(–)运算
自加: ++
对原有的数据进行+1
自减:- -
对原有的数据进行-1

使用场景

  • 单独使用
    放在操作数的前面和后面效果一样(这种用法是我们比较常见的)
  • 参与运算使用
    放在操作数的前面(++1||- -1),先自增或者自减,然后再参与运算
    放在操作数的后面(1++||1- -),先参与运算,再自增或者自减

在查询监听前后台的功能实现时 也查到了以下的这种方法,此方法主要是在监听BaseActivity内的生命周期,理论上也是可行的,但是我并没有亲自尝试,故此当做饭后甜点吧 ~

不过有人指出在Android6.0的机器上,在某些情景下使用无法获取到当前正在运行的包名,如在屏幕暗下后,使用下方代码中的isAppOnForeground方法内的获取进程的方法后,可能会获取不到正确的进程列表 ~

public class BaseActivity extends Activity {
 	//全局变量
    public static boolean isActive; 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base);
    }
 
    @Override
    protected void onResume() {
        if (!isActive) {
            //app 从后台唤醒,进入前台
            isActive = true;
            Log.i("ACTIVITY", "程序从后台唤醒");
        }
        super.onResume();
    }
 
    @Override
    protected void onStart() {
        super.onStart();
    }
 
    @Override
    protected void onPause() {
        super.onPause();
    }
 
    @Override
    protected void onStop() {
        if (!isAppOnForeground()) {
            //app 进入后台
            isActive = false;//记录当前已经进入后台
            Log.i("ACTIVITY", "程序进入后台");
        }
        super.onStop();
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
 
    /**
     * APP是否处于前台唤醒状态
     *
     * @return
     */
    public boolean isAppOnForeground() {
        ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        String packageName = getApplicationContext().getPackageName();
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
                
        if (appProcesses == null)
            return false;
 
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            // The name of the process that this object is associated with.
            if (appProcess.processName.equals(packageName)
                    && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                return true;
            }
        }
        return false;
    }
}

查询当前进程名

根据功能查询到早期处理方式,也可以使用getRunningTasks,不过后来随着更新此方式已经废弃!然后又有了通过getRunningTasks和RunningAppProcessInfo结合的处理方式(兼容API22+和API22-),不过有人说这种方法在Android6.0的机器黑屏时获取不到正确的进程列表 ~ 所以还是 建议使用上方的项目进阶 A 和 B 把

/**
     * 查询当前进程名
     * 
     * @param context
     * @return
     */
    public static String getCurrentPkgName(Context context) {
        ActivityManager am = (ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE);
        String pkgName = null;
        if (Build.VERSION.SDK_INT >= 22) {
            ActivityManager.RunningAppProcessInfo currentInfo = null;
            Field field = null;
            int START_TASK_TO_FRONT = 2;
            try {
                field = ActivityManager.RunningAppProcessInfo.class
                        .getDeclaredField("processState");
            } catch (Exception e) {
                e.printStackTrace();
            }

            List<RunningAppProcessInfo> appList = am.getRunningAppProcesses();
            for (ActivityManager.RunningAppProcessInfo app : appList) {
                if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                    Integer state = null;
                    try {
                        state = field.getInt(app);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (state != null && state == START_TASK_TO_FRONT) {
                        currentInfo = app;
                        break;
                    }
                }
            }
            if (currentInfo != null) {
                pkgName = currentInfo.processName;
            }
        } else {
            List<RunningTaskInfo> runTaskInfos = am.getRunningTasks(1);
            // 拿到当前运行的任务栈
            ActivityManager.RunningTaskInfo runningTaskInfo = runTaskInfos
                    .get(0);
            // 拿到要运行的Activity的包名
            pkgName = runningTaskInfo.baseActivity.getPackageName();
        }
        return pkgName;
    }