Android程序保活(最新)帶你淺析這幾種可行性的保活方案
1.概述
據前人驗證,在沒有白名單的情況下,安卓系統要做一個任何情況下都不被殺死的應用是基本不可能的,但是我們可以做到應用基本不被殺死,如果殺死可以立即復活.經過上網查詢,程序常駐的方案眾說紛紜,但是很多的方案都是不靠譜的或不是最好的,結合很多資料,今天總結一下Android程序保活的一些可行方法.
2.問題
系統為什麼會殺掉程序,殺的為什麼是我們的程序,這是根據什麼規則來決定的,是一次性幹掉多個程序,還是一個接著一個殺掉?保活套路一堆,如何進行程序保活才是比較恰當......
3.分析
3.1程序的劃分
Android中的程序也是有著嚴格的等級,分了三流九等,Android系統把程序劃為瞭如下幾種(重要性從高到低):
3.1.1. 前臺程序 —— Foreground process
使用者當前操作所必需的程序。通常在任意給定時間前臺程序都為數不多。只有在記憶體不足以支援它們同時繼續執行這一萬不得已的情況下,系統才會終止它們。
A. 擁有使用者正在互動的 Activity(已呼叫 onResume()
)
B. 擁有某個 Service,後者繫結到使用者正在互動的 Activity
C. 擁有正在“前臺”執行的 Service(服務已呼叫 startForeground()
)
D. 擁有正執行一個生命週期回撥的 Service(onCreate()
、onStart()
或 onDestroy()
E. 擁有正執行其 onReceive()
方法的 BroadcastReceiver
3.1.2. 可見程序 —— Visible process
沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程序。可見程序被視為是極其重要的程序,除非為了維持所有前臺程序同時執行而必須終止,否則系統不會終止這些程序。
A. 擁有不在前臺、但仍對使用者可見的 Activity(已呼叫 onPause()
)。
B. 擁有繫結到可見(或前臺)Activity 的 Service
3.1.3. 服務程序 —— Service process
儘管服務程序與使用者所見內容沒有直接關聯,但是它們通常在執行一些使用者關心的操作(例如,在後臺播放音樂或從網路下載資料)。因此,除非記憶體不足以維持所有前臺程序和可見程序同時執行,否則系統會讓服務程序保持執行狀態。
A. 正在執行 startService()
方法啟動的服務,且不屬於上述兩個更高類別程序的程序。
3.1.4. 後臺程序 —— Background process
後臺程序對使用者體驗沒有直接影響,系統可能隨時終止它們,以回收記憶體供前臺程序、可見程序或服務程序使用。 通常會有很多後臺程序在執行,因此它們會儲存在 LRU 列表中,以確保包含使用者最近檢視的 Activity
的程序最後一個被終止。如果某個 Activity 正確實現了生命週期方法,並儲存了其當前狀態,則終止其程序不會對使用者體驗產生明顯影響,因為當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態。
A. 對使用者不可見的 Activity 的程序(已呼叫 Activity的onStop()
方法)
3.1.5. 空程序 —— Empty process
保留這種程序的的唯一目的是用作快取,以縮短下次在其中執行元件所需的啟動時間。 為使總體系統資源在程序快取和底層核心快取之間保持平衡,系統往往會終止這些程序。
A. 不含任何活動應用元件的程序
具體地,活動程序指的就是使用者正在操作的程式,是前臺程序,可以看到且能夠操作;可見程序就是看得見摸不著的,不能直接操作的程序;服務程序是沒有介面的一直在後臺工作的程序,優先順序不高,當系統記憶體不足時會被殺死,再次充裕的時候會再次開啟;後臺程序就是使用者按了"back"或者"home"後,程式本身看不到了,但是其實還在執行的程式,比如Activity呼叫了onPause方法系統可能隨時終止它們,回收記憶體.空程序:某個程序不包含任何活躍的元件時該程序就會被置為空程序,完全沒用,殺了它只有好處沒壞處,第一個被處理!
3.2記憶體閾值
程序是怎麼被殺的呢?系統出於體驗和效能上的考慮,app在退到後臺時系統並不會真正的kill掉這個程序,而是將其快取起來。開啟的應用越多,後臺快取的程序也越多。在系統記憶體不足的情況下,系統開始依據自身的一套程序回收機制來判斷要kill掉哪些程序,以騰出記憶體來供給需要的app, 這套殺程序回收記憶體的機制就叫 Low Memory Killer。那這個不足怎麼來規定呢,那就是記憶體閾值,我們可以使用cat /sys/module/lowmemorykiller/parameters/minfree來檢視某個手機的記憶體閾值。
注意這些數字的單位是page. 1 page = 4 kb.上面的六個數字對應的就是(MB): 72,90,108,126,144,180,這些數字也就是對應的記憶體閥值,記憶體閾值在不同的手機上不一樣,一旦低於該值,Android便開始按順序關閉程序. 因此Android開始結束優先順序最低的空程序,即當可用記憶體小於180MB(46080*4/1024)。
程序是有它的優先順序的,這個優先順序通過程序的adj值來反映,它是linux核心分配給每個系統程序的一個值,代表程序的優先順序,程序回收機制就是根據這個優先順序來決定是否進行回收,adj值定義在com.android.server.am.ProcessList類中,這個類路徑是${android-sdk-path}\sources\android-23\com\android\server\am\ProcessList.java。oom_adj的值越小,程序的優先順序越高,普通程序oom_adj值是大於等於0的,而系統程序oom_adj的值是小於0的,我們可以通過cat /proc/程序id/oom_adj可以看到當前程序的adj值。
也就是說,oom_adj越大,佔用實體記憶體越多會被最先kill掉,OK,那麼現在對於程序如何保活這個問題就轉化成,如何降低oom_adj的值,以及如何使得我們應用佔的記憶體最少。
方案1
開啟一個畫素的Activity
據說這個是手Q的程序保活方案,基本思想,系統一般是不會殺死前臺程序的。所以要使得程序常駐,我們只需要在鎖屏的時候在本程序開啟一個Activity,為了欺騙使用者,讓這個Activity的大小是1畫素,並且透明無切換動畫,在開螢幕的時候,把這個Activity關閉掉,所以這個就需要監聽系統鎖屏廣播.
public class SinglePixelActivity extends Activity {
public static final String TAG = SinglePixelActivity.class.getSimpleName();
public static void actionToSinglePixelActivity(Context pContext) {
Intent intent = new Intent(pContext, SinglePixelActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pContext.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_singlepixel);
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 onDestroy() {
super.onDestroy();
}
}
在螢幕關閉的時候把SinglePixelActivity啟動起來,在開屏的時候把SinglePixelActivity 關閉掉,所以要監聽系統鎖屏廣播,以介面的形式通知MainActivity啟動或者關閉SinglePixActivity。
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() {
SinglePixelActivity.actionToSinglePixelActivity(mContext);
}
public void finishActivity() {
//結束掉SinglePixelActivity
if (mActivityWref != null) {
Activity activity = mActivityWref.get();
if (activity != null) {
activity.finish();
}
}
}
}
現在MainActivity改成如下
public class MainActivity extends AppCompatActivity {
@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();
}
});
}
}
按下back之後,進行鎖屏,現在測試一下oom_adj的值
果然將程序的優先順序提高了。
方案2
據說這個微信也用過的程序保活方案,該方案實際利用了Android前臺service的漏洞。
原理如下
對於 API level < 18 :呼叫startForeground(ID, new Notification()),傳送空的Notification ,圖示則不會顯示。
對於 API level >= 18:在需要提優先順序的service A啟動一個InnerService,兩個服務同時startForeground,且繫結同樣的 ID。Stop 掉InnerService ,這樣通知欄圖示即被移除。
public class KeepLiveService extends Service {
public static final int NOTIFICATION_ID=0x11;
public KeepLiveService() {
}
@Override
public IBinder onBind(Intent intent) {
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(NOTIFICATION_ID, new Notification());
} else {
//API 18以上,傳送Notification並將其置為前臺後,啟動InnerService
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, InnerService.class));
}
}
public static class InnerService extends Service{
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//傳送與KeepLiveService中ID相同的Notification,然後將其取消並取消自己的前臺顯示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
stopSelf();
}
},100);
}
}
}
在沒有采取前臺服務之前,啟動應用,oom_adj值是0,按下返回鍵之後,變成9(不同ROM可能不一樣)
在採取前臺服務之後,啟動應用,oom_adj值是0,按下返回鍵之後,變成2(不同ROM可能不一樣),確實程序的優先順序有所提高。
方案3
程序相互喚醒
顧名思義,就是指的不同程序,不同app之間互相喚醒,如你手機裡裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你開啟任意一個阿里系的app後,有可能就順便把其他阿里系的app給喚醒了。
方案4
JobSheduler
JobSheduler是作為程序死後復活的一種手段,native程序方式最大缺點是費電, Native 程序費電的原因是感知主程序是否存活有兩種實現方式,在 Native 程序中通過死迴圈或定時器,輪訓判斷主程序是否存活,當主程序不存活時進行拉活。其次5.0以上系統不支援。 但是JobSheduler可以替代在Android5.0以上native程序方式,這種方式即使使用者強制關閉,也能被拉起來,親測可行。
[email protected](Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
@Override
public void onCreate() {
super.onCreate();
startJobSheduler();
}
public void startJobSheduler() {
try {
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
builder.setPeriodic(5);
builder.setPersisted(true);
JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
這個是系統自帶的,onStartCommand方法必須具有一個整形的返回值,這個整形的返回值用來告訴系統在服務啟動完畢後,如果被Kill,系統將如何操作,這種方案雖然可以,但是在某些情況or某些定製ROM上可能失效,認為可以多做一種保保守方案。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}
-
START_STICKY
如果系統在onStartCommand返回後被銷燬,系統將會重新建立服務並依次呼叫onCreate和onStartCommand(注意:根據測試Android2.3.3以下版本只會呼叫onCreate根本不會呼叫onStartCommand,Android4.0可以辦到),這種相當於服務又重新啟動恢復到之前的狀態了)。 -
START_NOT_STICKY
如果系統在onStartCommand返回後被銷燬,如果返回該值,則在執行完onStartCommand方法後如果Service被殺掉系統將不會重啟該服務。 -
START_REDELIVER_INTENT
START_STICKY的相容版本,不同的是其不保證服務被殺後一定能重啟。
相比與粘性服務與系統服務捆綁更厲害一點,這裡說的系統服務很好理解,比如NotificationListenerService,NotificationListenerService就是一個監聽通知的服務,只要手機收到了通知,NotificationListenerService都能監聽到,即時使用者把程序殺死,也能重啟,所以說要是把這個服務放到我們的程序之中,那麼就得勁了
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class LiveService extends NotificationListenerService {
public LiveService() {
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
}
}
但是這種方式需要許可權
<service
android:name=".LiveService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
所以你的應用要是有訊息推送的話,那麼可以用這種方式去欺騙使用者。
總結
多種保活方式,沒有說哪一種最好,只有是在什麼場景下,使用哪一種最合適;當然,這些方式不是我發明或發現的,但是我覺得如果不知道的好好了解一下,對自己會有很大的幫助.掌握一些程序保活的手段,這不是耍流氓,是很多場景如果要想為使用者服務,就必須有一個程序常駐,以便在特定的時候做特定的事情。誠然,但凡程序常駐記憶體,無論怎樣優化,都會或多或少的增加一些額外的效能開支,在為使用者最負責任的服務,最高品質的體現我們的價值的前提下,我們要儘可能減少記憶體和電量的消耗。
親愛的讀者,如果此貼對您有幫助,請您動下發財的貴手幫忙右上角【點贊】支援下,非常感謝!