更新记录

1.1.2(2022-07-11)

补全JobHandlerService中非空判断中空值处理,尝试解决部分机型崩溃问题(无问题机型,未测效果)

1.1.1(2022-04-12)

​ 优化:fun_notif_title,fun_notif_text不设置分别默认为app名称、"正在后台运行..."。

​ 新增:跳转系统设置。

1.1.0(2022-03-21)

​ 修复:startForegroundService后因非空判断造成的startForeground未调用错误。

​ 变更:startKeep接口通知标题内容设置改为meta-data中配置fun_notif_title,fun_notif_text;建议使用配置值方式,接口中不再配置,后台服务保活中读数据为空时,会去读取meta-data的值。

查看更多

平台兼容性

Android Android CPU类型 iOS
适用版本区间:4.4 - 11.0 armeabi-v7a:未测试,arm64-v8a:未测试,x86:未测试 ×

原生插件通用使用流程:

  1. 购买插件,选择该插件绑定的项目。
  2. 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
  3. 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
  4. 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
  5. 开发完毕后正式云打包

付费原生插件目前不支持离线打包。
Android 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android
iOS 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/ios

注意事项:使用HBuilderX2.7.14以下版本,如果同一插件且同一appid下购买并绑定了多个包名,提交云打包界面提示包名绑定不一致时,需要在HBuilderX项目中manifest.json->“App原生插件配置”->”云端插件“列表中删除该插件重新选择



2022-03-18发布的1.1.0版本使用必读

重大变更:startKeep接口通知标题内容设置改为meta-data中配置fun_notif_title,fun_notif_text。建议使用配置值方式,接口中不再配置,后台服务保活中读数据为空时,会去读取meta-data的值。

即manifest.json->app原生插件配置->fun-keeplive下2个配置项(如果不存在,删除重新添加一下)



前言

因项目特殊需要制作此插件,因个人时间有限,当前插件未做全机型测试,需要的请自行测试

<!--测试时使用样例代码见最后面-->

目前在小米6(安卓9,夜间12:10开启早上7:31手动杀死,中间每隔一分钟上传数据到服务器,早上查看服务器共收440条数据记录在redis中)都能有很好的保活效果(只要你不主动杀死程序)。小米11Ultra[安卓11]和华为[鸿蒙]测试40分钟页无问题。

【测试过程:手机开启保活(+屏幕唤醒(10分钟),使用setInterval每分钟项服务器上传数据,数据内容为手机时间,服务端记录到redis集合中。)】

如果你的应用希望能够一直在后台运行(比如推送服务)而不被系统自动杀死的话,可以尝试一下。需要注意的是,程序保活并不代表能做到程序杀不死,除非你把你的应用做成系统应用或者加入到系统的白名单内,否则也只是提高了程序的优先级权重,减少程序被系统回收杀死的概率而已。

插件扩展

1.定位扩展插件【需使用本插件前提下,再加入扩展插件使用】https://ext.dcloud.net.cn/plugin?id=7013



已知问题

1.申请忽略电池优化:在小米新机子上打开的不是优化申请窗或优化页,而是【应用智能省电】(点击是省电策略),旧机子(如MI6)没问题。但是去【设置】搜索【电池优化】当前前应用已加入。

解决方式:可以先调用省电策略,在判断是否忽略电池优化,没忽略再申请。(实际小米高版本【智能省电无限制】,好像就默认忽略电池优化了)

更新内容

1.1.1

优化:fun_notif_title,fun_notif_text不设置分别默认为app名称、"正在后台运行..."。

新增:跳转系统设置。

1.1.0

修复:startForegroundService后因非空判断造成的startForeground未调用错误。

变更:startKeep接口通知标题内容设置改为meta-data中配置fun_notif_title,fun_notif_text;建议使用配置值方式,接口中不再配置,后台服务保活中读数据为空时,会去读取meta-data的值。

1.0.7

修复:个别机型保活过程中常驻通知发出声音问题。

1.0.6

更新:将淘宝weex类全部更为Uniapp类。

1.0.5

修复app关闭[杀死]后点击通知栏无效(即不打开app)

1.0.4

1.重构,精简功能

1.0.3

1.新增通知类接口

2.新增悬浮窗权限检测和授权申请

3.新增获取自启动开启状态(当前仅小米可用)

4.新增打开应用设置接口

5.新增打开电源管理页接口(在不同手机展示页面不同)

使用说明

需要先添加原生插件到uniapp项目,并配置manifest.json,再代码引用。使用教程见https://ask.dcloud.net.cn/article/35412

