Citation Needed Please quote: juejin.cn/post/684490…
1. Opening Remarks
Many companies and developers now want their apps to stay in the phone’s memory for a long time, because as long as the app is running, we can do a lot of things, albeit unglamorous ones, like stealing traffic, wasting power, secretly installing apps and pushing advertising messages. Fortunately, the Road is high, the Android native system has done a lot of protection for the mobile phone system now, it is difficult to ensure that the process of an application can not be killed, we can do is to try to keep the process alive, next we will make a summary of the process alive.
Second, process related basic knowledge
Before we dive into process survival, let’s take a quick look at a few things about processes. First of all, what is the process, I believe programmers will be clear, process is the system to allocate resources and scheduling the smallest unit, very simple, each process is like an APP running in the mobile phone system.
1. How do I view processes
Adb shell ps can be used to view process information:
id | instructions |
---|---|
u0_a344 | The current user |
9153 | Pid process name |
201 | Ppid Indicates the name of the parent process |
1597348 | VSIZE Virtual memory size of the process |
com.sunland.staffapp | The process of |
2. Process division
According to the current state of the process, we can divide the process into five categories: foreground process, visible process, server process, background process, empty process. Each process is explained as follows:
2.1 Foreground Process
- An activity is visible when it is in the resume state
- When the process has a Service interacting with an activity
- If you have a Service and the service is running foreground, for example, startForeground is called
- When the holding service is performing a lifecycle method callback
- The onReceive operation is performed when holding broadcast
As long as the process is in any of the above states, it is the foreground process. The foreground process has the highest priority, and the system will not kill the foreground process unless the phone system runs out of memory.
2.2 Visible Process
- When an activity is in the onPause state, the activity is still visible to us, but we cannot interact with it.
- Have a Service that is bound to a visible (or foreground) Activity, but the Activity does not interact with the user
Visible processes are also extremely important to the system, and the system does not kill visible processes unless it has to.
2.3 Service Process
A service running in a process is started by startService and does not interact with the user interface. If the memory is insufficient to support foreground and visible processes, the service process is killed first.
2.4 Background Processes
When the application is in the background, for example, the user presses the back key or home key, the interface is not visible, but the application is still running. The activity is in the onpause state, which can be seen in the task manager.
2.5 empty process
An empty process is a process that does not contain any active processes. The system reserves the process mainly for caching, which facilitates fast next access. If the system memory is insufficient, the empty process is killed first.
3. How do I view the process priority
A process has a parameter, oom_adj. Generally speaking, the smaller the value of this parameter, the higher the priority. The foreground process has a value of 0.
Generally speaking, a process with a larger ADJ occupies more system memory and is killed first. We do process protection from these two aspects. Now it’s time to get down to business.
Three, the process of survival scheme
1, 1 pixel Activity
Since the foreground process is not easy to kill, we can try to start a foreground process, and the foreground process is not perceived by the user. A 1 pixel activity is sufficient. We can start an activity with a lock screen. Finish the activity when the screen is on. Considering memory problems, we can start the activity in a service that is in a separate process. Example:
/**
* foreground service forkeeping alive */ public class KeepAliveService extends Service { private static final String TAG = Constants.LOG_TAG; public static final int NOTICE_ID = 100; Private ScreenReceiverUtil mScreenUtil; // 1 pixel Activity management class private ScreenManager mScreenManager; private View toucherLayout; private WindowManager.LayoutParams params; private WindowManager windowManager; private ScreenReceiverUtil.ScreenStateListener mScreenStateListenerer = new ScreenReceiverUtil.ScreenStateListener() {
@Override
public void onSreenOn() {
L.d(TAG, "KeepAliveService-->finsh 1 pixel activity");
mScreenManager.finishActivity();
}
@Override
public void onSreenOff() {
L.d(TAG, "KeepAliveService-->start 1 pixel activity");
mScreenManager.startActivity();
}
@Override
public void onUserPresent() {}}; @override public IBinder onBind(Intent Intent) {return null;
}
@Override
public void onCreate() { super.onCreate(); // If the API is greater than 18, a visible Notification needs to pop up. This visible Notification can be hidden by Cancel until the API is greater than 25. // So start the foreground service between API 18 and API 24 and hide the Notificationif(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N){
startForeground(NOTICE_ID, new NotificationUtils(this).getNotification("Sunlands"."Clock in to remind the process is running")); // Remove notifications by starting CancelNoticeService, Oom_adj values Intent Intent = new Intent (this, CancelNoticeService. Class); startService(intent); }else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
startForeground(NOTICE_ID, new Notification());
}
mScreenUtil = new ScreenReceiverUtil(this);
mScreenManager = ScreenManager.getInstance(this);
mScreenUtil.setScreenReceiverListener(mScreenStateListenerer);
createFloatingWindow();
L.d(TAG, "KeepAliveService-->KeepAliveService created");
// If app process is killed system, all task scheduled by AlarmManager is canceled.
// We need reschedule all task when KeepAliveService is revived.
TaskUtil.dispatchAllTask(this);
}
@Override
public void onDestroy() { super.onDestroy(); // If Service is killed, kill notificationif(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
NotificationManager mManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
mManager.cancel(NOTICE_ID);
}
if(toucherLayout ! = null) { ensureWindowManager(); try { L.d(TAG,"KeepAliveService-->remove floating window");
windowManager.removeView(toucherLayout);
} catch (Exception e) {
L.e(TAG, e == null ? "" : e.getMessage());
}
}
mScreenUtil.stopScreenReceiverListener();
}
public static void startKeepAliveServiceIfNeed(Context context) {
boolean isKeepAliveServiceEnabled = SystemUtils.isComponentEnabled(context.getApplicationContext(), KeepAliveService.class);
if (isKeepAliveServiceEnabled) {
try {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
if(! LifecycleHandler.getInstance().isForeground()) {return;
}
}
Intent intentAlive = new Intent(context.getApplicationContext(), KeepAliveService.class);
context.startService(intentAlive);
L.d(TAG, "KeepAliveService-->start KeepAliveService");
} catch (Exception e) {
L.e(TAG, e == null ? "" : e.getMessage());
}
}
}
private void createFloatingWindow() {// MIUI cannot display the hover window in the background with TYPE_TOAST and must obtain draw_over_other_app permissionif(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && OSJudgementUtil.isMIUI() && ! Settings.canDrawOverlays(this)) { L.d(TAG,"KeepAliveService-->MIUI needs draw overlay permission");
return; } / / assignment WindowManager&LayoutParam params = new WindowManager. LayoutParams (); DiShiXing window. / / set the type system, generally in the application window. Params. Type = WindowManager. LayoutParams. TYPE_TOAST; // Limited use of SYSTEM_ALERT, higher priorityif(OSJudgementUtil.isMIUI() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this))) { params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; Format = pixelformat.rgba_8888;} // Set the effect to transparent background. / / set flags. Do not focus and do not use buttons to control floating window. Params. Flags = WindowManager. LayoutParams. FLAG_NOT_FOCUSABLE; / / dock Settings window initial position. Params. Gravity = gravity. LEFT | gravity. The TOP; params.x = 0; params.y = 0; Params. Width = 1; params.height = 1; ToucherLayout = new View(this); //toucherLayout.setBackgroundColor(0x55ffffff); // Add ToucherLayout ensureWindowManager(); try { L.d(TAG,"KeepAliveService-->create floating window");
windowManager.addView(toucherLayout,params);
} catch (Exception e) {
L.e(TAG, e == null ? "" : e.getMessage());
}
}
private void ensureWindowManager() {
if(windowManager == null) { windowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE); }}}Copy the code
public class SinglePixelActivity extends AppCompatActivity {
private static final String TAG = Constants.LOG_TAG;
private ScreenReceiverUtil.ScreenStateListener mScreenStateListenerer = new ScreenReceiverUtil.ScreenStateListener() {
@Override
public void onSreenOn() {
if(! isFinishing()) { finish(); } } @Override public voidonSreenOff() {
}
@Override
public void onUserPresent() {
if(! isFinishing()) { finish(); }}}; private ScreenReceiverUtil mScreenUtil; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); L.d(TAG,"SinglePixelActivity--->onCreate"); 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); / / to bind SinglePixelActivity ScreenManager ScreenManager. GetInstance (this). SetSingleActivity (this); mScreenUtil = new ScreenReceiverUtil(this); mScreenUtil.setScreenReceiverListener(mScreenStateListenerer); } @Override public boolean onTouchEvent(MotionEvent event) { L.d(TAG,"SinglePixelActivity onTouchEvent-->finsih()");
if(! isFinishing()) { finish(); }return false;
}
@Override
protected void onDestroy() {
super.onDestroy();
L.d(TAG,"SinglePixelActivity-->onDestroy()");
if(mScreenUtil ! = null) { mScreenUtil.stopScreenReceiverListener(); }if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
if(! LifecycleHandler.getInstance().isForeground()) {return;
}
}
try {
Intent intentAlive = new Intent(this, KeepAliveService.class);
startService(intentAlive);
} catch (Exception e) {
L.e(TAG, e == null ? "": e.getMessage()); }}}Copy the code
Listen to the system lock screen broadcast, start the activity when the lock screen, turn off the open screen can be.
<service
android:name=".plantask.KeepAliveService"
android:enabled="false"
android:exported="true"
android:process=":keepAlive" />
Copy the code
If the service is in a separate process, the system will kill the background process after locking the screen for a period of time to save power.
Limitations: After Android 5.0, when the system kills a process, it will also kill the process in that process group.
Process.killProcessQuiet(app.pid);
Process.killProcessGroup(app.info.uid, app.pid);
Copy the code
So after 5.0, this method is not very reliable, we will need to find another method.
2. Reception service
This method can be said to be very reliable, the main principles are as follows:
For API level < 18: call startForeground(ID, ewNotification()) and send empty Notification, the icon will not be displayed.
For API level >= 18: Start an InnerService whose priority needs to be improved, and both services are startForeground, and bound with the same ID. Stop the InnerService so that the notification bar icon is removed. Here I also give a practical example:
public void onCreate() { super.onCreate(); // If the API is greater than 18, a visible notification needs to pop up. This visible notification can be hidden by Cancel until the API is greater than 25. And hide Notification if(build.version.sdk_int >= build.version_codes.jelly_bean_mr2&& build.version.sdk_int <= Build.VERSION_CODES.N){ startForeground(NOTICE_ID, new NotificationUtils(this).getNotification("Sunlands", "Clock in to remind the process is running ")); // Remove notifications by starting CancelNoticeService, Oom_adj values Intent Intent = new Intent (this, CancelNoticeService. Class); startService(intent); }else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { startForeground(NOTICE_ID, new Notification()); } mScreenUtil = new ScreenReceiverUtil(this); mScreenManager = ScreenManager.getInstance(this); mScreenUtil.setScreenReceiverListener(mScreenStateListenerer); createFloatingWindow(); L.d(TAG, "KeepAliveService-->KeepAliveService created"); // If app process is killed system, all task scheduled by AlarmManager is canceled. // We need reschedule all task when KeepAliveService is revived. TaskUtil.dispatchAllTask(this); }Copy the code
Continue to see CancelNoticeService
public class CancelNoticeService extends Service {
private static final String TAG = Constants.LOG_TAG;
@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){
L.d(TAG, "CancelNoticeService-->onStartCommand() begin");
//Notification.Builder builder = new Notification.Builder(this);
//builder.setSmallIcon(R.drawable.earth);
startForeground(KeepAliveService.NOTICE_ID, new NotificationUtils(this).getNotification("Sunlands"."Clock in to remind the process is running")); // Start a Thread to remove the notification from DaemonService new Thread(new)Runnable() {
@Override
public void run() {// delay 1s systemclock. sleep(50); // Cancel foreground stopForeground(CancelNoticeService)true); // Remove the notification displayed by DaemonService NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); manager.cancel(KeepAliveService.NOTICE_ID); // stopSelf(); L.d(TAG,"CancelNoticeService-->onStartCommand() end");
}
}).start();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() { super.onDestroy(); }}Copy the code
This way is essentially the use of the Android system service service vulnerability, wechat is also using this method to achieve the purpose of survival.
3. Wake each other up
As the name suggests, when the process is in the background and has low priority or is killed, another process can wake you up or pull you back to life. There are several options.
1. Use the system broadcast
Listen for some system broadcast, such as restart, start camera, etc., after listening to the broadcast can pull the process, but Android N has cancelled some system broadcast.
2. Broadcast from frequently used apps
As a matter of fact, QQ and wechat apps are frequently used in mobile phones. We can decomcompile these apps, obtain the broadcasts they can send, and then monitor these broadcasts, and then pull the process.
3. Make use of third-party push mechanisms
Such as carrier pigeon, aurora push, have the function of waking up the app.
4. Binding of sticky service and system service
This method is not reliable, but it can be a kind of insurance, the system service has onStartCommand method
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}
Copy the code
We can do special processing on the return value with the following parameters:
- START_STICKY
If the system is destroyed after onStartCommand returns, the system recreates the service and calls onCreate and onStartCommand in sequence (note: Android4.0 can do this), which is equivalent to restarting the service and restoring it to its previous state.
- START_NOT_STICKY
If the system is destroyed after onStartCommand returns, the system will not restart the Service if it is killed after executing the onStartCommand method.
- START_REDELIVER_INTENT
A compatible version of START_STICKY. The difference is that it does not guarantee that services can be restarted after being killed.
System services, the use of NotificationListenerService, only mobile phone notification will listen to, if the received message is more than words can use this way to deal with, and be killed even if the process can also be listening to, this is how the cow force.