应用保活终极总结(二):Android6.0及以上的保活实践
来源:互联网 发布:淘宝短信营销话术 编辑:程序博客网 时间:2024/04/28 19:31
应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)
原作者:“裂缝中的阳光dg”,本文由即时通讯网重新修订并整理发布,感谢原作者的无私分享。
1、前言
在Android 4.4及以后的系统中,应用能否常驻内存,一直以来都是相当头疼的事情,尤其移动端IM、消息推送这类应用,为了保证“全时在线”的概念,真是费尽了心思。虽然APP常驻内存对于用户来说比较”恶心”,但是在诸如IM和消息推送这类场景来说,APP的常驻内存却尤其重要。
APP常驻内存(保活防杀),旧事重提,距离上一次的研究亦有半年有余。最近,用户反馈说多进程守护方案(详见上篇《Android应用保活终极总结(一):Android6.0以下的双进程守护保活实践》)在华为Mate8(Andriod 7.0)保活效果不是很好,有时候还是不能及时收到消息。
于是,又带着怀疑的眼光,重新找回原来的代码进行测试,顺便分析了市场上主流运动类APP保活方法(微信、手Q就算了,富人家的孩子,不具代表性),同时也对系统对内存中APP的管理规则进行了进一步探索。
本文便是对最近一周的Android进程防杀、进程被杀复活的探索、学习、测试的内容总结,以备将来不时之需。因保活防杀和被杀复活涉及内容较多,我将它分成了两篇:即进程防杀篇(本文)和进程被杀复活篇(下篇),本篇将讨论如何实现进程防杀。
说起来比较绕口,总之本文要讨论的内容是如何防止Android应用被系统“杀掉”,下篇讨论的是“被杀掉”后如何让它复活。本文中的进程防杀方法最高适用至Android 7.0系统(版本再高也没有测试手机,无法验证哦)。
特别说明:本文中的Demo源码打包完整下载请至文末,直接从附件下载。
2、系列文章
本文是系列文章中的第2篇,本系列文章的大纲如下:
- 《应用保活终极总结(一):Android6.0以下的双进程守护保活实践》
- 《应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)》(本文)
- 《应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)》
3、参考资料
《Android进程保活详解:一篇文章解决你的所有疑问》
《Android端消息推送总结:实现原理、心跳保活、遇到的问题等》
《深入的聊聊Android消息推送这件小事》
《为何基于TCP协议的移动端IM仍然需要心跳保活机制?》
《微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)》
《微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)》
《移动端IM实践:实现Android版微信的智能心跳机制》
《移动端IM实践:WhatsApp、Line、微信的心跳策略分析》
>> 更多同类文章 ……
4、Andriod应用保活核心思想归纳
对于Android6.0及其以上系统APP保活,我觉得主要还是通过这两个方面进行,即:
- 降低omm_adj值,尽量保证进程不被系统杀死(本文要讨论的内容);
- 进程被杀死后,通过其他方式将进程复活(将在下篇讨论)。
但需要明白的是,面对各手机厂商的深度定制和谷歌越来越严格的资源管理机制,这两种方式结合的保活不是永久的,只能是相对存在,不同的机型结果也是不一样的。
由于篇幅限制,本文主要剖析下通过何种方式降低oom_adj的值来降低APP被杀的几率,以及oom_adj值是怎样做到的?
接下来,我们需要了解下Android系统回收内存中的进程所依据的规则:
进程在内存中时活动主要有五种状态:即前台进程、可见进程、服务进程、后台进程、空进程,这几种状态的进程优先级由高到低,oom_adj值由低到高(在ProcessList定义)。然后Android系统会根据当前系统资源和进程oom_adj值来回收相应的进程,前台进程一般不会被回收,空进程最容易被回收,这种管理规则就是"传说中"的Low Memory Killer。
为了更直观的了解这套规则,我画了个表:
注:优先级1表示最高级,普通进程的oom_adj>=0,系统进程oom_adj<0,系统会根据相应的内存阀值对符合某段oom_adj值的进程进行回收。另外,oom_adj值也会随着占用物理内存越大而增大,系统进程绝对不会被系统杀死。
接下来我将首先分析市面上的主流APP防杀方式,为我接下来的方案提供参考依据,请继续往下阅读。
5、市场主流运动类APP保活分析:咕咚(v 7.17.0)
5.1一键清理/滑动清理
- a. 当"咕咚"处于停止状态,其进程被杀死,通知栏图标被清理,等待几分钟没有 自动重启,当重新进入“咕咚”时,会从欢迎界面重新进入;
- b. 当"咕咚"处于运动进行状态,进程死亡,通知栏图标被清除,等待几分钟没有自动重启,但当重新进入“咕咚”时,其直接显示运动界面,而没有从欢迎界面进入,运动时间等状态与被清理时一样;
- c. 当"咕咚"处于运动暂停状态,其进程正常存活,通知栏图标正常显示。如果是单独清理,进程死亡,通知栏图标被清除;但当重新进入“咕咚”时,其直接显示运动界面,而没有从欢迎界面进入,运动时间等状态与被清理时一样。
5.2黑屏/锁屏
- a. 当"咕咚"处于停止状态,退到后台,锁屏进入黑屏状态,等待5分钟,进程死亡,通知栏被清除;
- b. 当"咕咚"处于运动进行状态,退到后台,锁屏进入黑屏状态,然后再进入系统,“咕咚”跑步界面自动弹出。再次锁屏,等待20分钟,进程没有被杀死,"咕咚"跑步界面自动弹出,运动状态保持不变;
- c. 当"咕咚"处于运动暂停状态,退到后台,锁屏进入黑屏状态,然后再进入系统,"咕咚"跑步界面自动弹出。再次锁屏。等待20分钟,进程没有被杀死,"咕咚"跑步界面自动弹出,运动状态保持不变。
前提:
- "手机管家->锁屏清理应用"关闭;
- "手机管家->自启管理"关闭;
- 运动状态,禁用返回键,用户只能从Home键退到后台;
- 运动界面文字闪烁或运动计时;
- 断网。
分析:
- 1)当"咕咚"处于停止状态时,一键清理和黑屏状态会被杀死,说明在没有进入运动界面之前,其保活机制没有被启动(即没有使运动界面切换到后台等);
- 2)当“咕咚”处于运动状态时,一键清理和黑屏状态没有被杀死(滑动清理除外),说明已经启动保活机制:
- ①"咕咚"禁止了返回键,以保证运动Activity不被销毁;
- ②不断更新通知栏计时,以保证APP始终在前台,防止被系统回收;
- ③"咕咚"被清理后能够自动重启,通知被删除后自动弹出,说明可能有另外一个东西(进程或Service)监听器运动Service(或进程)存活状态,当Service被销毁时,立马将其拉起来;
- ④“咕咚”被强制停止或清理杀死后,再次进入会直接显示运动界面且能够保持杀死之前的运动状态,说明其可能利用配置文件记录了相关状态;
- ⑤锁屏/解锁后,"咕咚"运动界面会自动弹出,说明其利用了广播机制对锁屏广播进行监听,弹出Activity以保证进程始终在前台。
结论:
常驻通知栏、双进程守护、广播锁屏、自定义锁屏。
备注:
以上为华为Mate8(Android 7.0)测试结果。其他如三星C9(Android 6.0)保活较好,特别是当一键清理时,"咕咚会自动启动,估计是使用了进程守护策略,而三星使用的是原生系统,因此结果你懂得;360F4(Android 6.0)保活很差,不愧是流氓中的战斗机,以更流氓的方式干掉流氓APP。
6、市场主流运动类APP保活分析:乐动力(v7.3.2)
6.1一键清理 / 滑动清理
- 三星C9(6.0):无论何种状态,"乐动力" 进程被杀死,等待几分钟,没有自动启动;
- 360F4(6.0):无论何种状态,"乐动力" 进程被杀死,等待几分钟,没有自动启动;
- 华为Mate8(7.0):无论何种状态,"乐动力" 进程被杀死,等待几分钟,没有自动启动。
6.2锁屏/黑屏
- a. 当"乐动力"处于停止状态,退到后台,锁屏,等待5分钟,进程死亡,通知栏被清除;
- b. 当"乐动力"处于运动暂停状态,退到后台,锁屏再开启,运动界面被切换到前台,并强制弹出自定义锁屏界面(覆盖在系统锁屏界面之上);再次锁屏,等待20分钟,应用进程存活;
- c. 当"乐动力"处于运动进行状态,退到后台,锁屏再开启,运动界面被切换到前台,并强制弹出自定义锁屏界面(覆盖在系统锁屏界面之上);再次锁屏,等待20分钟,应用进程存活。
前提:
- "手机管家->锁屏清理应用"关闭;
- "手机管家->自启管理"关闭;
- 运动状态,禁用返回键,用户只能从Home键退到后台;
- 断网。
分析:
- 当"乐动力"处于停止状态时,黑屏状态下,其在短时间内被系统杀死,说明保活机制没有启用;
- 但当处于运动暂停或进行状态时,"乐动力"在一段时间内没有被杀死,且当锁屏时,"乐动力"会自动将运动界面切换到前台,此外,还会强制弹出自定锁屏界面,这就说明"乐动力"的保活机制很可能是利用监听锁屏广播强制将相关界面切换到前台,以提高"乐动力"在黑屏状态下的存活率。
结论:
常驻通知栏、广播锁屏、自定义锁屏。
7、市场主流运动类APP保活分析:悦动圈(v3.1.2.9)
7.1一键清理 / 滑动清理
- 三星C9(6.0):效果与乐动力一致;
- 360F4(6.0):效果与乐动力一致;
- 华为Mate8(7.0):效果与乐动力一致。
6.2锁屏/黑屏
- a. 当"悦动圈"处于停止状态,退到后台,锁屏,等待3分钟,进程死亡,通知栏被清除;
- b. 当"悦动圈"处于运动暂停状态时,自定义锁屏、切换界面到前台与咕咚、乐动力一样,效果一致;
- c. 当"悦动圈"处于运动进行状态时,自定义锁屏、切换界面到前台与咕咚、乐动力一样,效果一致。
结论:
常驻通知栏、广播锁屏、自定义锁屏。
8、本文的APP进程防杀方案原理
经过上面的讨论分析,"咕咚"、"乐动力"等这类APP主要是通过监听锁屏、网络等系统广播,将进程置于前台以提高进程的级别,从而防止进程不那么轻易被系统干掉。另外,"咕咚"可能还使用了相关的进程被清理复活策略。当然,对于复活策略,我们下一篇文章再探讨,本文主要讨论以上APP是通过哪些方式降低进程omm_adj值,防止其被系统杀死的。
为了达到与"咕咚"等APP类似效果,我们模拟这么一种场景:当用户登录测试APP后,先不开启保活功能;当用户开始跑步时,开启保活功能,然后再在这基础上做黑屏运行、一键清理、强制停止等功能测试。也就是说,Android项目中SplashActivity、LoginActivity只是配合我们"演戏"的,真正启动APP保活逻辑的是在SportsActivity,它将上演"后宫争宠"戏码。
9、APP进程防杀方案第一阶:开启前台Service,“逼君上位”
将Service置为前台,目的时提高进程Service的oom_adj值,以降低其被系统回收的几率。该方案的原理是,通过使用 startForeground()方法将当前Service置于前台来提高Service的优先级。需要注意的是,对API大于18而言 startForeground()方法需要弹出一个可见通知,如果你觉得不爽,可以开启另一个Service将通知栏移除,其oom_adj值还是没变的。实现代码如下。
DaemonService.java:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**前台Service,使用startForeground
* 这个Service尽量要轻,不要占用过多的系统资源,否则
* 系统在资源紧张时,照样会将其杀死
*
* Created by jianddongguo on 2017/7/7.
*/
public
class
DaemonService
extends
Service {
private
static
final
String TAG =
"DaemonService"
;
public
static
final
int
NOTICE_ID =
100
;
@Nullable
@Override
public
IBinder onBind(Intent intent) {
return
null
;
}
@Override
public
void
onCreate() {
super
.onCreate();
if
(Contants.DEBUG)
Log.d(TAG,
"DaemonService---->onCreate被调用,启动前台service"
);
//如果API大于18,需要弹出一个可见通知
if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
Notification.Builder builder =
new
Notification.Builder(
this
);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle(
"KeepAppAlive"
);
builder.setContentText(
"DaemonService is runing..."
);
startForeground(NOTICE_ID,builder.build());
// 如果觉得常驻通知栏体验不好
// 可以通过启动CancelNoticeService,将通知移除,oom_adj值不变
Intent intent =
new
Intent(
this
,CancelNoticeService.
class
);
startService(intent);
}
else
{
startForeground(NOTICE_ID,
new
Notification());
}
}
@Override
public
int
onStartCommand(Intent intent,
int
flags,
int
startId) {
// 如果Service被终止
// 当资源允许情况下,重启service
return
START_STICKY;
}
@Override
public
void
onDestroy() {
super
.onDestroy();
// 如果Service被杀死,干掉通知
if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
NotificationManager mManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
mManager.cancel(NOTICE_ID);
}
if
(Contants.DEBUG)
Log.d(TAG,
"DaemonService---->onDestroy,前台service被杀死"
);
// 重启自己
Intent intent =
new
Intent(getApplicationContext(),DaemonService.
class
);
startService(intent);
}
}
讲解一下,这里还用到了两个技巧:
- 一是在onStartCommand方法中返回START_STICKY,其作用是当Service进程被kill后,系统会尝试重新创建这个Service,且会保留Service的状态为开始状态,但不保留传递的Intent对象,onStartCommand方法一定会被重新调用;
- 其二在onDestory方法中重新启动自己,也就是说,只要Service在被销毁时走到了onDestory这里我们就重新启动它。
CancelNoticeService.java:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/** 移除前台Service通知栏标志,这个Service选择性使用
*
* Created by jianddongguo on 2017/7/7.
*/
public
class
CancelNoticeService
extends
Service {
@Nullable
@Override
public
IBinder onBind(Intent intent) {
return
null
;
}
@Override
public
void
onCreate() {
super
.onCreate();
}
@Override
public
int
onStartCommand(Intent intent,
int
flags,
int
startId) {
if
(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2){
Notification.Builder builder =
new
Notification.Builder(
this
);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(DaemonService.NOTICE_ID,builder.build());
// 开启一条线程,去移除DaemonService弹出的通知
new
Thread(
new
Runnable() {
@Override
public
void
run() {
// 延迟1s
SystemClock.sleep(
1000
);
// 取消CancelNoticeService的前台
stopForeground(
true
);
// 移除DaemonService弹出的通知
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.cancel(DaemonService.NOTICE_ID);
// 任务完成,终止自己
stopSelf();
}
}).start();
}
return
super
.onStartCommand(intent, flags, startId);
}
@Override
public
void
onDestroy() {
super
.onDestroy();
}
}
AndroidManifest.xml:
1
2
3
4
5
6
7
8
9
<
service
android:name
=
".service.DaemonService"
android:enabled
=
"true"
android:exported
=
"true"
android:process
=
":daemon_service"
/>
<
service
android:name
=
".service.CancelNoticeService"
android:enabled
=
"true"
android:exported
=
"true"
android:process
=
":service"
/>
讲解一下:
从所周知,一个Service没有自己独立的进程,它一般是作为一个线程运行于它所在的应用进程中,且应用进程名称与包名一致。如果希望指定的组件和应用运行在指定的进程中,就需要通过android:process属性来为其创建一个进程,因此android:process=":daemon_service"就是让DaemonService运行在名为“com.jiangdg.keepappalive:daemon_service”进程中;android:enabled属性的作用是Android系统是否实例化应用程序中的组件;android:exported属性的作用是当前组件(Service)是否可以被包含本身以外的应用中的组件启动。
测试结果:
接下来,我们观察下KeepAppAlive进程的oom_adj值变化。
首先,adb查看KeepAppAlive进程的进程号:
1
2
3
E:\Android\StudioProject\KeepAppAlive>adb shell
shell@trltechn:/ $
su
root@trltechn:/
# ps | grep jiangdg
其次,观察KeepAppAlive进程在不同状态下的oom_adj值:
1
2
root@trltechn:/
# cat /proc/15689/oom_adj
root@trltechn:/
# cat /proc/16033/oom_adj
注意:如果执行su命令,提示"/system/bin/sh: su: not found",说明手机设备没有被root。ps命令用于显示静态进程状态,top命令可以对进程进行实时监控,每次启动KeepAppAlive进程号都不一样。
9、APP进程防杀方案第二阶:监听锁屏广播,“制造‘1像素’惨案”
先看测试Demo的源码。
ScreenManager.java:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**1像素管理类
*
* Created by jianddongguo on 2017/7/8.
*/
public
class
ScreenManager {
private
static
final
String TAG =
"ScreenManager"
;
private
Context mContext;
private
static
ScreenManager mSreenManager;
// 使用弱引用,防止内存泄漏
private
WeakReference<Activity> mActivityRef;
private
ScreenManager(Context mContext){
this
.mContext = mContext;
}
// 单例模式
public
static
ScreenManager getScreenManagerInstance(Context context){
if
(mSreenManager ==
null
){
mSreenManager =
new
ScreenManager(context);
}
return
mSreenManager;
}
// 获得SinglePixelActivity的引用
public
void
setSingleActivity(Activity mActivity){
mActivityRef =
new
WeakReference<>(mActivity);
}
// 启动SinglePixelActivity
public
void
startActivity(){
if
(Contants.DEBUG)
Log.d(TAG,
"准备启动SinglePixelActivity..."
);
Intent intent =
new
Intent(mContext,SinglePixelActivity.
class
);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
// 结束SinglePixelActivity
public
void
finishActivity(){
if
(Contants.DEBUG)
Log.d(TAG,
"准备结束SinglePixelActivity..."
);
if
(mActivityRef !=
null
){
Activity mActivity = mActivityRef.get();
if
(mActivity !=
null
){
mActivity.finish();
}
}
}
}
讲解一下:
Java中为对象的引用分了四个级别:强引用、软引用、弱引用、虚引用。这里,我们使用了弱引用WeakReference来防止内存泄漏,为了解释这个问题,我们举这么一个例子:有两个类class A和class B,分别实例化这两个类得到a,b,其中a又作为实例化B时传入的构造参数,代码如下:
1
2
A a =
new
A();
B b =
new
B(a);
SinglePixelActivity.java:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**1像素Activity
*
* Created by jianddongguo on 2017/7/8.
*/
public
class
SinglePixelActivity
extends
AppCompatActivity {
private
static
final
String TAG =
"SinglePixelActivity"
;
@Override
protected
void
onCreate(
@Nullable
Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
if
(Contants.DEBUG)
Log.d(TAG,
"onCreate--->启动1像素保活"
);
// 获得activity的Window对象,设置其属性
Window mWindow = getWindow();
mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
WindowManager.LayoutParams attrParams = mWindow.getAttributes();
attrParams.x =
0
;
attrParams.y =
0
;
attrParams.height =
1
;
attrParams.width =
1
;
mWindow.setAttributes(attrParams);
// 绑定SinglePixelActivity到ScreenManager
ScreenManager.getScreenManagerInstance(
this
).setSingleActivity(
this
);
}
@Override
protected
void
onDestroy() {
if
(Contants.DEBUG)
Log.d(TAG,
"onDestroy--->1像素保活被终止"
);
if
(! SystemUtils.isAppAlive(
this
,Contants.PACKAGE_NAME)){
Intent intentAlive =
new
Intent(
this
, SportsActivity.
class
);
intentAlive.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentAlive);
Log.i(TAG,
"SinglePixelActivity---->APP被干掉了,我要重启它"
);
}
super
.onDestroy();
}
}
讲解一下:
在UI界面架构中,每个Activity都包含一个Window对象,在Android中Window对象通常由PhoneWindow来实现,PhoneWindow将一个DecorView设置为整个应用窗口的根View,它作为窗口界面的顶层视图,封装了很多通用操作窗口的方法...好了,不扯远了,既然我们已经知道Window对象在一个Activity中的位置,这里我们通过getWindow方法来获得SinglePixelActivity 的Window对象,然后为其设置相关属性,比如窗体的大小、位置、坐标等,来达到所需的"1像素"界面效果。
SportsActivity.java:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/** 运动界面,启动监听锁屏广播,判断是否开关1像素界面
*
* Created by jianddongguo on 2017/7/7.
*/
public
class
SportsActivity
extends
AppCompatActivity {
// 动态注册锁屏等广播
private
ScreenReceiverUtil mScreenListener;
// 1像素Activity管理类
private
ScreenManager mScreenManager;
// 代码省略...
private
ScreenReceiverUtil.SreenStateListener mScreenListenerer =
new
ScreenReceiverUtil.SreenStateListener() {
@Override
public
void
onSreenOn() {
// 移除"1像素"
mScreenManager.finishActivity();
}
@Override
public
void
onSreenOff() {
// 接到锁屏广播,将SportsActivity切换到可见模式
// "咕咚"、"乐动力"、"悦动圈"就是这么做滴
// Intent intent = new Intent(SportsActivity.this,SportsActivity.class);
// startActivity(intent);
// 如果你觉得,直接跳出SportActivity很不爽
// 那么,我们就制造个"1像素"惨案
mScreenManager.startActivity();
}
@Override
public
void
onUserPresent() {
// 解锁,暂不用,保留
}
};
@Override
protected
void
onCreate(
@Nullable
Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_sports);
if
(Contants.DEBUG)
Log.d(TAG,
"--->onCreate"
);
// 1. 注册锁屏广播监听器
mScreenListener =
new
ScreenReceiverUtil(
this
);
mScreenManager = ScreenManager.getScreenManagerInstance(
this
);
mScreenListener.setScreenReceiverListener(mScreenListenerer);
}
// 代码省略...
}
AndroidManifest.xml:
1
2
3
4
5
6
7
8
<
activity
android:name
=
".SportsActivity"
android:launchMode
=
"singleTask"
/>
<
activity
android:name
=
".SinglePixelActivity"
android:configChanges
=
"keyboardHidden|orientation|screenSize|navigation|keyboard"
android:excludeFromRecents
=
"true"
android:finishOnTaskLaunch
=
"false"
android:launchMode
=
"singleInstance"
android:theme
=
"@style/SingleActivityStyle"
/>
讲解一下:
- 1)android:launchMode属性:用于指定activity的启动模式,总共分为四种,即:
- standar模式,每次启动activity都会创建其实例,并加入到任务栈的栈顶;
- singleTop模式,每次启动activity如果栈顶时该activity则无需创建,其余情况都要创建该activity的实例;
- singleTask模式,如果被启动的activity的实例存在栈中,则不需要创建,只需要把此activity加入到栈顶,并把该activity以上的activity实例全部pop;
- singleInstance模式:将创建的activity实例放入单独的栈中,该栈只能存储这个实例,且是作为共享实例存在。 - 2)android:configChanges属性:用于捕获手机状态的改变,即当手机状态(如切换横竖屏、屏幕大小)改变时会保存当前活动状态重启Activity,由于SinglePixelActivity肩负着保活的特殊使命,这里使用android:configChanges属性:防止Activity重启,它只是调用了onConfigurationChanged(Configuration newConfig)来通知手机状态的改变;
- 3)android:excludeFromRecents属性:用于控制SinglePixelActivity不在最近任务列表中显示;
- 4)android:finishOnTaskLaunch属性:用于标记当用户再起启动应用(TASK)时是否关闭已经存在的Activity的实例,false表示不关闭;
- 5)android:theme属性:用于指定Activity显示主题,这里我们自定义主题SingleActivityStyle。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<
style
name
=
"SingleActivityStyle"
parent
=
"horizontal_slide"
>
<!-- 窗体背景颜色为透明 -->
<
item
name
=
"android:windowBackground"
>@android:color/transparent</
item
>
<!-- 窗体没有边框 -->
<
item
name
=
"android:windowFrame"
>@null</
item
>
<!-- 窗体不包含标题栏 -->
<
item
name
=
"android:windowNoTitle"
>true</
item
>
<!-- 窗体悬浮 -->
<
item
name
=
"android:windowIsFloating"
>true</
item
>
<!-- 自定义TitleBar时去掉多余的阴影-->
<
item
name
=
"android:windowContentOverlay"
>@null</
item
>
<!-- 不允许窗体背景变暗-->
<
item
name
=
"android:backgroundDimEnabled"
>false</
item
>
<!-- 窗体切换无动画-->
<
item
name
=
"android:windowAnimationStyle"
>@null</
item
>
<!-- 禁用窗口的预览动画-->
<
item
name
=
"android:windowDisablePreview"
>true</
item
>
<
item
name
=
"android:windowNoDisplay"
>false</
item
>
</
style
>
测试结果:
监听锁屏广播,锁屏时将SportActivity置于前台(可见) :
监听锁屏广播,锁屏时开启SinglePixelActivity(1像素):
9、APP进程防杀方案第三阶:循环播放一段无声音频,"打造金刚不坏之身"
对于三星C9、Note4和华为4X来说,结合前台Service和悬浮界面(1像素)的保活方式,在用户不主动清理或强杀的情况下,测试APP的保活效果还是非常不错的。
但是,对于华为Mate8来说,效果还是差强人意,尤其是当使用一键清理内存时,测试APP基本无法幸存。然后,"咕咚"却奇妙的活了下来,一键清理怎么也清不掉,正当自己百思不得其"姐"时,一个"恶心"的界面出现在我面前。尼玛!看到下面的红框框没,"咕咚"居然在后台循环播放一个无声音乐,难怪生命力这么旺盛,但是耗电也是杠杠的。
好吧,不纠结这么多,这里只是从学技术的角度出发而研究,毕竟用户对耗电量还是很敏感的,不到万不得已还是收敛点,不要这么"风骚",用户体验很重要,一不小心就"泻"了你。
看咕咚这无声音乐播放保活方式,够不要脸吧:
PlayerMusicService.java:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**循环播放一段无声音频,以提升进程优先级
*
* Created by jianddongguo on 2017/7/11.
*/
public
class
PlayerMusicService
extends
Service {
private
final
static
String TAG =
"PlayerMusicService"
;
private
MediaPlayer mMediaPlayer;
@Nullable
@Override
public
IBinder onBind(Intent intent) {
return
null
;
}
@Override
public
void
onCreate() {
super
.onCreate();
if
(Contants.DEBUG)
Log.d(TAG,TAG+
"---->onCreate,启动服务"
);
mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
mMediaPlayer.setLooping(
true
);
}
@Override
public
int
onStartCommand(Intent intent,
int
flags,
int
startId) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
startPlayMusic();
}
}).start();
return
START_STICKY;
}
private
void
startPlayMusic(){
if
(mMediaPlayer !=
null
){
if
(Contants.DEBUG)
Log.d(TAG,
"启动后台播放音乐"
);
mMediaPlayer.start();
}
}
private
void
stopPlayMusic(){
if
(mMediaPlayer !=
null
){
if
(Contants.DEBUG)
Log.d(TAG,
"关闭后台播放音乐"
);
mMediaPlayer.stop();
}
}
@Override
public
void
onDestroy() {
super
.onDestroy();
stopPlayMusic();
if
(Contants.DEBUG)
Log.d(TAG,TAG+
"---->onCreate,停止服务"
);
// 重启
Intent intent =
new
Intent(getApplicationContext(),PlayerMusicService.
class
);
startService(intent);
}
}
AndroidManifest.xml:
1
2
3
4
<
service
android:name
=
".service.PlayerMusicService"
android:enabled
=
"true"
android:exported
=
"true"
android:process
=
":music_service"
/>
测试结果:
这里在cmd窗口使用"ps | grep jiangdg"命令,如果进程在内存中存在,则打印进程信息;如果不存在,则没有信息。
各机型测试情况如下:
- 1)华为Mate8(Android 7.0):将测试APP置于后台,前台Service在黑屏状态下1分钟之内被干掉,"1像素"悬浮Activity在黑屏状态下测试2小时依然存活,效果还可以。但是,当用户一键清理最近应用时,会被杀死,当在后台开启Serive循环播放一段无声音频时,一键清理依然存活,在置于后台的黑屏模式下存活12小时以上;
- 2)三星C9(Android 6.0):开启前台Service和1像素,KeepAppAlive在黑屏后台模式下存活9个小时以上,看样子原生系统还是温柔些;开启后台播放音频服务,用户一键清理最近应用成功保活;
- 3)华为4X(Android 6.0):效果同C9;
- 4)三星Note4(Android 5.0):效果同C9。
注:Mate8循环播放一段无声音频,当用户点击一键清理最近应用时,KeepAppAlive不会被干掉,但是如果用户只选择清理KeepAppAlive时,也会被杀死,这与"咕咚"保活效果一致。
三星C9(Android 6.0):运行Demo,后台黑屏保活效果
华为Mate8(Android 7.0):运行Demo,黑屏和一键清理保活效果
10、源码下载
KeepingAppAlive-master(52im.net).zip(1.29 MB , 下载次数: 25 , 售价: 3 金币)
11、下篇预告
下篇将介绍Android6.0及以上版本的APP进程被杀后的复活方法,敬请期待!
(原文链接:点此进入)
来源:即时通讯网 - 即时通讯开发者社区!
标签:进程保活
本主题由 JackJiang 于4 天前 加入精华
本帖已收录至以下技术专辑
- IM/推送心跳保活篇|
- 微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
- 微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
- 为什么说基于TCP的移动端IM仍然需要心跳保活?
- Android端消息推送总结:实现原理、心跳保活、遇到的问题等
- Android后台保活实践总结:即时通讯应用无法根治的“顽疾”
- Android进程保活详解:一篇文章解决你的所有疑问
- 一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)
- 应用保活终极总结(一):Android6.0以下的双进程守护保活实践
- 应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)
MobileIMSDK(v3.0精编版)
轻量级开源移动端即时通讯框架。
快速入门/性能/指南/提问
MobileIMSDK-Web(有偿开源)
轻量级Web端即时通讯框架。
详细介绍/精编源码/手册教程
RainbowAVnew(有偿开源)
移动端实时音视频框架。
详细介绍/性能测试/安装体验
RainbowChat(技术转让)
基于MobileIMSDK的移动IM系统。
详细介绍/产品截图/安装体验
阅读全文
0 0
- 应用保活终极总结(二):Android6.0及以上的保活实践
- 探讨Android6.0及以上系统APP保活实现
- 探讨Android6.0及以上系统APP常驻内存(保活)实现-争宠篇
- 探讨Android6.0及以上系统APP常驻内存(保活)实现-复活篇
- 探讨Android6.0及以上系统APP常驻内存(保活)实现-复活篇
- 探讨Android6.0及以上系统APP常驻内存(保活)实现-争宠篇
- Android5.0以上app进程保活的正确姿势
- 应用保活套路
- JobService和JobScheduler机制在Android5.0以上保活
- JobService和JobScheduler机制在Android5.0以上保活
- JobService和JobScheduler机制在Android5.0以上保活
- 进程的保活
- 安卓应用保活实践(双进程守护)
- android应用保活机制
- Android 应用保活笔记
- android应用保活1
- Android 进程保活总结
- Android进程保活总结
- MyEclipse导入jquery-1.8.0.min.js等文件报错的解决方案 版权声明:本文为博主原创文章,未经博主允许不得转载。 MyEclipse导入jQuery-1.8.0.min.j
- leetcode 51
- Android两个控件叠在一起,如何让被挡住的控件显示出来
- java线程之生产者与消费者模式
- 页面居中、响应式布局的总结
- 应用保活终极总结(二):Android6.0及以上的保活实践
- C语言Linux环境下编程的基本操作
- 三子棋
- Spring中bean的理解
- Android的IPC机制
- 【网络编程】高性能网络编程之reactor反应堆与定时器管理
- 数据中心怎么关机?光有UPS还不够
- 常用脚本
- VirtualBox – STATUS_OBJECT_NAME_NOT_FOUND