简易使用流程:先初始化→开启保活(+屏幕唤醒)→配置白名单类设置→不需要保活时关闭保活。

所有方法result返回值带code的最好判断为0再取值

初始化

const keepLive = uni.requireNativePlugin('fun-keeplive');

1.开启保活

//v1.1.0开始不建议在此处配置title&text,建议在原生插件处配置fun_notif_title,fun_notif_text,以规避异常风险。
let cof = {
    mode: 1, //0省电模式 1流氓模式
    //title: "推送服务", //通知栏标题 //v1.1.0开始不建议在此处配置
    //text: "正在运行,请勿关闭",//通知栏内容//v1.1.0开始不建议在此处配置
    //isShow: true //通知栏显示 true显示,false隐藏(建议true)现在绝大部分手机无效,可忽略
   // jobTime: 60.默认值,测试时使用的默认值
}
keepLive.startKeep(cof, function(result) {
    console.log(result)
});
//定时回调,时间为上面jobTime,需要明确的是,实际效果这个时间间隔不是固定的,
const globalEvent = uni.requireNativePlugin('globalEvent');//官方的,不需额外配置,引用即可
globalEvent.addEventListener('doKeepJobEvent', function() {
                console.log("----doKeepJobEvent-----");//http上传

            });

2.停止保活机制

keepLive.stopKeep(function(result) {
    console.log(result)
});

3.打开屏幕唤醒【开启保活后,开启唤醒增加保活效果】

//time:间隔时间,单位:分,建议5-10。
//1台华为[鸿蒙],2台小米(米6[安卓9]、米11Ultra[安卓11])使用的10分测试的。
keepLive.openWakeLock({time:5},function(result) {
    console.log(result)
});

4.关闭屏幕唤醒

keepLive.colseWakeLock(function(result) {
    console.log(result)
});

5.打开白名单配置页(电池优化、省电策略、自启动)

keepLive.gotoWhiteList(function(result) {
    console.log(result)
});

6.检测是否忽略电池优化(简单解释:开启电池优化,会干掉应用)

keepLive.isIgnoringBatteryOptimizations(function(result) {//SDK>=23,否则返回不支持
    console.log(result);
})
//以下为结果示例(期望结果是忽略优化即"flag": true)
{
    "sdk_int": 28,
    "flag": true,//电池是否忽略优化,true:忽略;false:优化
    "code": 0,
    "msg": "ok"
}

7. 申请忽略电池优化(如果已加入电池优化的白名单 则进入系统电池优化页面,若未加入则弹窗申请。先部分手机省电策略为无限制则自动忽略电池优化)

//如果已加入电池优化的白名单 则进入系统电池优化页面,
//若未加入则弹窗申请,
//可使用isIgnoringBatteryOptimizations检测
keepLive.requestIgnoreBatteryOptimizations(function(result) {
    console.log(result)
});
//以下为结果示例(调用那一刻的情况,比如初次默认是优化,那返回结果就是"flag": false)
{
    "sdk_int": 28,
    "flag": true,//电池是否忽略优化,true:忽略;false:优化
    "code": 0,
    "msg": "ok"
}

8. 设置省电策略

keepLive.setPowerKeeper(function(result) {
    console.log(result)
});

9.设置自启动

keepLive.startToAutoStartSetting(function(result) {
    console.log(result)
});

10.是否允许自启动[1.0.3新增](检测第9个接口。当前仅支持小米)

keepLive.isAutoStart(function(result) {

});

11.打开应用设置页[1.0.3新增]

keepLive.openSetting(function(result) {

});

12.打开耗电统计[1.0.3新增]

keepLive.openPowerUsageSummary(function(result) {

});

13.获取Android版本、应用版本号等

keepLive.getVersion(function(result) {
    console.log(result)
});

14.是否有悬浮窗权限[1.0.3新增]

keepLive.isAlertWindow(function(result) {

});

15.申请悬浮窗[1.0.3新增]

keepLive.requestAlertWindow(function(result) {

});

16.是否开启通知[1.0.3新增]

keepLive.isNotificationEnabled(function(result) {

});

17.进入应用通知设置页[1.0.3新增]

keepLive.openNotifySetting(function(result) {

});

18.检查是否授权读取通知[1.0.3新增]

keepLive.enabledNotificationListeners(function(result) {

});

19.申请授权读取通知[1.0.3新增]

keepLive.openNotifyListenerSetting(function(result) {

});

