想要一個進程永遠的活著不被kill掉买决,這個觀點備受駁議督赤。對于開發(fā)來說泻蚊,想要實現(xiàn)的一些需求性雄,保證消息的到達率,這一項的實現(xiàn)是無可厚非诀拭;對于用戶來說病蛉,被不知名的服務(wù)一直生存在我的后臺進程铺然。就像隔壁老王在藏在衣柜里偷偷輸出魄健,是不是很難受沽瘦!有些進程农尖,又不能強制用戶給你加入白名單盛卡,那能怎么辦呢滑沧?只能用一些黑科技了滓技。
但是Android系統(tǒng)中在沒有白名單的情況下做一個任何情況下都不被殺死的應(yīng)用是基本不可能的令漂,只能大幅度提高進程的存活率。強制性的流氓手段外潜,有些不道德处窥。更何況我也不會QAQ~ 我也只不過看了大牛們的技術(shù)博客,自己寫幾個demo玄组,演示記錄一下而已滔驾。
進程的級別
首先谒麦,要了解進程的級別。Android的應(yīng)用中哆致,進程可分為:
- 前臺進程绕德,在手機頁面可被看見的。如:正在被onResume()的Activity摊阀;綁定該Activity的Service耻蛇;正執(zhí)行其 onReceive()的BroadcastReceiver胞此。
- 可見進程臣咖,雖然可被看見,但是在執(zhí)行onPause()方法中Activity漱牵。如:彈出dialog被遮擋的Activity夺蛇;綁定該Activity的Service也級別也隨之降低。
- 服務(wù)進程酣胀,獨立于應(yīng)用的進程或者是私有進程刁赦,且正在運行 startService() 方法啟動的服務(wù)。
- 后臺進程闻镶,不可見Activity甚脉,執(zhí)行了onStop()方法。
-
空進程铆农,不包含任何活躍的應(yīng)用組件的進程
(盜用一下網(wǎng)上的圖)
對應(yīng)的不同級別的進程牺氨,系統(tǒng)都有一個oom_adj的值來標識,不同進程的級別高低顿涣。
綠色標識不容易被系統(tǒng)回收的進程波闹。系統(tǒng)當處于內(nèi)存緊缺時候,需要通過Lowmemorykiller 涛碑,kill掉一些對系統(tǒng)來說無關(guān)緊要的進程精堕。斬殺的規(guī)則就是,根據(jù)oom_adj大小蒲障。從最大的開始歹篓,當兩個oom_adj大小一樣時,先kill占用內(nèi)存大的那個揉阎。
ADB Shell使用
哇庄撮,第一次使用adb命令,感覺low爆了毙籽。要看進程活著還是死的洞斯,總得有個地方可以查看的嘛。原來Android Studio 命令框可以這么用的坑赡。用真機調(diào)試時候烙如,adb shell 進入的并不是root么抗,對應(yīng)的角標“$”,其權(quán)限也只能看到對應(yīng)進程的列表亚铁,只有手機root之后或者用模擬器才能看到進程的詳細信息蝇刀。角標也是“#”
可以通過
cat /proc/進程id/oom_adj
來查看。返回了一個值徘溢,對應(yīng)上面那張表吞琐,可直接看出對應(yīng)的進程所處在的級別。我們要做的是然爆,對我們需要的進程站粟,希望在其被置為不可見時候,其進程級別不會被降低施蜜,或者說不被降很多卒蘸。所以也就有了一下的“黑科技”雌隅。
一個像素的Activity
這個方法翻默,好像在網(wǎng)上已經(jīng)人盡皆知了的樣子。實現(xiàn)原理恰起,也就是監(jiān)聽手機屏幕的開啟和關(guān)閉修械,來打開和關(guān)閉這個1像素的Activity。這個需求只能做到检盼,屏幕熄黑后肯污,進程的級別不會被降低。
//屏幕點亮關(guān)閉監(jiān)聽
interface ScreenStateListener {
void onScreenOn();
void onScreenOff();
}
/**
* screen狀態(tài)廣播接收者
*/
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();
}
}
}
這個是1像素Activity創(chuàng)建的管理類吨枉。因為該Activity頻繁被創(chuàng)建和銷毀蹦渣,所以將其放置弱引用堆棧里,便于內(nèi)存回收貌亭。
public class ScreenManager {
private Context mContext;
//弱引用柬唯,LiveActivity在應(yīng)用可視下finish,創(chuàng)建頻繁
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() {
LiveActivity.actionToLiveActivity(mContext);
}
public void finishActivity() {
//結(jié)束掉LiveActivity
if (mActivityWref != null) {
Activity activity = mActivityWref.get();
if (activity != null) {
activity.finish();
}
}
}
}
LiveService 的具體操作圃庭,在應(yīng)用的進程開啟時候同時開啟這個Service锄奢,對屏幕關(guān)閉開啟進行監(jiān)控
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//屏幕關(guān)閉的時候啟動一個1像素的Activity,開屏的時候關(guān)閉Activity
final ScreenManager screenManager = ScreenManager.getInstance(LiveService.this);
ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
@Override
public void onScreenOn() {
screenManager.finishActivity();
}
@Override
public void onScreenOff() {
screenManager.startActivity();
}
});
return START_REDELIVER_INTENT;
}
主要的原理就熄屏時候剧腻,偷偷的開啟著一個無界面的Activity拘央,來告訴手機我這個進程是活躍著的。在屏幕亮起時候书在,則銷毀這個Activity灰伟,由現(xiàn)有的界面UI取代。但是我發(fā)現(xiàn)當應(yīng)用被置于后臺后儒旬,應(yīng)用的oom_adj直接變?yōu)?(后臺進程)栏账,所以滿足不了進程級別一直保持遏乔。我也很可愛的,在程序進入后臺也開啟這個1像素发笔,我在Application生命周期中盟萨,對這個Activity進行一樣的操作。奈何了讨。捻激。當我進入后臺后,過了一會前计,app又被重新拉起胞谭,顯示在頁面。關(guān)不掉了哈哈~
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
final ScreenManager screenManager = ScreenManager.getInstance(this);
screenManager.finishActivity();
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
screenManager.startActivity();
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
既然不能滿足男杈,那只能換種方法思路丈屹。。伶棒。旺垒。。
設(shè)置為前臺服務(wù)
據(jù)說微信用的是這個方案肤无,實現(xiàn)原理是利用了Android的前臺漏洞先蒋。對API<180時,調(diào)用startForeground(ID宛渐, new Notification())竞漾,這樣就不就有有圖標顯示。在API>18時,需要調(diào)用一個輔助的Service,綁定同一個ID勇边,然后并且stop 這個輔助Service,這樣通知欄也不會有任何顯示
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以下笔时,直接發(fā)送Notification并將其置為前臺
if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
startForeground(NOTIFICATION_ID, new Notification());
} else {
//API 18以上,發(fā)送Notification并將其置為前臺后幔荒,啟動InnerService
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, FuZhuService.class));
}
}
}
public class FuZhuService extends Service{
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//發(fā)送與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);
}
}
最后當應(yīng)用點擊后退后,退置后臺時候爹梁,該應(yīng)用的進程保持0變?yōu)?的程度右犹,也就保證了進程的存活率
系統(tǒng)自帶的服務(wù)
在Service的onStartCommand方法中返回一個整型,系統(tǒng)識別這個整型姚垃,進行判斷念链,如果被kill將會進行說明操作。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}
START_STICKY
如果系統(tǒng)在onStartCommand返回后被銷毀,系統(tǒng)將會重新創(chuàng)建服務(wù)并依次調(diào)用onCreate和onStartCommand(注意:根據(jù)測試Android2.3.3以下版本只會調(diào)用onCreate根本不會調(diào)用onStartCommand掂墓,Android4.0可以辦到)谦纱,這種相當于服務(wù)又重新啟動恢復到之前的狀態(tài)了)。START_NOT_STICKY
如果系統(tǒng)在onStartCommand返回后被銷毀君编,如果返回該值跨嘉,則在執(zhí)行完onStartCommand方法后如果Service被殺掉系統(tǒng)將不會重啟該服務(wù)。START_REDELIVER_INTENT
START_STICKY的兼容版本吃嘿,不同的是其不保證服務(wù)被殺后一定能重啟祠乃。
使用后好像不怎么靈巧,保留使用兑燥。亮瓷。。降瞳。
總結(jié)
總結(jié)一波嘱支,作為一個有情懷的開發(fā)者,我們都知道每當用戶關(guān)閉一個程序時挣饥,我們的程序就應(yīng)該徹底地死去并釋放其所占用的系統(tǒng)資源除师,這個淺顯的道理不僅適用于我們移動應(yīng)用開發(fā),也適用于任何桌面程序的開發(fā)亮靴。
但是作為開發(fā)者馍盟,我們更多的是面對產(chǎn)品經(jīng)理開發(fā)于置,針對各種市場資源需要茧吊,總會想盡辦法。對于常駐型進程八毯,在使用的時候搓侄,還是得三四而后行。當然特殊需求可以特殊考慮话速,為了用戶體驗更好的是沒話說的讶踪。
這篇文章更多的是站在網(wǎng)上各路大牛們的基礎(chǔ)上閱讀,尚未達到創(chuàng)造的級別泊交。慢慢努力乳讥。自此,感謝網(wǎng)上的大們牛寫的黑科技1 黑科技2