Android中利用前台服务白色保活

进程优先级

进程

我们都知道,每一个APP进程(process)都拥运行在独立的虚拟机(virtual machine)中,这样就保证了每个APP的独立。在系统资源紧张的时候会选择一部分进程杀掉释放内存,那么系统是如何选取杀掉哪些留下哪些呢?这就涉及到进程的优先级。

优先级

为了对运行的进程进行统一管理,系统根据进程的不同状态进行了分离,分为如下五个状态:

前台进程 (Foreground process)

可与与用户交互的进程,满足以下的都是:

  • 有可与用户交互的Activity(已调onResume());
  • 有广播接收者(BroadcastReceiver)正在接收广播;
  • 有服务(Service)正在执行它的回调方法(Service.onCreate(), Service.onStart(), or Service.onDestroy());
  • 有Service调用了startForeground()方法使之位于前台运行。

前台进程系统是不会销毁的,除非极端情况。

可见进程 (Visible process)

  • 有不在前台、但仍对用户可见的 Activity(已调用 onPause());
  • 有绑定到可见(或前台)Activity 的 Service;

一般系统也不会回收可见进程的,除非要保持某个或多个前台进程存活而且资源吃紧的情况下。

服务进程 (Service process)

  • 运行着一个通过startService()启动的Service;

在内存不够维持所有前台进程与可见进程运行时,服务进程会被销毁。

后台进程 (Background process)

  • 用户返回到界面或切换到其他APP,看不到但是还在运行的程序。

可能随时被系统销毁,回收内存。

空进程 (Empty process)

  • 没有任何活跃组件的进程;

最容易被销毁的进程

进程查看

获取进程pid

通过adb shell ps查看进程

B0000AS212345L:~ zwenkai$ adb shell ps
USER     PID   PPID  VSIZE  RSS     WCHAN    PC        NAME
root      1     0     932    688   ffffffff 00000000 S /init
root      2     0     0      0     ffffffff 00000000 S kthreadd
root      3     2     0      0     ffffffff 00000000 S ksoftirqd/0
root      6     2     0      0     ffffffff 00000000 S migration/0
... ...

由于进行信息太多,可以进行过滤下,比如我们只想查手百的进程ID,尽管不知道它的包名是什么,但一定包含baidu。ps|grep

B0000AS212345L:~ zwenkai$ adb shell ps | grep baidu
USER     PID   PPID  VSIZE  RSS     WCHAN    PC        NAME
u0_a201   9610  165   1387724 143820 ffffffff 00000000 S com.baidu.searchbox
u0_a201   10452 165   1052784 45944 ffffffff 00000000 S com.baidu.searchbox:megappInstaller
USERPIDPPIDVSIZERSSWCHANPCNAME
用户进程ID父进程ID虚拟内存大小实际内存大小0:代表正在运行Program Counter进程名

可以看到它的进程ID为9610

查看进程优先级

通过上一步找到进程ID为9610,在proc目录下找到对应的进程ID文件夹,查看里面的oom_adj就可以获取到进程的优先级了。

B0000AS212345L:~ zwenkai$ adb shell
shell@hwp7:/ $ cat proc/9610/oom_adj
0

可以看到值为0手百目前在前台,把它后台之后再看一下。

shell@hwp7:/ $ cat proc/9610/oom_adj
6

那这里的06分别代表什么呢?具体定义可以在com.android.server.am.ProcessList查看:

常量名称级别(android M)级别(android N)描述
NATIVE_ADJ-17-1000native进程
SYSTEM_ADJ-16-900系统进程
PERSISTENT_PROC_ADJ-12-800系统persistent进程,如telephony
PERSISTENT_SERVICE_ADJ-11-700绑定系统进程或persistent进程的服务
FOREGROUND_APP_ADJ00前台进程,正在前台运行的APP
VISIBLE_APP_ADJ1100可见进程
PERCEPTIBLE_APP_ADJ2200用户可感知进程,如音乐播放
BACKUP_APP_ADJ3300正在执行备份的进程
HEAVY_WEIGHT_APP_ADJ4400高权重进程
SERVICE_ADJ5500服务进程
HOME_APP_ADJ6600与Home交互的进程
PREVIOUS_APP_ADJ7700切换进程
CACHED_APP_MIN_ADJ9900缓存进程即空进程
CACHED_APP_MAX_ADJ15906缓存进程即空进程
UNKNOWN_ADJ161001未知进程

其他APP进程分析