20.开启通知监听[1.0.3新增]

// packageNameList: 监听的包名,数组形式。空则全部监听
keepLive.startNotifyListener({
                    // packageNameList: ["com.tencent.mobileqq"]
                }, function(result) {

});

// onFunNotification 通知回调Event
const globalEvent = uni.requireNativePlugin('globalEvent');//内置原生插件,引用即可
globalEvent.addEventListener('onFunNotification', function() {
                console.log("----onFunNotification-----");//
                //type : onNotificationRemoved 移除消息  onNotificationPosted 发布消息
            });

21.关闭通知监听[1.0.3新增]

keepLive.stopNotifyListener(function(result) {

});

22.清空通知信息[1.0.3新增]

keepLive.clearNotify(function(result) {

});

23.打开系统设置页[1.1.1新增]

keepLive.openSystemSetting(function(result) {

});

内置原生插件简要说明(示例中用到的)

内置原生插件,代码引用即可,不需要额外配置;

globalEvent 用于监听持久性事件,例如定位信息,陀螺仪等的变化。全局事件是需要额外 APIs 处理的次要 API。
modal 模块提供了以下展示消息框的 API。
toast(options)
  • @options
    • message, string, 展示的内容.
    • duration, number, 持续时间(以秒为单位)

例如:使用原生toast方便调试(在后台时也能弹,不用都接电脑方便调试)

const modal = uni.requireNativePlugin('modal');
modal.toast({
                    message: "====:进主页",
                    duration: 2
                });

简单调用示例

页面按钮只是展示可以不点击,onLoad()方法已调用,打开页面,按要求授权即可测试。

如果app有推送或者手机上后台运行着厂商白名单app(如微信/QQ/邮箱等)保活效果最好。(时间长了有时假死,屏幕量一下,立马工作)

本人测试时手机除了当前插件app均无自启动权限,并且清理了内存,只剩插件app,因时间有限,测试了一小时多没问题,截止1.0.2提交时没在测试,后台看到没上传数据,只要你点亮一下屏幕(无需解锁),会继续上传。【测试手机:小米11Ultra/安卓11】

<template>
    <view class="content">
        <view style="margin-bottom: 100rpx;"></view>
        <button @tap="startKeep ">启动服务</button>
        <button @tap="gotoWhiteList">配置后台运行,白名单,自启动</button>
        <button @tap="isIgnoringBattery">判断应用是否添加在白名单之中</button>
        <button @tap="requestIgnoreBattery">申请电池优化</button>
        <button @tap="setPowerKeeper">设置省电策略</button>
        <button @tap="startToAutoStartSetting">设置自启动</button>
        <button @tap="stopKeep">关闭服务</button>
        <!-- <button @tap="hasLocationPermissions">获取是否授权定位</button>
        <button @tap="requestLocationPermissions">申请定位</button> -->
        <!-- <button @tap="topost">保活传数据</button> -->
        <u-button class="m-button" type="primary" shape="circle" text="保活传数据" @click="topost"></u-button>
        <view style="margin-bottom: 30rpx;"></view>
        <view class="msg">{{ msg2 }}</view>
        <view class="msg">
            <text>{{ msg }}</text>
        </view>
        <!-- <view class="msg" v-html="msg"> </view> -->
    </view>
</template>

