Android 进程保活(一)

在这里插入图片描述

最近公司项目需求,需要给应用加入进程保活。
这里简述一下需求,由于App应用对接了蓝牙接收实时数据,并且数据量很大;
用户在操作App获取实时数据的时候,不可能一直看着手机屏幕,
这时候手机一般会有黑屏或者锁屏的情况,有时候就会导致数据丢失;
在我做的这款应用中,数据是非常重要的,所以数据丢失也是很致命的问题!

Android 进程保活(一)

Android进程保活————启动页黑屏及最优解决方案(二)

在如何处理这件问题上,自己也翻阅了很多资料。
毕竟这个问题已经很早就有人解决或者说处理过,
但是网上资料过于多,并且杂,实用性也是有待考虑的,
今天总结一下我自己应用过后,效果并且结果很nice的方案!


进程保活

对于解决这个问题,总共有如下几个技术:

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)

可见场景:

  1. 某个进程持有一个正在与用户交互的Activity并且该Activity正处于resume的状态。
  2. 某个进程持有一个Service,并且该Service与用户正在交互的Activity绑定。
  3. 某个进程持有一个Service,并且该Service调用startForeground()方法使之位于前台运行。
  4. 某个进程持有一个Service,并且该Service正在执行它的某个生命周期回调方法,比如onCreate()、
    onStart()或onDestroy()。
  5. 某个进程持有一个BroadcastReceiver,并且该BroadcastReceiver正在执行其onReceive()方法。

用户正在使用的程序,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等极端情况会杀死。

可见进程(Visible process)

使用场景:

  1. 拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
  2. 拥有绑定到可见(或前台)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 的值,以及如何使得我们应用占的内存最少

每个手机的厂商可能不一样,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,后期鸿蒙真机有待调试一下看看结果。

番外篇

还有另外一种方案,但是个人不是很推荐;
如果活动页功能多,加入此方案不仅耗时,还耗电。

循环播放无声音乐

网上随便找一个无声的音乐,主要的让其不断的循环播放音乐,同为测试真机华为P9,7.0,EMUI 5.0。

主要功能:播放音乐

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                startPlayMusic();
            }
        }).start();
        return START_STICKY;
    }


       最近忙里偷闲,↓↓↓↓【谁家de码农陈先生】↓↓↓↓,里面定时给大家分享技术博文、前方高能资讯内容!欢迎各位老板点赞关注,你们就是我的动力源泉!

猜你喜欢

转载自blog.csdn.net/chen_md/article/details/128867458