看下其他APP的进程是怎么搞的,这里挑几款一定有Server后台进程的。

网易云音乐

Service声明
<service
    android:name="com.netease.play.player.service.VideoPlayService"
    android:process=":play" />
进程ID
1|shell@hwp7:/ $ ps | grep netease
u0_a87    16029 165   1142056 104776 ffffffff 00000000 R com.netease.cloudmusic
u0_a87    16084 165   1072908 68608 ffffffff 00000000 S com.netease.cloudmusic:browser
u0_a87    16178 165   1058940 61900 ffffffff 00000000 S com.netease.cloudmusic:play
进程优先级

有三个进程,对应进程优先级:

状态主进程play进程browser进程
前台(未播放)007
后台(未播放)647
前台(播放)007
后台(播放)617

通过以上前后台变化验证了猜想,play进程在播放音乐的时候尽管后台,它的进程优先级是1,是不会被销毁的。

喜马拉雅

Service 声明
<service
    android:name="com.ximalaya.ting.android.opensdk.player.service.XmPlayerService"
    android:process=":player">
    <intent-filter
        android:priority="1000">
        <action
            android:name="com.ximalaya.ting.android.mainapp.player.service.XmPlayerService" />
    </intent-filter>
</service>
进程ID
shell@hwp7:/ $ ps | grep ximalaya
u0_a92    16944 165   1245432 156184 ffffffff 00000000 S com.ximalaya.ting.android
u0_a92    16979 165   1029400 59772 ffffffff 00000000 S com.ximalaya.ting.android:player
进程优先级

有两个进程,对应进程优先级:

状态主进程player进程
前台(未播放)00
后台(未播放)11
前台(播放)00
后台(播放)11

player进程后台播放时是符合预期的,发现它NB的是后台时主进程进程优先级竟然也是1,这里怀疑它进行了黑操作。

创建前台服务

TestService

class TestService : Service() {

    private val NOTICE_ID = 100

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        val builder = Notification.Builder(this)
        builder.setSmallIcon(R.mipmap.ic_launcher)
        builder.setContentTitle("TestService")
        builder.setContentText("TestService is running...")
        startForeground(NOTICE_ID, builder.notification)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 如果Service被终止, 当资源允许情况下,重启service
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        // 如果Service被杀死,干掉通知
        val mManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        mManager.cancel(NOTICE_ID)
        // 重启
        val intent = Intent(applicationContext, TestService::class.java)
        startService(intent)
    }

}

注册Service

<service android:name=".TestService"
    android:process=":test"/>

开启服务

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        startService(Intent(this, TestService::class.java))
    }
}

查看进程ID

1|shell@hwp7:/ $ ps | grep kevin
u0_a93    24386 165   999248 44576 ffffffff 00000000 S com.kevin.foregroundservice
u0_a93    24406 165   976268 29064 ffffffff 00000000 S com.kevin.foregroundservice:test

进程优先级

有两个进程,对应进程优先级:

状态主进程test进程
前台01
后台61

和分析的两款APP还是有差距,就是前台的时候我们的test进程优先级是1而不是0,其实通过bindService的方式创建就可以啦。

参考

  1. Processes and Application Lifecycle
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android前台服务保活是指通过将服务设置为前台服务,使其在系统资源紧张时更难被系统杀死,从而提高服务的稳定性和可靠性。下面是一个简单的Android前台服务保活的例子: 1. 创建一个继承自Service的前台服务类,例如ForegroundService: ```java public class ForegroundService extends Service { private static final int NOTIFICATION_ID = 1; private static final String CHANNEL_ID = "ForegroundServiceChannel"; @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { createNotificationChannel(); Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Foreground Service") .setContentText("Running") .setSmallIcon(R.drawable.ic_notification) .setContentIntent(pendingIntent) .build(); startForeground(NOTIFICATION_ID, notification); // 在这里执行你的业务逻辑 return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); stopForeground(true); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Foreground Service Channel", NotificationManager.IMPORTANCE_DEFAULT); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } } ``` 2. 在AndroidManifest.xml文件注册前台服务: ```xml <service android:name=".ForegroundService" android:enabled="true" android:exported="false" /> ``` 3. 在需要启动前台服务的地方调用startService方法: ```java Intent serviceIntent = new Intent(context, ForegroundService.class); ContextCompat.startForegroundService(context, serviceIntent); ``` 这样,当启动前台服务后,系统将会将该服务标记为前台服务,并显示一个持续运行的通知,从而提高服务优先级,减少被系统杀死的概率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值