<script>
    // const fUN_IM = uni.requireNativePlugin('FUN-IM');
    const keepLive = uni.requireNativePlugin('fun-keeplive');
    const globalEvent = uni.requireNativePlugin('globalEvent');
    // const fUN_AmapLocation = uni.requireNativePlugin('FUN-AmapLocation');

    var _this;
    export default {
        data() {
            return {
                title: '',
                msg: '',
                msg2: '',
                intTime: 0,
                maxTime: 0,
                minTime: 99999999999,
                totalTime: 0,
                initTime: new Date().getTime(),
                lastTime: 0,
                nowtime: 0,
                locTime: 0,
                reTime: 0,
                locationType: null,
                time_start: 0,
                time_end: 0
            };
        },
        onLoad() {
            console.log('====:进主页');
            _this = this;
            const deviceId = '';
            try {
                deviceId = uni.getStorageSync('storage_deviceId');
                if (deviceId) {
                    console.log(deviceId);
                } else {
                    try {
                        const res = uni.getSystemInfoSync();
                        _this.msg2 = res.deviceId;
                    } catch (e) {
                        _this.msg2 = _this.timeFormat(null, 'mm_dd_hh_MM_ss_SSS');
                    }
                    try {
                        uni.setStorageSync('storage_deviceId', _this.msg2);
                    } catch (e) {

                    }
                }
            } catch (e) {
                // error
            }
            if (!deviceId) {
                _this.msg2 = _this.timeFormat(null, 'mm_dd_hh_MM_ss_SSS');
            }
            ////////////////////////////////////////////直接调用这一段可以测试
            _this.startKeep();
            globalEvent.addEventListener('doKeepJobEvent', function() {
                console.log("----doKeepJobEvent-----");
                uni.request({
                    url: 'https://XX.XXX.com/api/open/localtion',
                    data: {
                        id: 'moyu-' + _this.msg2 + '-KJ',
                        loc: _this.timeFormat()
                    },
                    method: 'POST',
                    success: (res) => {
                        console.log(res)
                    }
                })
            });
            _this.topost();
            //////////////////////////////////////////////直接调用这一段可以测试
        },
        methods: {
            start: function() {},
            stop: function() {},
            once: function() {},
            timeFormat(dateTime = null, fmt = 'hh:MM:ss:SSS') {
                if (!dateTime) dateTime = Number(new Date());
                if (dateTime.toString().length == 10) dateTime *= 1000;
                let date = new Date(Number(dateTime));
                let ret;
                let opt = {
                    'y+': date.getFullYear().toString(), // 年
                    'm+': (date.getMonth() + 1).toString(), // 月
                    'd+': date.getDate().toString(), // 日
                    'h+': date.getHours().toString(), // 时
                    'M+': date.getMinutes().toString(), // 分
                    's+': date.getSeconds().toString(), // 秒
                    'S+': date.getMilliseconds().toString() // 毫秒
                };
                for (let k in opt) {
                    ret = new RegExp('(' + k + ')').exec(fmt);
                    if (ret) {
                        fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
                    }
                }
                return fmt;
            },
            topost() {
                uni.request({
                    url: 'https://XX.XXX.com/api/open/localtion',
                    data: {
                        id: 'moyu-' + _this.msg2 + 'I',
                        loc: _this.timeFormat()
                    },
                    method: 'POST',
                    success: (res) => {
                        console.log(res)
                    }
                });
                let dst = setInterval(function() {
                    uni.request({
                    url: 'https://XX.XXX.com/api/open/localtion',
                    data: {
                        id: 'moyu-' + _this.msg2 + 'I',
                        loc: _this.timeFormat()
                    },
                    method: 'POST',
                    success: (res) => {
                        console.log(res)
                    }
                });
                }, 1000 * 60)
            },
            startKeep() {
                let cof = {
                    mode: 1, //0省电模式 1流氓模式
                    title: "推送服务", //通知栏标题
                    text: "正在运行,请勿关闭", //通知栏内容
                    isShow: true //通知栏显示 true显示,false隐藏(建议true)
                }
                keepLive.startKeep(cof, function(result) {
                    console.log(result)
                });
                keepLive.isIgnoringBatteryOptimizations(function(res) {
                    console.log(res);
                    let flag = res.flag;
                    if (flag == false) {
                        keepLive.gotoWhiteList()
                    }
                })

                keepLive.openWakeLock({
                    time: 1
                }, function(result) {
                    console.log(result)
                });

            },
            //判断应用是否添加在白名单之中
            isIgnoringBattery() {
                keepLive.isIgnoringBatteryOptimizations(function(result) {
                    console.log(result)
                })
            },
            //申请加入白名单
            requestIgnoreBattery() {
                keepLive.requestIgnoreBatteryOptimizations(function(result) {
                    console.log(result)
                });
            },
            setPowerKeeper() {
                keepLive.setPowerKeeper(function(result) {
                    console.log(result)
                });
            },
            //设置app自启动
            startToAutoStartSetting() {
                keepLive.startToAutoStartSetting();
            },
            stopKeep() {
                keepLive.stopKeep(function(result) {
                    console.log(result)
                });
            },
            hasLocationPermissions() {
                keepLive.hasLocationPermissions(function(result) {
                    console.log(result)
                });
            },
            requestLocationPermissions() {
                keepLive.requestLocationPermissions();
            },
            gotoWhiteList() {
                keepLive.gotoWhiteList()
            },

        }
    };
</script>

<style>
    .content {
        margin: 15rpx;
    }

    .m-button {
        margin: 15rpx 0;
    }

    .msg {
        width: 750rpx;
    }
</style>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.GET_TASKS" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

插件无广告

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问