Android进程与进程保活(涉及Notification)以及app crash表现

Android进程与进程保活(涉及Notification)

由于内存限制,android系统会在内存不足时回收进程,
一、进程回收顺序
回收优先级:前台进程<可视进程<服务进程<后台进程<内容供应根节点<空进程
oom_adj越大 越可能被回收
系统进程 <0 前台进程0 可见进程1
进程级别参考:https://juejin.im/entry/58acf391ac502e007e9a0a11


1、Foreground process 前台进程

下面几种情况属于前台进程:
(1)Activity正在与用户进程交互(Activity的onResume已经被调用)
(2)与正在和用户交互的Activity绑定的Service
(3)Service运行在前台——Service中调用了startForeground函数
(4)Service正在执行生命周期回调函数(onCreate,onStart,onDestory)
(5)BroadcastReceiver正在执行onReceive方法

2、Visible process 可视进程

下面几种情况属于可视进程:
(1)Activity没有运行在前台,但是用户仍然可见(它的onPause方法被调用),例如:当前台Activity启动了一个Dialog,这样Dialog运行在前台,Activity仍然可见,属于可视进程。
(2)与一个可视的Activity绑定的服务所在的进程

3、Service process 服务进程
运行服务的进程被startService()启动,并且没有进入上面1中(3)、(4)这两种情况。例如,音乐播放、网络下载数据

4、Background process 后台进程
当Activity不可见的时候,它的进程属于后台进程(Activity的onStop方法被调用)

5、Empty process 空进程
没有包含活动应用组件的进程为空进程,也就是进程的应用组件已经运行完毕。


查看某个包的进程
Terminal 或者 cmd 进入 adb shell 模式
ps|grep 包名
第一个参数:u0_a124 当前用户
第二个参数:进程ID
第三个参数: 进程的父进程ID
第四个参数:进程的虚拟内存大小
第五个参数:实际内存大小
最后一个参数:进程名

查看某个进程的进程优先级(oom_adj)
cat /proc/进程id/oom_adj
(注意 cat后要加空格)
permission denied
要root

检查Service是否开启在前台
1 用 cat /proc/进程名/包名
查看开启前后的进程优先级变化 优先级降低了并且为0即为前台进程
2 或者
dumpsys activity services PackageName

查看 services 找到相应的services名
发现 isForeground=true 即为前台进程


进程保活常见方式

一、白色手段
开启前台Service,会在通知栏显示
通过notification方式 如音乐播放

如果希望从前台移除这个服务,只需要调用stopForeground(),一般情况我们只需要在onStartCommand里面调用 startForeground,然后再onDestroy里面调用stopForeground即可。

public class WhiteService extends Service {
    private static final String TAG = WhiteService.class.getSimpleName();
    private static final int NOTIFICATION_FLAG =0X11;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        // 在Android进行通知处理,首先需要重系统哪里获得通知管理器NotificationManager,它是一个系统Service。
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // 设置点击通知跳转的Intent
        Intent nfIntent = new Intent(this, MainActivity.class);
        // 设置 延迟Intent 
        // 最后一个参数可以为PendingIntent.FLAG_CANCEL_CURRENT 或者 PendingIntent.FLAG_UPDATE_CURRENT
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, nfIntent, 0);

        //构建一个Notification构造器
        Notification.Builder builder = new Notification.Builder(this.getApplicationContext());

        builder.setContentIntent(pendingIntent)   // 设置点击跳转界面
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
                        R.mipmap.fight_total_money2x)) // 设置下拉列表中的图标(大图标)
                .setTicker("您有一个notification")// statusBar上的提示
                .setContentTitle("这是标题") // 设置下拉列表里的标题
                .setSmallIcon(R.mipmap.fight_total_order2x) // 设置状态栏内的小图标24X24
                .setContentText("这是内容") // 设置详细内容
                .setContentIntent(pendingIntent) // 设置点击跳转的界面
                .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
				.setDefaults(Notification.DEFAULT_VIBRATE) //默认震动方式
                .setPriority(Notification.PRIORITY_HIGH)   //优先级高
                
        Notification notification = builder.build(); // 获取构建好的Notification
        
        notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
        notification.flags |= Notification.FLAG_AUTO_CANCEL; // FLAG_AUTO_CANCEL表明当通知被用户点击时,通知将被清除。
        notification.flags |= FLAG_ONGOING_EVENT; //将此通知放到通知栏的"Ongoing"即"正在运行"组中
		notification.flags |= FLAG_NO_CLEAR; //表明在点击了通知栏中的"清除通知"后,此通知不清除,常与FLAG_ONGOING_EVENT一起使用
		
		
        manager.notify(NOTIFICATION_FLAG, notification);
        // 启动前台服务
        // 参数一:唯一的通知标识;参数二:通知消息。
        startForeground(NOTIFICATION_FLAG, notification);// 开始前台服务

        Log.d(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 停止前台服务--参数:表示是否移除之前的通知
        stopForeground(true);
        Log.d(TAG, "onDestroy");
    }

}

开启服务:

Intent intent = new Intent(MainActivity.this,WhiteService.class);
        startService(intent);// (服务与开启者无联系的启动形式  )

结束服务:

 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stopService(intent);
            }
        });

更新通知:
重新创建notification.builder和notification
notificationManager调用 notify方法
其实就是再创建一个notification 只是标志和之前的那个一样 这样就会更新前面的通知了


