最新常规app前台运行保活手段

最新常规app前台运行保活手段

最近做了一个项目,需要一直运行在前台,系统虽然是定制的,但厂商不配合,不去修改系统,使app一直运行在前台
所以保活手段只能 app 端实现

某些保活手段酌情使用,因为这个项目运行在某台长期运行的设备上,而不是手机,所以不考虑耗电等问题


随着Android 版本更新,很多黑科技保活手段都不再有效
同时为了兼容性,防止在某些设备上无法使用,所以目前只使用常规手段保活。

线程轮询

使用线程轮询运行状态,如果app退出,立刻启动app到前台

监听 Activity 的启动

自定义 Application 并注册 activity 启动监听

Application需要实现 Application.ActivityLifecycleCallbacks

定义 private int appCount = 0;

在下面两个方法中添加如下

    @Override
    public void onActivityStarted(Activity activity) {
        appCount++;
    }

    @Override
    public void onActivityStopped(Activity activity) {
        appCount--;
    }

Application 添加方法

    public boolean isForground() {
        return appCount > 0;
    }

注册 activity 生命周期回调

registerActivityLifecycleCallbacks(this);

启动线程,然后编写 死循环

注意死循环需要适当的暂停,例如 一秒执行一次,或者五秒执行一次

在线程中,如果检测到 app 没有运行在前台,则立刻启动 MainActivity

        if (!((MApplication) mContext.getApplicationContext()).isForground()) {
            Intent newIntent = new Intent(MApplication.getContent(), MainActivity.class);
            newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            MApplication.getContent().startActivity(newIntent);
        }

桌面启动器

由于app需要一直运行在前台,所以可以将app 直接作为桌面启动器

修改 maniface 文件

直接添加下面三项即可

                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY" />

修改后:

        <activity
            android:name=".activity.MainActivity"
            android:exported="true"
            android:launchMode="singleTask"
            android:windowSoftInputMode="stateAlwaysHidden">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY" />
            </intent-filter>
        </activity>

加上以后,在用户按下 home 键时,要求选择 桌面启动器,点选 本 app 并勾选 始终

注意:需要留出接口,跳转到系统设置,否则无法取消桌面启动器,则无法再打开其它应用

系统服务保活

系统服务包括 辅助服务,通知栏监听服务

服务需要引导用户手动开启

定义服务,继承自 通知栏监听服务

public class NotifyServer extends NotificationListenerService {


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

定义服务,继承自 辅助服务

public class HelpServer extends AccessibilityService {

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

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

    }

    @Override
    public void onInterrupt() {

    }
}

在 Manifest 的 application 中,添加这两个服务

        <service
            android:name=".NotifyServer"
            android:exported="true"
            android:label="@string/service_name"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>

        <service
            android:name=".HelpServer"
            android:label="@string/service_name"
            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/accessibility_service_config" />
        </service>

其中, accessibility_service_config 文件内容如下:

accessibility_service_config 文件存放在 res/xml 目录下

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/help_info"
    android:notificationTimeout="100" />

help_info 文本是辅助服务的提示信息

自此,两个服务已经创建好了,

如果用户授予 app 通知栏监听权限,或者辅助服务权限,对应的服务就会被启动
在里面编写保活代码即可,也可以和前面代码一样,启动线程,每隔数秒,监听 app 是否运行于前台

跳转到授权服务的代码:

// 跳转到 通知栏监听 权限授权界面
    private void startNotifySetting(Context context) {
        try {
            Intent intent;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
                intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
            } else {
                intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            }
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
            Toast.makeText(this, getString(R.string.permission_tip), Toast.LENGTH_LONG).show();
        } catch (ActivityNotFoundException e) {//普通情况下找不到的时候需要再特殊处理找一次
            try {
                Intent intent = new Intent();
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                ComponentName cn = new ComponentName("com.android.settings", "com.android.settings.Settings$NotificationAccessSettingsActivity");
                intent.setComponent(cn);
                intent.putExtra(":settings:show_fragment", "NotificationAccessSettings");
                context.startActivity(intent);
                Toast.makeText(this, getString(R.string.permission_tip), Toast.LENGTH_LONG).show();
            } catch (Exception ignore) {
                Toast.makeText(this, getString(R.string.open_activity_faild), Toast.LENGTH_LONG).show();
            }
            e.printStackTrace();
        }
    }

// 跳转到 辅助服务 授权界面
    private void startHelpSetting() {
        try {
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            startActivity(intent);
            Toast.makeText(this, getString(R.string.permission_tip), Toast.LENGTH_LONG).show();
        } catch (ActivityNotFoundException ignore) {
            Toast.makeText(this, getString(R.string.open_activity_faild), Toast.LENGTH_LONG).show();
        }
    }


这两个方法在合适的提示后,调用即可

双 app 广播保活

使用两个app,每隔数秒发送广播,相互保活
这两个app 也可以使用上述手段(线程轮询、辅助服务)保活自身

每隔一段时间发送广播

        try {
            Intent intent = new Intent(Constant.HEART_BEAT_BROAD_CAST);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                    | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);

            ComponentName componentName = new ComponentName(pkg, cls);
            intent.setComponent(componentName);
            sendBroadcast(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }

在Android 8.0 上,需要使用 ComponentName 指定广播包名,否则无法接收到广播

监听 home 键退出 app,随后重启

在 Activity 中动态注册广播

        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        registerReceiver(forgroundReceive, intentFilter);

该广播需要动态注册

Intent.ACTION_CLOSE_SYSTEM_DIALOGS 广播在按下 home 键和 菜单键 时,都能接收到

在广播接收者中

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
            Intent newIntent = new Intent(context, MainActivity.class);
            newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(newIntent);
        }
    }

这样用户按下 homemenu 都会重启应用

注意,如果用户在: 设置->应用管理-> <app> -> 强制停止
会导致应用无法接收到任何广播,这是系统默认拦截行为

开机广播

manifest 注册广播

        <receiver
            android:name=".StartReceive"
            android:exported="true"
            android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
            <intent-filter>
                <!-- 开机广播 -->
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

启动

public class StartReceive extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        LogUtil.i("StartReceive", "receive boot start broadcast ,intent:" + LogUtil.objToString(intent));
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())
                || Intent.ACTION_REBOOT.equals(intent.getAction())) {
            Intent newIntent = new Intent(context, MainActivity.class);
            newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  //注意,必须添加这个标记,否则启动会失败
            //开机广播监听,启动本应用
            context.startActivity(newIntent);
        }
    }
}

建议

在app所有回到前台的地方,建议都加上判断,如果在某些情况下,需要暂时退出 app,(例如升级app),则允许暂时退出。否则app无法退出的情况下,无法进入系统做任何操作

例如加上 MApplication.isCheckForground()

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && MApplication.isCheckForground()) {
            Intent newIntent = new Intent(context, MainActivity.class);
            newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(newIntent);
        }
    }

如果app需要手动点击升级,则 isCheckForground 返回值为 false ,允许暂时退出app

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://zwc365.com/2019/11/19/app-alive-new

Buy me a cup of coffee ☕.