最近公司项目需求,需要给应用加入进程保活。
这里简述一下需求,由于App应用对接了蓝牙接收实时数据,并且数据量很大;
用户在操作App获取实时数据的时候,不可能一直看着手机屏幕,
这时候手机一般会有黑屏或者锁屏的情况,有时候就会导致数据丢失;
在我做的这款应用中,数据是非常重要的,所以数据丢失也是很致命的问题!
在如何处理这件问题上,自己也翻阅了很多资料。
毕竟这个问题已经很早就有人解决或者说处理过,但是网上资料过于多,并且杂,实用性也是有待考虑的,今天总结一下我自己应用过后,效果并且结果很nice的方案!
Android 进程保活
- 进程保活
- 进程初步了解
- 如何查看进程基本信息
- 进程划分
- 前台进程(Foreground process)
- 可见进程(Visible process)
- 服务进程(Service process)
- 后台进程(Background process)
- 空进程(Empty process)
- 内存阈值
- 进程保活方案
- 1像素原理:
- 实战操作:
- 创建一个1像素的Activity
- 设置当前主题(异常)
- 监听系统锁屏广播
- 活动页调用
- 实测结果
进程保活
对于解决这个问题,总共有如下几个技术:
1像素Activity、前台服务、账号同步、Jobscheduler、相互唤醒、系统服务捆绑,但是我在这里应用的是1像素Activity
。
进程初步了解
每一个Android应用启动后至少对应一个进程,有的是多个进程,而且主流应用中多个进程的应用比例较大。
如何查看进程基本信息
adb shell ps|grep <package_name>
23924 进程ID
23902 进程的父进程ID
com.harry.glucose.application 进程名
进程划分
Android 中的进程跟封建社会一样,分了三流九等,Android 系统把进程的划为了如下几种(重要性从高到低),网上多位大神都详细总结过(备注:严格来说是划分了 6 种)。
前台进程(Foreground process)
可见场景:
- 某个进程持有一个正在与用户交互的Activity并且该Activity正处于resume的状态。
- 某个进程持有一个Service,并且该Service与用户正在交互的Activity绑定。
- 某个进程持有一个Service,并且该Service调用startForeground()方法使之位于前台运行。
- 某个进程持有一个Service,并且该Service正在执行它的某个生命周期回调方法,比如onCreate()、
onStart()或onDestroy()。 - 某个进程持有一个BroadcastReceiver,并且该BroadcastReceiver正在执行其onReceive()方法。
用户正在使用的程序,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等极端情况会杀死。
可见进程(Visible process)
使用场景:
- 拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
- 拥有绑定到可见(或前台)Activity 的 Service。
用户正在使用,看得到,但是摸不着,没有覆盖到整个屏幕,只有屏幕的一部分可见进程不包含任何前台组件,一般系统也是不会杀死可见进程的,除非要在资源吃紧的情况下,要保持某个或多个前台进程存活。
服务进程(Service process)
场景:
- 某个进程中运行着一个Service且该Service是通过startService()启动的,与用户看见的界面没有直接关联。
在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死。
后台进程(Background process)
场景:
- 在用户按了"back"或者"home"后,程序本身看不到了,但是其实还在运行的程序,比如Activity调用了onPause方法系统可能随时终止它们,回收内存。
空进程(Empty process)
- 某个进程不包含任何活跃的组件时该进程就会被置为空进程,完全没用,杀了它只有好处没坏处,第一个干它!
内存阈值
上面是进程的分类,进程是怎么被杀的呢?
系统出于体验和性能上的考虑,app 在退到后台时系统并不会真正的 kill 掉这个进程,而是将其缓存起来。
打开的应用越多,后台缓存的进程也越多。
在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要 kill 掉哪些进程,以腾出内存来供给需要的 app, 这套杀进程回收内存的机制就叫 Low Memory Killer。
那这个不足怎么来规定呢,那就是内存阈值,我们可以使用cat /sys/module/lowmemorykiller/parameters/minfree 来查看某个手机的内存阈值。
其实系统在进程回收跟内存回收类似也是有一套严格的策略,可以自己去了解,大概是这个样子的,oom_adj 越大,占用物理内存越多会被最先 kill 掉,OK,那么现在对于进程如何保活这个问题就转化成,如何降低 oom_adj 的值,以及如何使得我们应用占的内存最少
。
进程保活方案
上面几种解决办法,最终我在项目应用中使用的是1像素Activity.
1像素原理:
基本思想,系统一般是不会杀死前台进程的。所以使得进程常驻,可以通过白名单;
但是这里我们在使用1像素的时候,只需要在锁屏或者黑屏的时候,在本进程开启一个activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动态。在开屏幕的时候,把这个Activity关闭掉。
如果直接启动一个Activity,当我们按下back键返回桌面的时候,oom_adj的值是8
,上面已经提到过,这个进程在资源不够的情况下是容易被回收的。
实战操作:
通过监听系统锁屏广播,来实现进程保活的方案。
创建一个1像素的Activity
/**
* @author 拉莫帅
* @date 2023/2/2
* @address
* @Desc 1像素
*/
public class PixelActivity extends Activity {
private static final String TAG = PixelActivity.class.getSimpleName();
public static void actionToLiveActivity(Context pContext){
Intent intent = new Intent(pContext, PixelActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pContext.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pixel);
Log.e(TAG, "onCreate: " );
Window window = getWindow();
//放到左上角
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams attributes = window.getAttributes();
//宽高设计为1个像素
attributes.width = 1;
attributes.height = 1;
//起始坐标
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);
ScreenManager.getInstance(this).setActivity(this);
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart: " );
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: " );
}
}
设置当前主题(异常)
这里还有一步很重要,一定要给当前1像素Activity设置主题,要不然会有黑屏
的情况出现,下一篇文章会给大家总结一下。
<!-- 设置1像素主题-->
<style name="APPTheme" parent="@android:style/Theme.Holo.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowNoTitle">true</item>
</style>
AndroidManifest.xml
<activity android:name=".PixelActivity"
android:theme="@style/APPTheme"/>
监听系统锁屏广播
在屏幕关闭的时候把 PixelActivity 启动起来,在开屏的时候把 PixelActivity关闭掉,所以要监听系统锁屏广播,以接口的形式通知 MainActivity 启动或者关闭 PixelActivity。
/**
* @author 拉莫帅
* @date 2023/2/2
* @address
* @Desc 监听系统开/锁屏广播
*/
public class ScreenBroadcastListener {
private Context mContext;
private ScreenBroadcastReceiver mScreenReceiver;
private ScreenStateListener mListener;
public ScreenBroadcastListener(Context context) {
mContext = context.getApplicationContext();
mScreenReceiver = new ScreenBroadcastReceiver();
}
interface ScreenStateListener {
void onScreenOn();
void onScreenOff();
}
/**
* screen状态广播接收者
*/
private class ScreenBroadcastReceiver extends BroadcastReceiver {
private String action = null;
@Override
public void onReceive(Context context, Intent intent) {
action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
mListener.onScreenOn();
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
mListener.onScreenOff();
}
}
}
public void registerListener(ScreenStateListener listener) {
mListener = listener;
registerListener();
}
private void registerListener() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, filter);
}
}
public class ScreenManager {
private Context mContext;
private WeakReference<Activity> mActivityWref;
public static ScreenManager gDefualt;
public static ScreenManager getInstance(Context pContext) {
if (gDefualt == null) {
gDefualt = new ScreenManager(pContext.getApplicationContext());
}
return gDefualt;
}
private ScreenManager(Context pContext) {
this.mContext = pContext;
}
public void setActivity(Activity pActivity) {
mActivityWref = new WeakReference<Activity>(pActivity);
}
public void startActivity() {
PixelActivity.actionToLiveActivity(mContext);
}
public void finishActivity() {
//结束掉LiveActivity
if (mActivityWref != null) {
Activity activity = mActivityWref.get();
if (activity != null) {
activity.finish();
}
}
}
}
活动页调用
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this);
ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
@Override
public void onScreenOn() {
screenManager.finishActivity();
}
@Override
public void onScreenOff() {
screenManager.startActivity();
}
});
}
}
在屏幕关闭的时候把PixelActivity启动起来,在开屏的时候把PixelActivity关闭掉,所以要监听系统锁屏广播,以接口的形式通知MainActivity启动或者关闭PixelActivity。
实测结果
最终运行到真机华为、小米、红米Android 6.0、9.0、11.0、12.0结果ojbk,后期鸿蒙真机有待调试一下看看结果。
最近忙里偷闲,↓↓↓↓【谁家de码农陈先生】↓↓↓↓,里面定时给大家分享技术博文、前方高能资讯内容!欢迎各位老板点赞关注,你们就是我的动力源泉!