二、灰色保活
也是开启前台Service,但是不会在通知栏显示
adb shell 模式下:
dumpsys activity services PackageName
若Service有 isForeground=true 而通知栏却没有显示 则是灰色保活方式

方式:
API < 18,启动前台Service时直接传入空的 new Notification();
API >= 18,在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除

public class GrayService extends Service {
    private static final String TAG = GrayService.class.getSimpleName();
    private final static int GRAY_SERVICE_ID = 0x12;

    public GrayService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //API 18以下,直接发送Notification并将其置为前台
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            startForeground(GRAY_SERVICE_ID, new Notification());
        } else {
            //API 18以上,发送Notification并将其置为前台后,启动InnerService
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.fight_total_money2x);
            Notification notification = builder.build(); // 获取构建好的Notification

            startForeground(GRAY_SERVICE_ID, notification);

            startService(new Intent(this, GrayInnerService.class));
        }
        Log.d(TAG, "GrayServiceOnCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 停止前台服务--参数:表示是否移除之前的通知
        stopForeground(true);
        Log.d(TAG, "onDestroy");
    }
}
public class GrayInnerService extends Service {
    private static final String TAG = GrayInnerService.class.getSimpleName();
    public static final int GRAY_INNER_SERVICE_ID=0x12;
    public GrayInnerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //发送与GrayService中ID相同的Notification,然后将其取消并取消自己的前台显示
        Notification.Builder builder = new Notification.Builder(this);
        builder.setSmallIcon(R.mipmap.fight_total_order2x);
        Notification notification = builder.build(); // 获取构建好的Notification

        startForeground(GRAY_INNER_SERVICE_ID,notification);

        // 延迟0.1s 终止掉innerService 这样 通知栏图标会清除
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                stopForeground(true);
                NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                if (manager != null) {
                    manager.cancel(GRAY_INNER_SERVICE_ID);
                }else {
                    Log.e(TAG, "notification is null!");
                }
                stopSelf();
            }
        },100);
        Log.d(TAG, "GrayInnerServiceOnCreate");
    }
}

三、黑色手段
利用不同的app进程使用广播来进行相互唤醒
(1)开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app
最新的Android N取消了 ACTION_NEW_PICTURE(拍照),ACTION_NEW_VIDEO(拍视频),CONNECTIVITY_ACTION(网络切换)等三种广播
(2)接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝
(3)app互相唤醒


四、白名单方式
系统给app加入白名单,系统不会杀死白名单中的app


后台服务

Android8.0以上 电池优化策略 应用在后台经过 idle timeout后 会禁用后台服务 stopService 并禁止启动服务

解决:
1 设置为前台服务
2 用JobIntentService 参考:https://blog.csdn.net/weixin_37577039/article/details/78495357
3 用JobService+JobScheduler

JobScheduler

1 可以推迟的非面向用户的任务(如定期数据库数据更新)
2 当充电时才希望执行的工作(如备份数据)
3 需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取内置数据)
4 希望作为一个批次定期运行的许多任务

开启时间能保证实时吗 好像有延迟

能挂多久后台

doze mode(息屏了低电耗模式) 系统不允许运行 JobScheduler

1 创建JobService

class XXXJobService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
        mXX = params.extras.getString(XX_KEY)
        开启线程执行任务
        }
 override fun onStopJob(params: JobParameters): Boolean {
        Logger.t(TAG).d("===onStop SSDPJob===")
        return false
    }
    override fun onDestroy() {
        super.onDestroy()
        Logger.t(TAG).d("===onDestroy SSDPJob===")
    }

2 通过JobScheduler开启JobService

if (mJobScheduler == null) {
            mJobScheduler = applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
        }
        val bundle = PersistableBundle()
        bundle.putString(XXKEY,XXValue)
        val builder: JobInfo.Builder = JobInfo.Builder(XX_JOB_ID,
                ComponentName(applicationContext,XXXJobService::class.java))
        builder.setMinimumLatency(latency) // 延迟lantency毫秒后开启任务
        builder.setPersisted(false) // 是否设备重启扔执行
        builder.setExtras(bundle) // 设置额外参数
        mJobScheduler?.schedule(builder.build())

3 停止JobService

mJobScheduler?.cancel(SSDP_JOB_ID)

appcrash表现

若在前台:
不包含service/broadcastReceiver,只有一个Activity,那么系统不会重新启动该应用
包含service/broadcastReceiver,只有一个Activity,那么系统会重新启动该应用(包括service/broadcastReceiver) 但不会重启该界面

不包含service/broadcastReceiver,但是当前栈中包含两个Activity, A–>B, 如果B crash,那么系统会重启该应用(application)也会进入A
不包含service/broadcastReceiver,但是当前栈中包含三个Activity, A–>B–>C, 如果C crash,那么系统会重启该应用(application) 进入B,并且A仍然存在,即可以从重启的Back到A
若在后台:
不会重启
但是若重新在前台了 是会和在前台的表现一样

sevice是可以根据START_STICK标志位判断是否crash了重新启动

静态广播
收到了广播才会创建 而且是收到一次创建一次 执行一次构造函数
若crash了 不管应用在前台还是后台 只要应用创建了(application oncreate) 都是会重启
动态广播
是不会自动重启的 除非进入到了你的启动逻辑 因为动态广播启动和关闭是自己设置的

你可能感兴趣的:(android)